Merge github.com:paritytech/parity into block_header_rpc

This commit is contained in:
Robert Habermeier 2017-04-05 16:27:32 +02:00
commit 4f843ada3c
122 changed files with 6769 additions and 2358 deletions

166
Cargo.lock generated
View File

@ -10,7 +10,6 @@ dependencies = [
"docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.7.0 (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)",
"ethcore 1.7.0", "ethcore 1.7.0",
"ethcore-dapps 1.7.0",
"ethcore-devtools 1.7.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.7.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.7.0", "ethcore-ipc 1.7.0",
@ -24,15 +23,17 @@ dependencies = [
"ethcore-signer 1.7.0", "ethcore-signer 1.7.0",
"ethcore-stratum 1.7.0", "ethcore-stratum 1.7.0",
"ethcore-util 1.7.0", "ethcore-util 1.7.0",
"ethkey 0.2.0",
"ethsync 1.7.0", "ethsync 1.7.0",
"evmbin 0.1.0", "evmbin 0.1.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)",
"hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "futures 0.1.11 (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.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (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)",
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps 1.7.0",
"parity-hash-fetch 1.7.0", "parity-hash-fetch 1.7.0",
"parity-ipfs-api 1.7.0", "parity-ipfs-api 1.7.0",
"parity-local-store 0.1.0", "parity-local-store 0.1.0",
@ -40,6 +41,7 @@ dependencies = [
"parity-rpc-client 1.4.0", "parity-rpc-client 1.4.0",
"parity-updater 1.7.0", "parity-updater 1.7.0",
"path 0.1.0", "path 0.1.0",
"pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
"rlp 0.1.0", "rlp 0.1.0",
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -309,6 +311,11 @@ dependencies = [
"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)",
] ]
[[package]]
name = "difference"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "docopt" name = "docopt"
version = "0.7.0" version = "0.7.0"
@ -446,40 +453,6 @@ dependencies = [
"siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "ethcore-dapps"
version = "1.7.0"
dependencies = [
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.7.0",
"ethcore-rpc 1.7.0",
"ethcore-util 1.7.0",
"fetch 0.1.0",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (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)",
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.7.0",
"parity-reactor 0.1.0",
"parity-ui 1.7.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ethcore-devtools" name = "ethcore-devtools"
version = "1.7.0" version = "1.7.0"
@ -561,6 +534,7 @@ name = "ethcore-light"
version = "1.7.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore 1.7.0", "ethcore 1.7.0",
"ethcore-devtools 1.7.0",
"ethcore-io 1.7.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.7.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.7.0", "ethcore-ipc-codegen 1.7.0",
@ -642,6 +616,7 @@ dependencies = [
"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)",
"jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-minihttp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
@ -661,6 +636,9 @@ dependencies = [
name = "ethcore-secretstore" name = "ethcore-secretstore"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.7.0",
"ethcore-devtools 1.7.0", "ethcore-devtools 1.7.0",
"ethcore-ipc 1.7.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.7.0", "ethcore-ipc-codegen 1.7.0",
@ -668,9 +646,19 @@ dependencies = [
"ethcore-util 1.7.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"native-contracts 0.1.0",
"parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1088,7 +1076,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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1100,7 +1088,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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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)",
@ -1113,7 +1101,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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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)",
@ -1125,17 +1113,31 @@ 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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "jsonrpc-minihttp-server"
version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)",
"tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1145,7 +1147,7 @@ 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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1156,7 +1158,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#f4521e8a543145bec7936de0f875d6550e92c7f7" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#32c1c083139db50db6a5d532ccfc2004236dbfc3"
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)",
@ -1604,6 +1606,38 @@ dependencies = [
"stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "parity-dapps"
version = "1.7.0"
dependencies = [
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.7.0",
"ethcore-util 1.7.0",
"fetch 0.1.0",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (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)",
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.7.0",
"parity-reactor 0.1.0",
"parity-ui 1.7.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "parity-dapps-glue" name = "parity-dapps-glue"
version = "1.7.0" version = "1.7.0"
@ -1723,7 +1757,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#6028c355854797a5938c26f5d2b2faf10d8833d7" source = "git+https://github.com/paritytech/js-precompiled.git#9bfc6f3dfca2c337c53084bedcc65c2b526927a1"
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)",
] ]
@ -1826,6 +1860,14 @@ name = "podio"
version = "0.1.5" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pretty_assertions"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "primal" name = "primal"
version = "0.2.3" version = "0.2.3"
@ -2431,6 +2473,21 @@ dependencies = [
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "tokio-minihttp"
version = "0.1.0"
source = "git+https://github.com/tomusdrw/tokio-minihttp#8acbafae3e77e7f7eb516b441ec84695580221dd"
dependencies = [
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "tokio-named-pipes" name = "tokio-named-pipes"
version = "0.1.0" version = "0.1.0"
@ -2441,6 +2498,22 @@ dependencies = [
"tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "tokio-proto"
version = "0.1.0"
source = "git+https://github.com/tomusdrw/tokio-proto#f6ee08cb594fa2fc1b4178eaaca0855d66e68fd3"
dependencies = [
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "tokio-proto" name = "tokio-proto"
version = "0.1.0" version = "0.1.0"
@ -2706,6 +2779,7 @@ dependencies = [
"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 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 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 docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8" "checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8"
"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f"
"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"
@ -2739,6 +2813,7 @@ dependencies = [
"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-http-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-ipc-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-macros 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-minihttp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
"checksum jsonrpc-tcp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>" "checksum jsonrpc-tcp-server 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)" = "<none>"
@ -2802,6 +2877,7 @@ dependencies = [
"checksum phf_shared 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fee4d039930e4f45123c9b15976cf93a499847b6483dc09c42ea0ec4940f2aa6" "checksum phf_shared 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fee4d039930e4f45123c9b15976cf93a499847b6483dc09c42ea0ec4940f2aa6"
"checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa"
"checksum podio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e5422a1ee1bc57cc47ae717b0137314258138f38fd5f3cea083f43a9725383a0" "checksum podio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e5422a1ee1bc57cc47ae717b0137314258138f38fd5f3cea083f43a9725383a0"
"checksum pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2412f3332a07c7a2a50168988dcc184f32180a9758ad470390e5f55e089f6b6e"
"checksum primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0e31b86efadeaeb1235452171a66689682783149a6249ff334a2c5d8218d00a4" "checksum primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0e31b86efadeaeb1235452171a66689682783149a6249ff334a2c5d8218d00a4"
"checksum primal-bit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "464a91febc06166783d4f5ba3577b5ed8dda8e421012df80bfe48a971ed7be8f" "checksum primal-bit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "464a91febc06166783d4f5ba3577b5ed8dda8e421012df80bfe48a971ed7be8f"
"checksum primal-check 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "647c81b67bb9551a7b88d0bcd785ac35b7d0bf4b2f358683d7c2375d04daec51" "checksum primal-check 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "647c81b67bb9551a7b88d0bcd785ac35b7d0bf4b2f358683d7c2375d04daec51"
@ -2871,7 +2947,9 @@ dependencies = [
"checksum tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3d1be481b55126f02ef88ff86748086473cb537a949fc4a8f4be403a530ae54b" "checksum tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3d1be481b55126f02ef88ff86748086473cb537a949fc4a8f4be403a530ae54b"
"checksum tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6a278fde45f1be68e44995227d426aaa4841e0980bb0a21b981092f28c3c8473" "checksum tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6a278fde45f1be68e44995227d426aaa4841e0980bb0a21b981092f28c3c8473"
"checksum tokio-line 0.1.0 (git+https://github.com/tokio-rs/tokio-line)" = "<none>" "checksum tokio-line 0.1.0 (git+https://github.com/tokio-rs/tokio-line)" = "<none>"
"checksum tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)" = "<none>"
"checksum tokio-named-pipes 0.1.0 (git+https://github.com/alexcrichton/tokio-named-pipes)" = "<none>" "checksum tokio-named-pipes 0.1.0 (git+https://github.com/alexcrichton/tokio-named-pipes)" = "<none>"
"checksum tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)" = "<none>"
"checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808" "checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808"
"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
"checksum tokio-uds 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc7b5fc8e19e220b29566d1750949224a518478eab9cebc8df60583242ca30a" "checksum tokio-uds 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc7b5fc8e19e220b29566d1750949224a518478eab9cebc8df60583242ca30a"

View File

@ -23,9 +23,9 @@ toml = "0.2"
serde = "0.9" serde = "0.9"
serde_json = "0.9" serde_json = "0.9"
app_dirs = "1.1.1" app_dirs = "1.1.1"
futures = "0.1"
fdlimit = "0.1" fdlimit = "0.1"
ws2_32-sys = "0.2" ws2_32-sys = "0.2"
hyper = { default-features = false, git = "https://github.com/paritytech/hyper" }
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
ethsync = { path = "sync" } ethsync = { path = "sync" }
@ -41,6 +41,7 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
ethcore-light = { path = "ethcore/light" } ethcore-light = { path = "ethcore/light" }
ethcore-logger = { path = "logger" } ethcore-logger = { path = "logger" }
ethcore-stratum = { path = "stratum" } ethcore-stratum = { path = "stratum" }
ethkey = { path = "ethkey" }
evmbin = { path = "evmbin" } evmbin = { path = "evmbin" }
rlp = { path = "util/rlp" } rlp = { path = "util/rlp" }
rpc-cli = { path = "rpc_cli" } rpc-cli = { path = "rpc_cli" }
@ -50,8 +51,9 @@ parity-ipfs-api = { path = "ipfs" }
parity-updater = { path = "updater" } parity-updater = { path = "updater" }
parity-reactor = { path = "util/reactor" } parity-reactor = { path = "util/reactor" }
parity-local-store = { path = "local-store" } parity-local-store = { path = "local-store" }
ethcore-dapps = { path = "dapps", optional = true }
path = { path = "util/path" } path = { path = "util/path" }
parity-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.103", optional = true} clippy = { version = "0.0.103", optional = true}
ethcore-secretstore = { path = "secret_store", optional = true } ethcore-secretstore = { path = "secret_store", optional = true }
@ -60,6 +62,7 @@ rustc_version = "0.2"
[dev-dependencies] [dev-dependencies]
ethcore-ipc-tests = { path = "ipc/tests" } ethcore-ipc-tests = { path = "ipc/tests" }
pretty_assertions = "0.1"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = "0.2" winapi = "0.2"
@ -71,18 +74,18 @@ daemonize = "0.2"
default = ["ui-precompiled"] default = ["ui-precompiled"]
ui = [ ui = [
"dapps", "dapps",
"ethcore-dapps/ui", "parity-dapps/ui",
"ethcore-signer/ui", "ethcore-signer/ui",
] ]
ui-precompiled = [ ui-precompiled = [
"dapps", "dapps",
"ethcore-signer/ui-precompiled", "ethcore-signer/ui-precompiled",
"ethcore-dapps/ui-precompiled", "parity-dapps/ui-precompiled",
] ]
dapps = ["ethcore-dapps"] dapps = ["parity-dapps"]
ipc = ["ethcore/ipc", "ethsync/ipc"] ipc = ["ethcore/ipc", "ethsync/ipc"]
jit = ["ethcore/jit"] jit = ["ethcore/jit"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "parity-dapps/dev", "ethcore-signer/dev"]
json-tests = ["ethcore/json-tests"] json-tests = ["ethcore/json-tests"]
test-heavy = ["ethcore/test-heavy"] test-heavy = ["ethcore/test-heavy"]
ethkey-cli = ["ethcore/ethkey-cli"] ethkey-cli = ["ethcore/ethkey-cli"]

View File

@ -1,6 +1,8 @@
# [Parity](https://ethcore.io/parity.html) # [Parity](https://ethcore.io/parity.html)
### Fast, light, and robust Ethereum implementation ### Fast, light, and robust Ethereum implementation
### [Download latest release](https://github.com/paritytech/parity/releases)
[![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url] [![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
### Join the chat! ### Join the chat!
@ -22,7 +24,6 @@ Be sure to check out [our wiki][wiki-url] for more information.
[doc-url]: https://paritytech.github.io/parity/ethcore/index.html [doc-url]: https://paritytech.github.io/parity/ethcore/index.html
[wiki-url]: https://github.com/paritytech/parity/wiki [wiki-url]: https://github.com/paritytech/parity/wiki
**Parity requires Rust version 1.15.0 to build**
---- ----
@ -45,14 +46,14 @@ of RPC APIs.
If you run into an issue while using parity, feel free to file one in this repository If you run into an issue while using parity, feel free to file one in this repository
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help! or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
Parity's current release is 1.5. You can download it at https://parity.io or follow the instructions Parity's current release is 1.6. You can download it at https://github.com/paritytech/parity/releases or follow the instructions
below to build from source. below to build from source.
---- ----
## Build dependencies ## Build dependencies
Parity is fully compatible with Stable Rust. **Parity requires Rust version 1.16.0 to build**
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this: We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
@ -80,7 +81,7 @@ Once you have rustup, install parity or download and build from source
---- ----
## Quick install ## Quick build and install
```bash ```bash
cargo install --git https://github.com/paritytech/parity.git parity cargo install --git https://github.com/paritytech/parity.git parity

View File

@ -1,6 +1,6 @@
[package] [package]
description = "Parity Dapps crate" description = "Parity Dapps crate"
name = "ethcore-dapps" name = "parity-dapps"
version = "1.7.0" version = "1.7.0"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
@ -28,11 +28,8 @@ zip = { version = "0.1", default-features = false }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
# TODO [ToDr] Temporary solution, server should be merged with RPC.
jsonrpc-server-utils = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
ethcore-devtools = { path = "../devtools" } ethcore-devtools = { path = "../devtools" }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
fetch = { path = "../util/fetch" } fetch = { path = "../util/fetch" }
parity-hash-fetch = { path = "../hash-fetch" } parity-hash-fetch = { path = "../hash-fetch" }
@ -42,7 +39,7 @@ parity-ui = { path = "./ui" }
clippy = { version = "0.0.103", optional = true} clippy = { version = "0.0.103", optional = true}
[features] [features]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] dev = ["clippy", "ethcore-util/dev"]
ui = ["parity-ui/no-precompiled-js"] ui = ["parity-ui/no-precompiled-js"]
ui-precompiled = ["parity-ui/use-precompiled-js"] ui-precompiled = ["parity-ui/use-precompiled-js"]

View File

@ -14,7 +14,6 @@
// 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/>.
use std::sync::Arc;
use unicase::UniCase; use unicase::UniCase;
use hyper::{server, net, Decoder, Encoder, Next, Control}; use hyper::{server, net, Decoder, Encoder, Next, Control};
use hyper::header; use hyper::header;
@ -26,48 +25,49 @@ use apps::fetcher::Fetcher;
use handlers::extract_url; use handlers::extract_url;
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
use jsonrpc_http_server; use jsonrpc_http_server::{self, AccessControlAllowOrigin};
use jsonrpc_server_utils::cors;
#[derive(Clone)] #[derive(Clone)]
pub struct RestApi { pub struct RestApi<F> {
cors_domains: Option<Vec<cors::AccessControlAllowOrigin>>, // TODO [ToDr] cors_domains should be handled by the server to avoid duplicated logic.
endpoints: Arc<Endpoints>, // RequestMiddleware should be able to tell that cors headers should be included.
fetcher: Arc<Fetcher>, cors_domains: Option<Vec<AccessControlAllowOrigin>>,
apps: Vec<App>,
fetcher: F,
} }
impl RestApi { impl<F: Fetcher + Clone> RestApi<F> {
pub fn new(cors_domains: Vec<cors::AccessControlAllowOrigin>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> { pub fn new(cors_domains: Vec<AccessControlAllowOrigin>, endpoints: &Endpoints, fetcher: F) -> Box<Endpoint> {
Box::new(RestApi { Box::new(RestApi {
cors_domains: Some(cors_domains), cors_domains: Some(cors_domains),
endpoints: endpoints, apps: Self::list_apps(endpoints),
fetcher: fetcher, fetcher: fetcher,
}) })
} }
fn list_apps(&self) -> Vec<App> { fn list_apps(endpoints: &Endpoints) -> Vec<App> {
self.endpoints.iter().filter_map(|(ref k, ref e)| { endpoints.iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| App::from_info(k, info)) e.info().map(|ref info| App::from_info(k, info))
}).collect() }).collect()
} }
} }
impl Endpoint for RestApi { impl<F: Fetcher + Clone> Endpoint for RestApi<F> {
fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> { fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
Box::new(RestApiRouter::new(self.clone(), path, control)) Box::new(RestApiRouter::new((*self).clone(), path, control))
} }
} }
struct RestApiRouter { struct RestApiRouter<F> {
api: RestApi, api: RestApi<F>,
cors_header: Option<header::AccessControlAllowOrigin>, cors_header: Option<header::AccessControlAllowOrigin>,
path: Option<EndpointPath>, path: Option<EndpointPath>,
control: Option<Control>, control: Option<Control>,
handler: Box<Handler>, handler: Box<Handler>,
} }
impl RestApiRouter { impl<F: Fetcher> RestApiRouter<F> {
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { fn new(api: RestApi<F>, path: EndpointPath, control: Control) -> Self {
RestApiRouter { RestApiRouter {
path: Some(path), path: Some(path),
cors_header: None, cors_header: None,
@ -114,7 +114,7 @@ impl RestApiRouter {
} }
} }
impl server::Handler<net::HttpStream> for RestApiRouter { impl<F: Fetcher> server::Handler<net::HttpStream> for RestApiRouter<F> {
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next { fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains).into(); self.cors_header = jsonrpc_http_server::cors_header(&request, &self.api.cors_domains).into();
@ -142,7 +142,7 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() } if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
let handler = endpoint.and_then(|v| match v { let handler = endpoint.and_then(|v| match v {
"apps" => Some(response::as_json(&self.api.list_apps())), "apps" => Some(response::as_json(&self.api.apps)),
"ping" => Some(response::ping()), "ping" => Some(response::ping()),
"content" => self.resolve_content(hash, path, control), "content" => self.resolve_content(hash, path, control),
_ => None _ => None

View File

@ -47,7 +47,8 @@ pub trait Fetcher: Send + Sync + 'static {
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler>; fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler>;
} }
pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + Send + Sync + 'static = URLHintContract> { #[derive(Clone)]
pub struct ContentFetcher<F: Fetch + Clone = FetchClient, R: URLHint + Clone + 'static = URLHintContract> {
dapps_path: PathBuf, dapps_path: PathBuf,
resolver: R, resolver: R,
cache: Arc<Mutex<ContentCache>>, cache: Arc<Mutex<ContentCache>>,
@ -57,14 +58,14 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + Send + Sync + 'st
fetch: F, fetch: F,
} }
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Drop for ContentFetcher<F, R> { impl<R: URLHint + Clone + 'static, F: Fetch + Clone> Drop for ContentFetcher<F, R> {
fn drop(&mut self) { fn drop(&mut self) {
// Clear cache path // Clear cache path
let _ = fs::remove_dir_all(&self.dapps_path); let _ = fs::remove_dir_all(&self.dapps_path);
} }
} }
impl<R: URLHint + Send + Sync + 'static, F: Fetch> ContentFetcher<F, R> { impl<R: URLHint + Clone + 'static, F: Fetch + Clone> ContentFetcher<F, R> {
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self { pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
let mut dapps_path = env::temp_dir(); let mut dapps_path = env::temp_dir();
@ -97,7 +98,7 @@ impl<R: URLHint + Send + Sync + 'static, F: Fetch> ContentFetcher<F, R> {
} }
} }
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> { impl<R: URLHint + Clone + 'static, F: Fetch + Clone> Fetcher for ContentFetcher<F, R> {
fn contains(&self, content_id: &str) -> bool { fn contains(&self, content_id: &str) -> bool {
{ {
let mut cache = self.cache.lock(); let mut cache = self.cache.lock();
@ -233,6 +234,7 @@ mod tests {
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
use super::{ContentFetcher, Fetcher}; use super::{ContentFetcher, Fetcher};
#[derive(Clone)]
struct FakeResolver; struct FakeResolver;
impl URLHint for FakeResolver { impl URLHint for FakeResolver {
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> { fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {

View File

@ -16,9 +16,10 @@
//! URL Endpoint traits //! URL Endpoint traits
use hyper::{self, server, net};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use hyper::{self, server, net};
#[derive(Debug, PartialEq, Default, Clone)] #[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath { pub struct EndpointPath {
pub app_id: String, pub app_id: String,

View File

@ -1,44 +0,0 @@
// 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/>.
//! Authorization Handlers
use hyper::{server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
pub struct AuthRequiredHandler;
impl server::Handler<HttpStream> for AuthRequiredHandler {
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Unauthorized);
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
Next::write()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

View File

@ -16,14 +16,12 @@
//! Hyper handlers implementations. //! Hyper handlers implementations.
mod auth;
mod content; mod content;
mod echo; mod echo;
mod fetch; mod fetch;
mod redirect; mod redirect;
mod streaming; mod streaming;
pub use self::auth::AuthRequiredHandler;
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};

View File

@ -34,9 +34,7 @@ extern crate zip;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate jsonrpc_http_server; extern crate jsonrpc_http_server;
extern crate jsonrpc_server_utils;
extern crate ethcore_rpc;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate fetch; extern crate fetch;
extern crate parity_dapps_glue as parity_dapps; extern crate parity_dapps_glue as parity_dapps;
@ -61,7 +59,6 @@ mod apps;
mod page; mod page;
mod router; mod router;
mod handlers; mod handlers;
mod rpc;
mod api; mod api;
mod proxypac; mod proxypac;
mod url; mod url;
@ -69,23 +66,16 @@ mod web;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::net::SocketAddr;
use std::collections::HashMap; use std::collections::HashMap;
use jsonrpc_core::{Middleware, MetaIoHandler}; use jsonrpc_http_server::{self as http, hyper, AccessControlAllowOrigin};
use jsonrpc_http_server::tokio_core::reactor::Remote as TokioRemote;
pub use jsonrpc_http_server::{DomainsValidation, Host, AccessControlAllowOrigin};
pub use jsonrpc_http_server::hyper;
use ethcore_rpc::Metadata; use fetch::Fetch;
use fetch::{Fetch, Client as FetchClient};
use hash_fetch::urlhint::ContractClient;
use parity_reactor::Remote; use parity_reactor::Remote;
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; pub use hash_fetch::urlhint::ContractClient;
/// Indicates sync status /// Indicates sync status
pub trait SyncStatus: Send + Sync { pub trait SyncStatus: Send + Sync {
@ -107,296 +97,92 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) } fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) }
} }
/// Webapps HTTP+RPC server build. /// Dapps server as `jsonrpc-http-server` request middleware.
pub struct ServerBuilder<T: Fetch = FetchClient> { pub struct Middleware<F: Fetch + Clone> {
dapps_path: PathBuf, router: router::Router<apps::fetcher::ContentFetcher<F>>,
extra_dapps: Vec<PathBuf>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: Option<Vec<Host>>,
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
remote: Remote,
fetch: Option<T>,
} }
impl ServerBuilder { impl<F: Fetch + Clone> Middleware<F> {
/// Construct new dapps server /// Creates new Dapps server middleware.
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self { pub fn new(
ServerBuilder { remote: Remote,
dapps_path: dapps_path.as_ref().to_owned(),
extra_dapps: vec![],
registrar: registrar,
sync_status: Arc::new(|| false),
web_proxy_tokens: Arc::new(|_| false),
signer_address: None,
allowed_hosts: Some(vec![]),
extra_cors: None,
remote: remote,
fetch: None,
}
}
}
impl<T: Fetch> ServerBuilder<T> {
/// Set a fetch client to use.
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
extra_dapps: vec![],
registrar: self.registrar,
sync_status: self.sync_status,
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
extra_cors: self.extra_cors,
remote: self.remote,
fetch: Some(fetch),
}
}
/// Change default sync status.
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
self.sync_status = status;
self
}
/// Change default web proxy tokens validator.
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
self.web_proxy_tokens = tokens;
self
}
/// Change default signer port.
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
self.signer_address = signer_address;
self
}
/// Change allowed hosts.
/// `None` - All hosts are allowed
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation<Host>) -> Self {
self.allowed_hosts = allowed_hosts.into();
self
}
/// Extra cors headers.
/// `None` - no additional CORS URLs
pub fn extra_cors_headers(mut self, cors: DomainsValidation<AccessControlAllowOrigin>) -> Self {
self.extra_cors = cors.into();
self
}
/// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, handler: MetaIoHandler<Metadata, S>, tokio_remote: TokioRemote) -> Result<Server, ServerError> {
let fetch = self.fetch_client()?;
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
NoAuth,
handler,
self.dapps_path,
self.extra_dapps,
self.signer_address,
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
tokio_remote,
fetch,
)
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, username: &str, password: &str, handler: MetaIoHandler<Metadata, S>, tokio_remote: TokioRemote) -> Result<Server, ServerError> {
let fetch = self.fetch_client()?;
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
HttpBasicAuth::single_user(username, password),
handler,
self.dapps_path,
self.extra_dapps,
self.signer_address,
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
tokio_remote,
fetch,
)
}
fn fetch_client(&self) -> Result<T, ServerError> {
match self.fetch.clone() {
Some(fetch) => Ok(fetch),
None => T::new().map_err(|_| ServerError::FetchInitialization),
}
}
}
/// Webapps HTTP server.
pub struct Server {
server: Option<hyper::server::Listening>,
}
impl Server {
/// Returns a list of allowed hosts or `None` if all hosts are allowed.
fn allowed_hosts(hosts: Option<Vec<Host>>, bind_address: String) -> Option<Vec<Host>> {
let mut allowed = Vec::new();
match hosts {
Some(hosts) => allowed.extend_from_slice(&hosts),
None => return None,
}
// Add localhost domain as valid too if listening on loopback interface.
allowed.push(bind_address.replace("127.0.0.1", "localhost").into());
allowed.push(bind_address.into());
Some(allowed)
}
/// Returns a list of CORS domains for API endpoint.
fn cors_domains(
signer_address: Option<(String, u16)>, signer_address: Option<(String, u16)>,
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
) -> Vec<AccessControlAllowOrigin> {
let basic_cors = match signer_address {
Some(signer_address) => [
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("http://{}", address(&signer_address)),
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)),
].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(),
None => vec![],
};
match extra_cors {
None => basic_cors,
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
}
}
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
addr: &SocketAddr,
hosts: Option<Vec<Host>>,
extra_cors: Option<Vec<AccessControlAllowOrigin>>,
authorization: A,
handler: MetaIoHandler<Metadata, T>,
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
tokio_remote: TokioRemote,
fetch: F, fetch: F,
) -> Result<Server, ServerError> { ) -> Self {
let authorization = Arc::new(authorization); let content_fetcher = 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,
signer_address.clone(), signer_address.clone(),
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
)); );
let endpoints = Arc::new(apps::all_endpoints( let endpoints = apps::all_endpoints(
dapps_path, dapps_path,
extra_dapps, extra_dapps,
signer_address.clone(), signer_address.clone(),
web_proxy_tokens, web_proxy_tokens,
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
)); );
let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
let special = Arc::new({ let cors_domains = cors_domains(signer_address.clone());
let special = {
let mut special = HashMap::new(); let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, tokio_remote, cors_domains.clone())); special.insert(router::SpecialEndpoint::Rpc, None);
special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
special.insert( special.insert(
router::SpecialEndpoint::Api, router::SpecialEndpoint::Api,
api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone()) Some(api::RestApi::new(cors_domains.clone(), &endpoints, content_fetcher.clone())),
); );
special special
}); };
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
hyper::Server::http(addr)? let router = router::Router::new(
.handle(move |ctrl| router::Router::new( signer_address,
ctrl, content_fetcher,
signer_address.clone(), endpoints,
content_fetcher.clone(), special,
endpoints.clone(), );
special.clone(),
authorization.clone(),
hosts.clone(),
))
.map(|(l, srv)| {
::std::thread::spawn(move || { Middleware {
srv.run(); router: router,
});
Server {
server: Some(l),
}
})
.map_err(ServerError::from)
}
#[cfg(test)]
/// Returns address that this server is bound to.
pub fn addr(&self) -> &SocketAddr {
self.server.as_ref()
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
.addrs()
.first()
.expect("You cannot start the server without binding to at least one address; qed")
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}
/// Webapp Server startup error
#[derive(Debug)]
pub enum ServerError {
/// Wrapped `std::io::Error`
IoError(std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
/// Fetch service initialization error
FetchInitialization,
}
impl From<hyper::error::Error> for ServerError {
fn from(err: hyper::error::Error) -> Self {
match err {
hyper::error::Error::Io(e) => ServerError::IoError(e),
e => ServerError::Other(e),
} }
} }
} }
impl<F: Fetch + Clone> http::RequestMiddleware for Middleware<F> {
fn on_request(&self, req: &hyper::server::Request<hyper::net::HttpStream>, control: &hyper::Control) -> http::RequestMiddlewareAction {
self.router.on_request(req, control)
}
}
/// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<AccessControlAllowOrigin> {
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
match signer_address {
Some(signer_address) => [
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("http://{}", address(&signer_address)),
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)),
].into_iter().map(|val| AccessControlAllowOrigin::Value(val.into())).collect(),
None => vec![],
}
}
fn address(address: &(String, u16)) -> String {
format!("{}:{}", address.0, address.1)
}
/// Random filename /// Random filename
fn random_filename() -> String { fn random_filename() -> String {
use ::rand::Rng; use ::rand::Rng;
@ -404,39 +190,18 @@ fn random_filename() -> String {
rng.gen_ascii_chars().take(12).collect() rng.gen_ascii_chars().take(12).collect()
} }
fn address(address: &(String, u16)) -> String {
format!("{}:{}", address.0, address.1)
}
#[cfg(test)] #[cfg(test)]
mod util_tests { mod util_tests {
use super::Server; use super::cors_domains;
use jsonrpc_http_server::AccessControlAllowOrigin; use jsonrpc_http_server::AccessControlAllowOrigin;
#[test]
fn should_return_allowed_hosts() {
// given
let bind_address = "127.0.0.1".to_owned();
// when
let all = Server::allowed_hosts(None, bind_address.clone());
let address = Server::allowed_hosts(Some(Vec::new()), bind_address.clone());
let some = Server::allowed_hosts(Some(vec!["ethcore.io".into()]), bind_address.clone());
// then
assert_eq!(all, None);
assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()]));
assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()]));
}
#[test] #[test]
fn should_return_cors_domains() { fn should_return_cors_domains() {
// given // given
// when // when
let none = Server::cors_domains(None, None); let none = cors_domains(None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None); let some = cors_domains(Some(("127.0.0.1".into(), 18180)));
let extra = Server::cors_domains(None, Some(vec!["all".into()]));
// then // then
assert_eq!(none, Vec::<AccessControlAllowOrigin>::new()); assert_eq!(none, Vec::<AccessControlAllowOrigin>::new());
@ -448,6 +213,5 @@ mod util_tests {
"https://parity.web3.site:18180".into(), "https://parity.web3.site:18180".into(),
"https://127.0.0.1:18180".into(), "https://127.0.0.1:18180".into(),
]); ]);
assert_eq!(extra, vec![AccessControlAllowOrigin::Any]);
} }
} }

View File

@ -15,24 +15,20 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Router implementation //! Router implementation
//! Processes request handling authorization and dispatching it to proper application. //! Dispatch requests to proper application.
pub mod auth;
mod host_validation;
use address; use address;
use std::cmp; use std::cmp;
use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use url::{Url, Host}; use url::{Url, Host};
use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode}; use hyper::{self, server, header, Control, StatusCode};
use hyper::net::HttpStream; use hyper::net::HttpStream;
use jsonrpc_server_utils::hosts; use jsonrpc_http_server as http;
use apps::{self, DAPPS_DOMAIN}; use apps::{self, DAPPS_DOMAIN};
use apps::fetcher::Fetcher; use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath}; use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
use handlers::{self, Redirection, ContentHandler}; use handlers::{self, Redirection, ContentHandler};
/// Special endpoints are accessible on every domain (every dapp) /// Special endpoints are accessible on every domain (every dapp)
@ -44,51 +40,29 @@ pub enum SpecialEndpoint {
None, None,
} }
pub struct Router<A: auth::Authorization + 'static> { pub struct Router<F> {
control: Option<Control>,
signer_address: Option<(String, u16)>, signer_address: Option<(String, u16)>,
endpoints: Arc<Endpoints>, endpoints: Endpoints,
fetch: Arc<Fetcher>, fetch: F,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
authorization: Arc<A>,
allowed_hosts: Option<Vec<hosts::Host>>,
handler: Box<server::Handler<HttpStream> + Send>,
} }
impl<A: auth::Authorization + 'static> server::Handler<HttpStream> for Router<A> { impl<F: Fetcher + 'static> http::RequestMiddleware for Router<F> {
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
// Choose proper handler depending on path / domain // Choose proper handler depending on path / domain
let url = handlers::extract_url(&req); let url = handlers::extract_url(req);
let endpoint = extract_endpoint(&url); let endpoint = extract_endpoint(&url);
let referer = extract_referer_endpoint(&req); let referer = extract_referer_endpoint(req);
let is_utils = endpoint.1 == SpecialEndpoint::Utils; let is_utils = endpoint.1 == SpecialEndpoint::Utils;
let is_dapps_domain = endpoint.0.as_ref().map(|endpoint| endpoint.using_dapps_domains).unwrap_or(false);
let is_origin_set = req.headers().get::<http::hyper::header::Origin>().is_some();
let is_get_request = *req.method() == hyper::Method::Get; let is_get_request = *req.method() == hyper::Method::Get;
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
// Validate Host header let control = control.clone();
trace!(target: "dapps", "Validating host headers against: {:?}", self.allowed_hosts);
let is_valid = is_utils || host_validation::is_valid(&req, &self.allowed_hosts);
if !is_valid {
debug!(target: "dapps", "Rejecting invalid host header.");
self.handler = host_validation::host_invalid_response();
return self.handler.on_request(req);
}
trace!(target: "dapps", "Checking authorization.");
// Check authorization
let auth = self.authorization.is_authorized(&req);
if let auth::Authorized::No(handler) = auth {
debug!(target: "dapps", "Authorization denied.");
self.handler = handler;
return self.handler.on_request(req);
}
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint); debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
self.handler = match (endpoint.0, endpoint.1, referer) { let handler: Option<Box<Handler>> = match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from // Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url))) (ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
if referer.app_id == apps::WEB_PATH if referer.app_id == apps::WEB_PATH
@ -100,26 +74,27 @@ impl<A: auth::Authorization + 'static> server::Handler<HttpStream> for Router<A>
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/ let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
let base = referer_url.path[..len].join("/"); let base = referer_url.path[..len].join("/");
let requested = url.map(|u| u.path.join("/")).unwrap_or_default(); let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
Redirection::boxed(&format!("/{}/{}", base, requested)) Some(Redirection::boxed(&format!("/{}/{}", base, requested)))
}, },
// First check special endpoints // First check special endpoints
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => { (ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
trace!(target: "dapps", "Resolving to special endpoint."); trace!(target: "dapps", "Resolving to special endpoint.");
self.special.get(endpoint) self.special.get(endpoint)
.expect("special known to contain key; qed") .expect("special known to contain key; qed")
.to_async_handler(path.clone().unwrap_or_default(), control) .as_ref()
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
}, },
// Then delegate to dapp // Then delegate to dapp
(Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => { (Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => {
trace!(target: "dapps", "Resolving to local/builtin dapp."); trace!(target: "dapps", "Resolving to local/builtin dapp.");
self.endpoints.get(&path.app_id) Some(self.endpoints.get(&path.app_id)
.expect("endpoints known to contain key; qed") .expect("endpoints known to contain key; qed")
.to_async_handler(path.clone(), control) .to_async_handler(path.clone(), control))
}, },
// Try to resolve and fetch the dapp // Try to resolve and fetch the dapp
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => { (Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
trace!(target: "dapps", "Resolving to fetchable content."); trace!(target: "dapps", "Resolving to fetchable content.");
self.fetch.to_async_handler(path.clone(), control) Some(self.fetch.to_async_handler(path.clone(), control))
}, },
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached // NOTE [todr] /home is redirected to home page since some users may have the redirection cached
// (in the past we used 301 instead of 302) // (in the past we used 301 instead of 302)
@ -128,82 +103,61 @@ impl<A: auth::Authorization + 'static> server::Handler<HttpStream> for Router<A>
// 404 for non-existent content // 404 for non-existent content
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => { (Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
trace!(target: "dapps", "Resolving to 404."); trace!(target: "dapps", "Resolving to 404.");
Box::new(ContentHandler::error( Some(Box::new(ContentHandler::error(
StatusCode::NotFound, StatusCode::NotFound,
"404 Not Found", "404 Not Found",
"Requested content was not found.", "Requested content was not found.",
None, None,
self.signer_address.clone(), self.signer_address.clone(),
)) )))
}, },
// Redirect any other GET request to signer. // Redirect any other GET request to signer.
_ if is_get_request => { _ if is_get_request => {
if let Some(ref signer_address) = self.signer_address { if let Some(ref signer_address) = self.signer_address {
trace!(target: "dapps", "Redirecting to signer interface."); trace!(target: "dapps", "Redirecting to signer interface.");
Redirection::boxed(&format!("http://{}", address(signer_address))) Some(Redirection::boxed(&format!("http://{}", address(signer_address))))
} else { } else {
trace!(target: "dapps", "Signer disabled, returning 404."); trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(ContentHandler::error( Some(Box::new(ContentHandler::error(
StatusCode::NotFound, StatusCode::NotFound,
"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(), self.signer_address.clone(),
)) )))
} }
}, },
// RPC by default // RPC by default
_ => { _ => {
trace!(target: "dapps", "Resolving to RPC call."); trace!(target: "dapps", "Resolving to RPC call.");
self.special.get(&SpecialEndpoint::Rpc) None
.expect("RPC endpoint always stored; qed")
.to_async_handler(EndpointPath::default(), control)
} }
}; };
// Delegate on_request to proper handler match handler {
self.handler.on_request(req) Some(handler) => http::RequestMiddlewareAction::Respond {
} should_validate_hosts: !(is_utils || is_dapps_domain),
handler: handler,
/// This event occurs each time the `Request` is ready to be read from. },
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next { None => http::RequestMiddlewareAction::Proceed {
self.handler.on_request_readable(decoder) should_continue_on_invalid_cors: !is_origin_set,
} },
}
/// This event occurs after the first time this handled signals `Next::write()`.
fn on_response(&mut self, response: &mut server::Response) -> Next {
self.handler.on_response(response)
}
/// This event occurs each time the `Response` is ready to be written to.
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.on_response_writable(encoder)
} }
} }
impl<A: auth::Authorization> Router<A> { impl<F> Router<F> {
pub fn new( pub fn new(
control: Control,
signer_address: Option<(String, u16)>, signer_address: Option<(String, u16)>,
content_fetcher: Arc<Fetcher>, content_fetcher: F,
endpoints: Arc<Endpoints>, endpoints: Endpoints,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>, special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
authorization: Arc<A>, ) -> Self {
allowed_hosts: Option<Vec<hosts::Host>>,
) -> Self {
let handler = special.get(&SpecialEndpoint::Utils)
.expect("Utils endpoint always stored; qed")
.to_handler(EndpointPath::default());
Router { Router {
control: Some(control),
signer_address: signer_address, signer_address: signer_address,
endpoints: endpoints, endpoints: endpoints,
fetch: content_fetcher, fetch: content_fetcher,
special: special, special: special,
authorization: authorization,
allowed_hosts: allowed_hosts,
handler: handler,
} }
} }
} }

View File

@ -1,106 +0,0 @@
// 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/>.
//! HTTP Authorization implementations
use std::collections::HashMap;
use hyper::{server, net, header, status};
use endpoint::Handler;
use handlers::{AuthRequiredHandler, ContentHandler};
/// Authorization result
pub enum Authorized {
/// Authorization was successful.
Yes,
/// Unsuccessful authorization. Handler for further work is returned.
No(Box<Handler>),
}
/// Authorization interface
pub trait Authorization : Send + Sync {
/// Checks if authorization is valid.
fn is_authorized(&self, req: &server::Request<net::HttpStream>)-> Authorized;
}
/// HTTP Basic Authorization handler
pub struct HttpBasicAuth {
users: HashMap<String, String>,
}
/// No-authorization implementation (authorization disabled)
pub struct NoAuth;
impl Authorization for NoAuth {
fn is_authorized(&self, _req: &server::Request<net::HttpStream>)-> Authorized {
Authorized::Yes
}
}
impl Authorization for HttpBasicAuth {
fn is_authorized(&self, req: &server::Request<net::HttpStream>) -> Authorized {
let auth = self.check_auth(&req);
match auth {
Access::Denied => {
Authorized::No(Box::new(ContentHandler::error(
status::StatusCode::Unauthorized,
"Unauthorized",
"You need to provide valid credentials to access this page.",
None,
None,
)))
},
Access::AuthRequired => {
Authorized::No(Box::new(AuthRequiredHandler))
},
Access::Granted => {
Authorized::Yes
},
}
}
}
#[derive(Debug)]
enum Access {
Granted,
Denied,
AuthRequired,
}
impl HttpBasicAuth {
/// Creates `HttpBasicAuth` instance with only one user.
pub fn single_user(username: &str, password: &str) -> Self {
let mut users = HashMap::new();
users.insert(username.to_owned(), password.to_owned());
HttpBasicAuth {
users: users
}
}
fn is_authorized(&self, username: &str, password: &str) -> bool {
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
}
fn check_auth(&self, req: &server::Request<net::HttpStream>) -> Access {
match req.headers().get::<header::Authorization<header::Basic>>() {
Some(&header::Authorization(
header::Basic { ref username, password: Some(ref password) }
)) if self.is_authorized(username, password) => Access::Granted,
Some(_) => Access::Denied,
None => Access::AuthRequired,
}
}
}

View File

@ -1,42 +0,0 @@
// 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/>.
use apps::DAPPS_DOMAIN;
use hyper::{server, header, StatusCode};
use hyper::net::HttpStream;
use handlers::ContentHandler;
use jsonrpc_http_server;
use jsonrpc_server_utils::hosts;
pub fn is_valid(req: &server::Request<HttpStream>, allowed_hosts: &Option<Vec<hosts::Host>>) -> bool {
let header_valid = jsonrpc_http_server::is_host_allowed(req, allowed_hosts);
match (header_valid, req.headers().get::<header::Host>()) {
(true, _) => true,
(_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN),
_ => false,
}
}
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
Box::new(ContentHandler::error(StatusCode::Forbidden,
"Current Host Is Disallowed",
"You are trying to access your node using incorrect address.",
Some("Use allowed URL or specify different <code>hosts</code> CLI options."),
None,
))
}

View File

@ -66,14 +66,14 @@ impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
#[derive(Default)] #[derive(Default)]
struct NoopMiddleware; struct NoopMiddleware;
impl http::RequestMiddleware for NoopMiddleware { impl http::RequestMiddleware for NoopMiddleware {
fn on_request(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>) -> http::RequestMiddlewareAction { fn on_request(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>, _control: &http::hyper::Control) -> http::RequestMiddlewareAction {
http::RequestMiddlewareAction::Proceed { http::RequestMiddlewareAction::Proceed {
should_continue_on_invalid_cors: request.headers().get::<http::hyper::header::Origin>().is_none(), should_continue_on_invalid_cors: request.headers().get::<http::hyper::header::Origin>().is_none(),
} }
} }
} }
struct MetadataExtractor; pub struct MetadataExtractor;
impl HttpMetaExtractor<Metadata> for MetadataExtractor { impl HttpMetaExtractor<Metadata> for MetadataExtractor {
fn read_metadata(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>) -> Metadata { fn read_metadata(&self, request: &http::hyper::server::Request<http::hyper::net::HttpStream>) -> Metadata {
let dapp_id = request.headers().get::<http::hyper::header::Origin>() let dapp_id = request.headers().get::<http::hyper::header::Origin>()

View File

@ -14,7 +14,7 @@
// 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/>.
use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers}; use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
#[test] #[test]
fn should_return_error() { fn should_return_error() {
@ -195,26 +195,3 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
response.assert_status("HTTP/1.1 200 OK"); response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site:18180"); response.assert_header("Access-Control-Allow-Origin", "http://parity.web3.site:18180");
} }
#[test]
fn should_return_extra_cors_headers() {
// given
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://somedomain.io\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
}

View File

@ -1,80 +0,0 @@
// 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/>.
use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed};
#[test]
fn should_require_authorization() {
// given
let server = serve_with_auth("test", "test");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "WWW-Authenticate: Basic realm=\"Parity\"");
}
#[test]
fn should_reject_on_invalid_auth() {
// given
let server = serve_with_auth("test", "test");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
assert!(response.body.contains("Unauthorized"), response.body);
assert_eq!(response.headers_raw.contains("WWW-Authenticate"), false);
}
#[test]
fn should_allow_on_valid_auth() {
// given
let server = serve_with_auth("Aladdin", "OpenSesame");
// when
let response = request(server,
"\
GET /ui/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_security_headers_for_embed(&response.headers);
}

View File

@ -16,18 +16,20 @@
use std::env; use std::env;
use std::str; use std::str;
use std::ops::Deref; use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use env_logger::LogBuilder; use env_logger::LogBuilder;
use ethcore_rpc::Metadata; use jsonrpc_core::IoHandler;
use jsonrpc_core::MetaIoHandler; use jsonrpc_http_server::{self as http, Host, DomainsValidation};
use ServerBuilder;
use Server;
use fetch::Fetch;
use devtools::http_client; use devtools::http_client;
use hash_fetch::urlhint::ContractClient;
use fetch::{Fetch, Client as FetchClient};
use parity_reactor::{EventLoop, Remote}; use parity_reactor::{EventLoop, Remote};
use {Middleware, SyncStatus, WebProxyTokens};
mod registrar; mod registrar;
mod fetch; mod fetch;
@ -50,7 +52,7 @@ pub struct ServerLoop {
pub event_loop: EventLoop, pub event_loop: EventLoop,
} }
impl Deref for ServerLoop { impl ::std::ops::Deref for ServerLoop {
type Target = Server; type Target = Server;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -58,7 +60,7 @@ impl Deref for ServerLoop {
} }
} }
pub fn init_server<F, B>(process: F, io: MetaIoHandler<Metadata>, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
F: FnOnce(ServerBuilder) -> ServerBuilder<B>, F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
B: Fetch, B: Fetch,
{ {
@ -74,33 +76,15 @@ pub fn init_server<F, B>(process: F, io: MetaIoHandler<Metadata>, remote: Remote
&dapps_path, registrar.clone(), remote, &dapps_path, registrar.clone(), remote,
)) ))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io, event_loop.raw_remote()).unwrap(); .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
( (
ServerLoop { server: server, event_loop: event_loop }, ServerLoop { server: server, event_loop: event_loop },
registrar, registrar,
) )
} }
pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop { pub fn serve_with_rpc(io: IoHandler) -> ServerLoop {
init_logger(); init_server(|builder| builder, io, Remote::new_sync()).0
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let event_loop = EventLoop::spawn();
let io = MetaIoHandler::default();
let server = ServerBuilder::new(&dapps_path, registrar, event_loop.remote())
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.allowed_hosts(None.into())
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), user, pass, io, event_loop.raw_remote()).unwrap();
ServerLoop {
server: server,
event_loop: event_loop,
}
}
pub fn serve_with_rpc(io: MetaIoHandler<Metadata>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None.into()), io, Remote::new_sync()).0
} }
pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop { pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
@ -108,20 +92,13 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0 init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0
} }
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
let extra_cors = extra_cors.map(|cors| cors.into_iter().map(Into::into).collect());
init_server(|builder| builder.allowed_hosts(None.into()).extra_cors_headers(extra_cors.into()), Default::default(), Remote::new_sync()).0
}
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) { pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| builder.allowed_hosts(None.into()), Default::default(), Remote::new_sync()) init_server(|builder| builder, Default::default(), Remote::new_sync())
} }
pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) { pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| { init_server(|builder| {
builder builder.sync_status(Arc::new(|| true))
.sync_status(Arc::new(|| true))
.allowed_hosts(None.into())
}, Default::default(), Remote::new_sync()) }, Default::default(), Remote::new_sync())
} }
@ -133,7 +110,7 @@ pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Serv
let fetch = FakeFetch::default(); let fetch = FakeFetch::default();
let f = fetch.clone(); let f = fetch.clone();
let (server, reg) = init_server(move |builder| { let (server, reg) = init_server(move |builder| {
builder.allowed_hosts(None.into()).fetch(f.clone()) builder.fetch(f.clone())
}, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() }); }, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
(server, fetch, reg) (server, fetch, reg)
@ -144,7 +121,6 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
let f = fetch.clone(); let f = fetch.clone();
let (server, _) = init_server(move |builder| { let (server, _) = init_server(move |builder| {
builder builder
.allowed_hosts(None.into())
.fetch(f.clone()) .fetch(f.clone())
.web_proxy_tokens(Arc::new(move |token| &token == web_token)) .web_proxy_tokens(Arc::new(move |token| &token == web_token))
}, Default::default(), Remote::new_sync()); }, Default::default(), Remote::new_sync());
@ -153,7 +129,7 @@ pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
} }
pub fn serve() -> ServerLoop { pub fn serve() -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None.into()), Default::default(), Remote::new_sync()).0 init_server(|builder| builder, Default::default(), Remote::new_sync()).0
} }
pub fn request(server: ServerLoop, request: &str) -> http_client::Response { pub fn request(server: ServerLoop, request: &str) -> http_client::Response {
@ -166,3 +142,157 @@ pub fn assert_security_headers(headers: &[String]) {
pub fn assert_security_headers_for_embed(headers: &[String]) { pub fn assert_security_headers_for_embed(headers: &[String]) {
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT)) http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
} }
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: PathBuf,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: DomainsValidation<Host>,
remote: Remote,
fetch: Option<T>,
}
impl ServerBuilder {
/// Construct new dapps server
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
ServerBuilder {
dapps_path: dapps_path.as_ref().to_owned(),
registrar: registrar,
sync_status: Arc::new(|| false),
web_proxy_tokens: Arc::new(|_| false),
signer_address: None,
allowed_hosts: DomainsValidation::Disabled,
remote: remote,
fetch: None,
}
}
}
impl<T: Fetch> ServerBuilder<T> {
/// Set a fetch client to use.
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
registrar: self.registrar,
sync_status: self.sync_status,
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
remote: self.remote,
fetch: Some(fetch),
}
}
/// Change default sync status.
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
self.sync_status = status;
self
}
/// Change default web proxy tokens validator.
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
self.web_proxy_tokens = tokens;
self
}
/// Change default signer port.
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
self.signer_address = signer_address;
self
}
/// Change allowed hosts.
/// `None` - All hosts are allowed
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation<Host>) -> Self {
self.allowed_hosts = allowed_hosts;
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(self, addr: &SocketAddr, io: IoHandler) -> Result<Server, http::Error> {
let fetch = self.fetch_client();
Server::start_http(
addr,
io,
self.allowed_hosts,
self.signer_address,
self.dapps_path,
vec![],
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
fetch,
)
}
fn fetch_client(&self) -> T {
match self.fetch.clone() {
Some(fetch) => fetch,
None => T::new().unwrap(),
}
}
}
/// Webapps HTTP server.
pub struct Server {
server: Option<http::Server>,
}
impl Server {
fn start_http<F: Fetch>(
addr: &SocketAddr,
io: IoHandler,
allowed_hosts: DomainsValidation<Host>,
signer_address: Option<(String, u16)>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Result<Server, http::Error> {
let middleware = Middleware::new(
remote,
signer_address,
dapps_path,
extra_dapps,
registrar,
sync_status,
web_proxy_tokens,
fetch,
);
http::ServerBuilder::new(io)
.request_middleware(middleware)
.allowed_hosts(allowed_hosts)
.cors(http::DomainsValidation::Disabled)
.start_http(addr)
.map(|server| Server {
server: Some(server),
})
}
/// Returns address that this server is bound to.
pub fn addr(&self) -> &SocketAddr {
self.server.as_ref()
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
.addrs()
.first()
.expect("You cannot start the server without binding to at least one address; qed")
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}

View File

@ -19,7 +19,6 @@
mod helpers; mod helpers;
mod api; mod api;
mod authorization;
mod fetch; mod fetch;
mod redirection; mod redirection;
mod rpc; mod rpc;

View File

@ -14,16 +14,14 @@
// 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/>.
use futures::{future, Future}; use jsonrpc_core::{IoHandler, Value};
use ethcore_rpc::{Metadata, Origin};
use jsonrpc_core::{MetaIoHandler, Value};
use tests::helpers::{serve_with_rpc, request}; use tests::helpers::{serve_with_rpc, request};
#[test] #[test]
fn should_serve_rpc() { fn should_serve_rpc() {
// given // given
let mut io = MetaIoHandler::default(); let mut io = IoHandler::default();
io.add_method("rpc_test", |_| { io.add_method("rpc_test", |_| {
Ok(Value::String("Hello World!".into())) Ok(Value::String("Hello World!".into()))
}); });
@ -49,70 +47,3 @@ fn should_serve_rpc() {
response.assert_status("HTTP/1.1 200 OK"); response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned()); assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
} }
#[test]
fn should_extract_metadata() {
// given
let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
assert_eq!(meta.origin, Origin::Dapps("".into()));
assert_eq!(meta.dapp_id(), "".into());
future::ok(Value::String("Hello World!".into())).boxed()
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
X-Parity-Origin: https://this.should.be.ignored\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}
#[test]
fn should_extract_metadata_from_custom_header() {
// given
let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into()));
assert_eq!(meta.dapp_id(), "https://parity.io/".into());
future::ok(Value::String("Hello World!".into())).boxed()
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Origin: null\r\n\
X-Parity-Origin: https://parity.io/\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}

View File

@ -34,7 +34,7 @@ fn should_reject_invalid_host() {
// then // then
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned()); assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
assert!(response.body.contains("Current Host Is Disallowed"), response.body); assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
} }
#[test] #[test]
@ -97,31 +97,3 @@ fn should_allow_parity_utils_even_on_invalid_domain() {
// then // then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
} }
#[test]
fn should_not_return_cors_headers_for_rpc() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
POST /rpc HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: null\r\n\
Content-Type: application/json\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert!(
!response.headers_raw.contains("Access-Control-Allow-Origin"),
"CORS headers were not expected: {:?}",
response.headers
);
}

View File

@ -8,7 +8,10 @@ RUN apt-get update && \
curl \ curl \
git \ git \
file \ file \
binutils binutils \
libssl-dev \
pkg-config \
libudev-dev
# install rustup # install rustup
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y RUN curl https://sh.rustup.rs -sSf | sh -s -- -y

View File

@ -17,6 +17,7 @@ ethcore-util = { path = "../../util" }
ethcore-network = { path = "../../util/network" } ethcore-network = { path = "../../util/network" }
ethcore-io = { path = "../../util/io" } ethcore-io = { path = "../../util/io" }
ethcore-ipc = { path = "../../ipc/rpc", optional = true } ethcore-ipc = { path = "../../ipc/rpc", optional = true }
ethcore-devtools = { path = "../../devtools" }
rlp = { path = "../../util/rlp" } rlp = { path = "../../util/rlp" }
time = "0.1" time = "0.1"
smallvec = "0.3.1" smallvec = "0.3.1"

View File

@ -23,9 +23,9 @@
//! This is separate from the `BlockChain` for two reasons: //! This is separate from the `BlockChain` for two reasons:
//! - It stores only headers (and a pruned subset of them) //! - It stores only headers (and a pruned subset of them)
//! - To allow for flexibility in the database layout once that's incorporated. //! - To allow for flexibility in the database layout once that's incorporated.
// TODO: use DB instead of memory. DB Layout: just the contents of `candidates`/`headers`
use std::collections::{BTreeMap, HashMap}; use std::collections::BTreeMap;
use std::sync::Arc;
use cht; use cht;
@ -34,7 +34,10 @@ use ethcore::error::BlockError;
use ethcore::encoded; use ethcore::encoded;
use ethcore::header::Header; use ethcore::header::Header;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use util::{H256, U256, HeapSizeOf, Mutex, RwLock};
use rlp::{Encodable, Decodable, DecoderError, RlpStream, Rlp, UntrustedRlp};
use util::{H256, U256, HeapSizeOf, RwLock};
use util::kvdb::{DBTransaction, KeyValueDB};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -43,6 +46,9 @@ use smallvec::SmallVec;
/// relevant to any blocks we've got in memory. /// relevant to any blocks we've got in memory.
const HISTORY: u64 = 2048; const HISTORY: u64 = 2048;
/// The best block key. Maps to an RLP list: [best_era, last_era]
const CURRENT_KEY: &'static [u8] = &*b"best_and_latest";
/// Information about a block. /// Information about a block.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BlockDescriptor { pub struct BlockDescriptor {
@ -75,42 +81,142 @@ impl HeapSizeOf for Entry {
} }
} }
impl Encodable for Entry {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(self.candidates.len());
for candidate in &self.candidates {
s.begin_list(3)
.append(&candidate.hash)
.append(&candidate.parent_hash)
.append(&candidate.total_difficulty);
}
}
}
impl Decodable for Entry {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
let mut candidates = SmallVec::<[Candidate; 3]>::new();
for item in rlp.iter() {
candidates.push(Candidate {
hash: item.val_at(0)?,
parent_hash: item.val_at(1)?,
total_difficulty: item.val_at(2)?,
})
}
if candidates.is_empty() { return Err(DecoderError::Custom("Empty candidates vector submitted.")) }
// rely on the invariant that the canonical entry is always first.
let canon_hash = candidates[0].hash;
Ok(Entry {
candidates: candidates,
canonical_hash: canon_hash,
})
}
}
fn cht_key(number: u64) -> String {
format!("{:08x}_canonical", number)
}
fn era_key(number: u64) -> String {
format!("candidates_{}", number)
}
/// Pending changes from `insert` to be applied after the database write has finished.
pub struct PendingChanges {
best_block: Option<BlockDescriptor>, // new best block.
}
/// Header chain. See module docs for more details. /// Header chain. See module docs for more details.
pub struct HeaderChain { pub struct HeaderChain {
genesis_header: encoded::Header, // special-case the genesis. genesis_header: encoded::Header, // special-case the genesis.
candidates: RwLock<BTreeMap<u64, Entry>>, candidates: RwLock<BTreeMap<u64, Entry>>,
headers: RwLock<HashMap<H256, encoded::Header>>,
best_block: RwLock<BlockDescriptor>, best_block: RwLock<BlockDescriptor>,
cht_roots: Mutex<Vec<H256>>, db: Arc<KeyValueDB>,
col: Option<u32>,
} }
impl HeaderChain { impl HeaderChain {
/// Create a new header chain given this genesis block. /// Create a new header chain given this genesis block and database to read from.
pub fn new(genesis: &[u8]) -> Self { pub fn new(db: Arc<KeyValueDB>, col: Option<u32>, genesis: &[u8]) -> Result<Self, String> {
use ethcore::views::HeaderView; use ethcore::views::HeaderView;
let g_view = HeaderView::new(genesis); let chain = if let Some(current) = db.get(col, CURRENT_KEY)? {
let (best_number, highest_number) = {
let rlp = Rlp::new(&current);
(rlp.val_at(0), rlp.val_at(1))
};
HeaderChain { let mut cur_number = highest_number;
genesis_header: encoded::Header::new(genesis.to_owned()), let mut candidates = BTreeMap::new();
best_block: RwLock::new(BlockDescriptor {
hash: g_view.hash(), // load all era entries and referenced headers within them.
number: 0, while let Some(entry) = db.get(col, era_key(cur_number).as_bytes())? {
total_difficulty: g_view.difficulty(), let entry: Entry = ::rlp::decode(&entry);
}), trace!(target: "chain", "loaded header chain entry for era {} with {} candidates",
candidates: RwLock::new(BTreeMap::new()), cur_number, entry.candidates.len());
headers: RwLock::new(HashMap::new()),
cht_roots: Mutex::new(Vec::new()), candidates.insert(cur_number, entry);
}
cur_number -= 1;
}
// fill best block block descriptor.
let best_block = {
let era = match candidates.get(&best_number) {
Some(era) => era,
None => return Err(format!("Database corrupt: highest block referenced but no data.")),
};
let best = &era.candidates[0];
BlockDescriptor {
hash: best.hash,
number: best_number,
total_difficulty: best.total_difficulty,
}
};
HeaderChain {
genesis_header: encoded::Header::new(genesis.to_owned()),
best_block: RwLock::new(best_block),
candidates: RwLock::new(candidates),
db: db,
col: col,
}
} else {
let g_view = HeaderView::new(genesis);
HeaderChain {
genesis_header: encoded::Header::new(genesis.to_owned()),
best_block: RwLock::new(BlockDescriptor {
hash: g_view.hash(),
number: 0,
total_difficulty: g_view.difficulty(),
}),
candidates: RwLock::new(BTreeMap::new()),
db: db,
col: col,
}
};
Ok(chain)
} }
/// Insert a pre-verified header. /// Insert a pre-verified header.
/// ///
/// This blindly trusts that the data given to it is sensible. /// This blindly trusts that the data given to it is sensible.
pub fn insert(&self, header: Header) -> Result<(), BlockError> { /// Returns a set of pending changes to be applied with `apply_pending`
/// before the next call to insert and after the transaction has been written.
pub fn insert(&self, transaction: &mut DBTransaction, header: Header) -> Result<PendingChanges, BlockError> {
let hash = header.hash(); let hash = header.hash();
let number = header.number(); let number = header.number();
let parent_hash = *header.parent_hash(); let parent_hash = *header.parent_hash();
let mut pending = PendingChanges {
best_block: None,
};
// hold candidates the whole time to guard import order. // hold candidates the whole time to guard import order.
let mut candidates = self.candidates.write(); let mut candidates = self.candidates.write();
@ -128,20 +234,41 @@ impl HeaderChain {
let total_difficulty = parent_td + *header.difficulty(); let total_difficulty = parent_td + *header.difficulty();
// insert headers and candidates entries. // insert headers and candidates entries and write era to disk.
candidates.entry(number).or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash }) {
.candidates.push(Candidate { let cur_era = candidates.entry(number)
.or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash });
cur_era.candidates.push(Candidate {
hash: hash, hash: hash,
parent_hash: parent_hash, parent_hash: parent_hash,
total_difficulty: total_difficulty, total_difficulty: total_difficulty,
}); });
let raw = ::rlp::encode(&header).to_vec(); // fix ordering of era before writing.
self.headers.write().insert(hash, encoded::Header::new(raw)); if total_difficulty > cur_era.candidates[0].total_difficulty {
let cur_pos = cur_era.candidates.len() - 1;
cur_era.candidates.swap(cur_pos, 0);
cur_era.canonical_hash = hash;
}
transaction.put(self.col, era_key(number).as_bytes(), &::rlp::encode(&*cur_era))
}
let raw = ::rlp::encode(&header);
transaction.put(self.col, &hash[..], &*raw);
let (best_num, is_new_best) = {
let cur_best = self.best_block.read();
if cur_best.total_difficulty < total_difficulty {
(number, true)
} else {
(cur_best.number, false)
}
};
// reorganize ancestors so canonical entries are first in their // reorganize ancestors so canonical entries are first in their
// respective candidates vectors. // respective candidates vectors.
if self.best_block.read().total_difficulty < total_difficulty { if is_new_best {
let mut canon_hash = hash; let mut canon_hash = hash;
for (&height, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) { for (&height, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) {
if height != number && entry.canonical_hash == canon_hash { break; } if height != number && entry.canonical_hash == canon_hash { break; }
@ -160,23 +287,26 @@ impl HeaderChain {
// what about reorgs > cht::SIZE + HISTORY? // what about reorgs > cht::SIZE + HISTORY?
// resetting to the last block of a given CHT should be possible. // resetting to the last block of a given CHT should be possible.
canon_hash = entry.candidates[0].parent_hash; canon_hash = entry.candidates[0].parent_hash;
// write altered era to disk
if height != number {
let rlp_era = ::rlp::encode(&*entry);
transaction.put(self.col, era_key(height).as_bytes(), &rlp_era);
}
} }
trace!(target: "chain", "New best block: ({}, {}), TD {}", number, hash, total_difficulty); trace!(target: "chain", "New best block: ({}, {}), TD {}", number, hash, total_difficulty);
*self.best_block.write() = BlockDescriptor { pending.best_block = Some(BlockDescriptor {
hash: hash, hash: hash,
number: number, number: number,
total_difficulty: total_difficulty, total_difficulty: total_difficulty,
}; });
// produce next CHT root if it's time. // produce next CHT root if it's time.
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed"); let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
if earliest_era + HISTORY + cht::SIZE <= number { if earliest_era + HISTORY + cht::SIZE <= number {
let cht_num = cht::block_to_cht_number(earliest_era) let cht_num = cht::block_to_cht_number(earliest_era)
.expect("fails only for number == 0; genesis never imported; qed"); .expect("fails only for number == 0; genesis never imported; qed");
debug_assert_eq!(cht_num as usize, self.cht_roots.lock().len());
let mut headers = self.headers.write();
let cht_root = { let cht_root = {
let mut i = earliest_era; let mut i = earliest_era;
@ -186,10 +316,12 @@ impl HeaderChain {
let iter = || { let iter = || {
let era_entry = candidates.remove(&i) let era_entry = candidates.remove(&i)
.expect("all eras are sequential with no gaps; qed"); .expect("all eras are sequential with no gaps; qed");
transaction.delete(self.col, era_key(i).as_bytes());
i += 1; i += 1;
for ancient in &era_entry.candidates { for ancient in &era_entry.candidates {
headers.remove(&ancient.hash); transaction.delete(self.col, &ancient.hash);
} }
let canon = &era_entry.candidates[0]; let canon = &era_entry.candidates[0];
@ -199,28 +331,56 @@ impl HeaderChain {
.expect("fails only when too few items; this is checked; qed") .expect("fails only when too few items; this is checked; qed")
}; };
// write the CHT root to the database.
debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root); debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root);
transaction.put(self.col, cht_key(cht_num).as_bytes(), &::rlp::encode(&cht_root));
self.cht_roots.lock().push(cht_root);
} }
} }
Ok(()) // write the best and latest eras to the database.
{
let latest_num = *candidates.iter().rev().next().expect("at least one era just inserted; qed").0;
let mut stream = RlpStream::new_list(2);
stream.append(&best_num).append(&latest_num);
transaction.put(self.col, CURRENT_KEY, &stream.out())
}
Ok(pending)
}
/// Apply pending changes from a previous `insert` operation.
/// Must be done before the next `insert` call.
pub fn apply_pending(&self, pending: PendingChanges) {
if let Some(best_block) = pending.best_block {
*self.best_block.write() = best_block;
}
} }
/// Get a block header. In the case of query by number, only canonical blocks /// Get a block header. In the case of query by number, only canonical blocks
/// will be returned. /// will be returned.
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> { pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
let load_from_db = |hash: H256| {
match self.db.get(self.col, &hash) {
Ok(val) => val.map(|x| x.to_vec()).map(encoded::Header::new),
Err(e) => {
warn!(target: "chain", "Failed to read from database: {}", e);
None
}
}
};
match id { match id {
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()), BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
BlockId::Hash(hash) => self.headers.read().get(&hash).cloned(), BlockId::Hash(hash) => load_from_db(hash),
BlockId::Number(num) => { BlockId::Number(num) => {
if self.best_block.read().number < num { return None } if self.best_block.read().number < num { return None }
self.candidates.read().get(&num).map(|entry| entry.canonical_hash) self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
.and_then(|hash| self.headers.read().get(&hash).cloned()) .and_then(load_from_db)
} }
BlockId::Latest | BlockId::Pending => { BlockId::Latest | BlockId::Pending => {
// hold candidates hear to prevent deletion of the header
// as we read it.
let _candidates = self.candidates.read();
let hash = { let hash = {
let best = self.best_block.read(); let best = self.best_block.read();
if best.number == 0 { if best.number == 0 {
@ -230,7 +390,7 @@ impl HeaderChain {
best.hash best.hash
}; };
self.headers.read().get(&hash).cloned() load_from_db(hash)
} }
} }
} }
@ -257,7 +417,13 @@ impl HeaderChain {
/// This is because it's assumed that the genesis hash is known, /// This is because it's assumed that the genesis hash is known,
/// so including it within a CHT would be redundant. /// so including it within a CHT would be redundant.
pub fn cht_root(&self, n: usize) -> Option<H256> { pub fn cht_root(&self, n: usize) -> Option<H256> {
self.cht_roots.lock().get(n).map(|h| h.clone()) match self.db.get(self.col, cht_key(n as u64).as_bytes()) {
Ok(val) => val.map(|x| ::rlp::decode(&x)),
Err(e) => {
warn!(target: "chain", "Error reading from database: {}", e);
None
}
}
} }
/// Get the genesis hash. /// Get the genesis hash.
@ -287,7 +453,7 @@ impl HeaderChain {
/// Get block status. /// Get block status.
pub fn status(&self, hash: &H256) -> BlockStatus { pub fn status(&self, hash: &H256) -> BlockStatus {
match self.headers.read().contains_key(hash) { match self.db.get(self.col, &*hash).ok().map_or(false, |x| x.is_some()) {
true => BlockStatus::InChain, true => BlockStatus::InChain,
false => BlockStatus::Unknown, false => BlockStatus::Unknown,
} }
@ -296,9 +462,7 @@ impl HeaderChain {
impl HeapSizeOf for HeaderChain { impl HeapSizeOf for HeaderChain {
fn heap_size_of_children(&self) -> usize { fn heap_size_of_children(&self) -> usize {
self.candidates.read().heap_size_of_children() + self.candidates.read().heap_size_of_children()
self.headers.read().heap_size_of_children() +
self.cht_roots.lock().heap_size_of_children()
} }
} }
@ -324,16 +488,23 @@ impl<'a> Iterator for AncestryIter<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::HeaderChain; use super::HeaderChain;
use std::sync::Arc;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::header::Header; use ethcore::header::Header;
use ethcore::spec::Spec; use ethcore::spec::Spec;
fn make_db() -> Arc<::util::KeyValueDB> {
Arc::new(::util::kvdb::in_memory(0))
}
#[test] #[test]
fn basic_chain() { fn basic_chain() {
let spec = Spec::new_test(); let spec = Spec::new_test();
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let db = make_db();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header)); let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
let mut parent_hash = genesis_header.hash(); let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp(); let mut rolling_timestamp = genesis_header.timestamp();
@ -345,7 +516,10 @@ mod tests {
header.set_difficulty(*genesis_header.difficulty() * i.into()); header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash(); parent_hash = header.hash();
chain.insert(header).unwrap(); let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10; rolling_timestamp += 10;
} }
@ -361,7 +535,8 @@ mod tests {
let spec = Spec::new_test(); let spec = Spec::new_test();
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header)); let db = make_db();
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
let mut parent_hash = genesis_header.hash(); let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp(); let mut rolling_timestamp = genesis_header.timestamp();
@ -373,7 +548,10 @@ mod tests {
header.set_difficulty(*genesis_header.difficulty() * i.into()); header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash(); parent_hash = header.hash();
chain.insert(header).unwrap(); let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10; rolling_timestamp += 10;
} }
@ -389,7 +567,10 @@ mod tests {
header.set_difficulty(*genesis_header.difficulty() * i.into()); header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash(); parent_hash = header.hash();
chain.insert(header).unwrap(); let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10; rolling_timestamp += 10;
} }
@ -410,7 +591,10 @@ mod tests {
header.set_difficulty(*genesis_header.difficulty() * (i * i).into()); header.set_difficulty(*genesis_header.difficulty() * (i * i).into());
parent_hash = header.hash(); parent_hash = header.hash();
chain.insert(header).unwrap(); let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 11; rolling_timestamp += 11;
} }
@ -432,11 +616,101 @@ mod tests {
fn earliest_is_latest() { fn earliest_is_latest() {
let spec = Spec::new_test(); let spec = Spec::new_test();
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let db = make_db();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header)); let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
assert!(chain.block_header(BlockId::Earliest).is_some()); assert!(chain.block_header(BlockId::Earliest).is_some());
assert!(chain.block_header(BlockId::Latest).is_some()); assert!(chain.block_header(BlockId::Latest).is_some());
assert!(chain.block_header(BlockId::Pending).is_some()); assert!(chain.block_header(BlockId::Pending).is_some());
} }
#[test]
fn restore_from_db() {
let spec = Spec::new_test();
let genesis_header = spec.genesis_header();
let db = make_db();
{
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp();
for i in 1..10000 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash();
let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10;
}
}
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
assert!(chain.block_header(BlockId::Number(10)).is_none());
assert!(chain.block_header(BlockId::Number(9000)).is_some());
assert!(chain.cht_root(2).is_some());
assert!(chain.cht_root(3).is_none());
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 9999);
}
#[test]
fn restore_higher_non_canonical() {
let spec = Spec::new_test();
let genesis_header = spec.genesis_header();
let db = make_db();
{
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp();
// push 100 low-difficulty blocks.
for i in 1..101 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash();
let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10;
}
// push fewer high-difficulty blocks.
for i in 1..11 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into() * 1000.into());
parent_hash = header.hash();
let mut tx = db.transaction();
let pending = chain.insert(&mut tx, header).unwrap();
db.write(tx).unwrap();
chain.apply_pending(pending);
rolling_timestamp += 10;
}
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10);
}
// after restoration, non-canonical eras should still be loaded.
let chain = HeaderChain::new(db.clone(), None, &::rlp::encode(&genesis_header)).unwrap();
assert_eq!(chain.block_header(BlockId::Latest).unwrap().number(), 10);
assert!(chain.candidates.read().get(&100).is_some())
}
} }

View File

@ -32,6 +32,7 @@ use ethcore::encoded;
use io::IoChannel; use io::IoChannel;
use util::{H256, Mutex, RwLock}; use util::{H256, Mutex, RwLock};
use util::kvdb::{KeyValueDB, CompactionProfile};
use self::header_chain::{AncestryIter, HeaderChain}; use self::header_chain::{AncestryIter, HeaderChain};
@ -45,6 +46,14 @@ mod service;
pub struct Config { pub struct Config {
/// Verification queue config. /// Verification queue config.
pub queue: queue::Config, pub queue: queue::Config,
/// Chain column in database.
pub chain_column: Option<u32>,
/// Database cache size. `None` => rocksdb default.
pub db_cache_size: Option<usize>,
/// State db compaction profile
pub db_compaction: CompactionProfile,
/// Should db have WAL enabled?
pub db_wal: bool,
} }
/// Trait for interacting with the header chain abstractly. /// Trait for interacting with the header chain abstractly.
@ -113,18 +122,30 @@ pub struct Client {
chain: HeaderChain, chain: HeaderChain,
report: RwLock<ClientReport>, report: RwLock<ClientReport>,
import_lock: Mutex<()>, import_lock: Mutex<()>,
db: Arc<KeyValueDB>,
} }
impl Client { impl Client {
/// Create a new `Client`. /// Create a new `Client`.
pub fn new(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self { pub fn new(config: Config, db: Arc<KeyValueDB>, chain_col: Option<u32>, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Result<Self, String> {
Client { let gh = ::rlp::encode(&spec.genesis_header());
Ok(Client {
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true), queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
engine: spec.engine.clone(), engine: spec.engine.clone(),
chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())), chain: HeaderChain::new(db.clone(), chain_col, &gh)?,
report: RwLock::new(ClientReport::default()), report: RwLock::new(ClientReport::default()),
import_lock: Mutex::new(()), import_lock: Mutex::new(()),
} db: db,
})
}
/// Create a new `Client` backed purely in-memory.
/// This will ignore all database options in the configuration.
pub fn in_memory(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self {
let db = ::util::kvdb::in_memory(0);
Client::new(config, Arc::new(db), None, spec, io_channel).expect("New DB creation infallible; qed")
} }
/// Import a header to the queue for additional verification. /// Import a header to the queue for additional verification.
@ -208,15 +229,23 @@ impl Client {
for verified_header in self.queue.drain(MAX) { for verified_header in self.queue.drain(MAX) {
let (num, hash) = (verified_header.number(), verified_header.hash()); let (num, hash) = (verified_header.number(), verified_header.hash());
match self.chain.insert(verified_header) { let mut tx = self.db.transaction();
Ok(()) => { let pending = match self.chain.insert(&mut tx, verified_header) {
Ok(pending) => {
good.push(hash); good.push(hash);
self.report.write().blocks_imported += 1; self.report.write().blocks_imported += 1;
pending
} }
Err(e) => { Err(e) => {
debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e); debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e);
bad.push(hash); bad.push(hash);
break;
} }
};
self.db.write_buffered(tx);
self.chain.apply_pending(pending);
if let Err(e) = self.db.flush() {
panic!("Database flush failed: {}. Check disk health and space.", e);
} }
} }

View File

@ -17,33 +17,80 @@
//! Minimal IO service for light client. //! Minimal IO service for light client.
//! Just handles block import messages and passes them to the client. //! Just handles block import messages and passes them to the client.
use std::fmt;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use ethcore::db;
use ethcore::service::ClientIoMessage; use ethcore::service::ClientIoMessage;
use ethcore::spec::Spec; use ethcore::spec::Spec;
use io::{IoContext, IoError, IoHandler, IoService}; use io::{IoContext, IoError, IoHandler, IoService};
use util::kvdb::{Database, DatabaseConfig};
use super::{Client, Config as ClientConfig}; use super::{Client, Config as ClientConfig};
/// Errors on service initialization.
#[derive(Debug)]
pub enum Error {
/// Database error.
Database(String),
/// I/O service error.
Io(IoError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Database(ref msg) => write!(f, "Database error: {}", msg),
Error::Io(ref err) => write!(f, "I/O service error: {}", err),
}
}
}
/// Light client service. /// Light client service.
pub struct Service { pub struct Service {
client: Arc<Client>, client: Arc<Client>,
_io_service: IoService<ClientIoMessage>, io_service: IoService<ClientIoMessage>,
} }
impl Service { impl Service {
/// Start the service: initialize I/O workers and client itself. /// Start the service: initialize I/O workers and client itself.
pub fn start(config: ClientConfig, spec: &Spec) -> Result<Self, IoError> { pub fn start(config: ClientConfig, spec: &Spec, path: &Path) -> Result<Self, Error> {
let io_service = try!(IoService::<ClientIoMessage>::start()); // initialize database.
let client = Arc::new(Client::new(config, spec, io_service.channel())); let mut db_config = DatabaseConfig::with_columns(db::NUM_COLUMNS);
try!(io_service.register_handler(Arc::new(ImportBlocks(client.clone()))));
// give all rocksdb cache to the header chain column.
if let Some(size) = config.db_cache_size {
db_config.set_cache(db::COL_LIGHT_CHAIN, size);
}
db_config.compaction = config.db_compaction;
db_config.wal = config.db_wal;
let db = Arc::new(Database::open(
&db_config,
&path.to_str().expect("DB path could not be converted to string.")
).map_err(Error::Database)?);
let io_service = IoService::<ClientIoMessage>::start().map_err(Error::Io)?;
let client = Arc::new(Client::new(config,
db,
db::COL_LIGHT_CHAIN,
spec,
io_service.channel(),
).map_err(Error::Database)?);
io_service.register_handler(Arc::new(ImportBlocks(client.clone()))).map_err(Error::Io)?;
Ok(Service { Ok(Service {
client: client, client: client,
_io_service: io_service, io_service: io_service,
}) })
} }
/// Register an I/O handler on the service.
pub fn register_handler(&self, handler: Arc<IoHandler<ClientIoMessage> + Send>) -> Result<(), IoError> {
self.io_service.register_handler(handler)
}
/// Get a handle to the client. /// Get a handle to the client.
pub fn client(&self) -> &Arc<Client> { pub fn client(&self) -> &Arc<Client> {
&self.client &self.client
@ -63,11 +110,13 @@ impl IoHandler<ClientIoMessage> for ImportBlocks {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Service; use super::Service;
use devtools::RandomTempPath;
use ethcore::spec::Spec; use ethcore::spec::Spec;
#[test] #[test]
fn it_works() { fn it_works() {
let spec = Spec::new_test(); let spec = Spec::new_test();
Service::start(Default::default(), &spec).unwrap(); let temp_path = RandomTempPath::new();
Service::start(Default::default(), &spec, temp_path.as_path()).unwrap();
} }
} }

View File

@ -55,6 +55,7 @@ pub mod remote {
mod types; mod types;
pub use self::cache::Cache;
pub use self::provider::Provider; pub use self::provider::Provider;
pub use self::transaction_queue::TransactionQueue; pub use self::transaction_queue::TransactionQueue;
pub use types::request as request; pub use types::request as request;
@ -76,3 +77,6 @@ extern crate stats;
#[cfg(feature = "ipc")] #[cfg(feature = "ipc")]
extern crate ethcore_ipc as ipc; extern crate ethcore_ipc as ipc;
#[cfg(test)]
extern crate ethcore_devtools as devtools;

View File

@ -61,10 +61,12 @@ impl<'a> IoContext for NetworkContext<'a> {
} }
fn disconnect_peer(&self, peer: PeerId) { fn disconnect_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disconnect of peer {}", peer);
NetworkContext::disconnect_peer(self, peer); NetworkContext::disconnect_peer(self, peer);
} }
fn disable_peer(&self, peer: PeerId) { fn disable_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disable of peer {}", peer);
NetworkContext::disable_peer(self, peer); NetworkContext::disable_peer(self, peer);
} }

View File

@ -27,7 +27,7 @@ use util::hash::H256;
use util::{DBValue, Mutex, RwLock, U256}; use util::{DBValue, Mutex, RwLock, U256};
use time::{Duration, SteadyTime}; use time::{Duration, SteadyTime};
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -61,6 +61,9 @@ const TIMEOUT_INTERVAL_MS: u64 = 1000;
const TICK_TIMEOUT: TimerToken = 1; const TICK_TIMEOUT: TimerToken = 1;
const TICK_TIMEOUT_INTERVAL_MS: u64 = 5000; const TICK_TIMEOUT_INTERVAL_MS: u64 = 5000;
const PROPAGATE_TIMEOUT: TimerToken = 2;
const PROPAGATE_TIMEOUT_INTERVAL_MS: u64 = 5000;
// minimum interval between updates. // minimum interval between updates.
const UPDATE_INTERVAL_MS: i64 = 5000; const UPDATE_INTERVAL_MS: i64 = 5000;
@ -132,6 +135,7 @@ pub struct Peer {
last_update: SteadyTime, last_update: SteadyTime,
pending_requests: RequestSet, pending_requests: RequestSet,
failed_requests: Vec<ReqId>, failed_requests: Vec<ReqId>,
propagated_transactions: HashSet<H256>,
} }
/// A light protocol event handler. /// A light protocol event handler.
@ -303,12 +307,18 @@ impl LightProtocol {
match peer.remote_flow { match peer.remote_flow {
None => Err(Error::NotServer), None => Err(Error::NotServer),
Some((ref mut creds, ref params)) => { Some((ref mut creds, ref params)) => {
// check that enough credits are available. // apply recharge to credits if there's no pending requests.
let mut temp_creds: Credits = creds.clone(); if peer.pending_requests.is_empty() {
for request in requests.requests() { params.recharge(creds);
temp_creds.deduct_cost(params.compute_cost(request))?;
} }
*creds = temp_creds;
// compute and deduct cost.
let pre_creds = creds.current();
let cost = params.compute_cost_multi(requests.requests());
creds.deduct_cost(cost)?;
trace!(target: "pip", "requesting from peer {}. Cost: {}; Available: {}",
peer_id, cost, pre_creds);
let req_id = ReqId(self.req_id.fetch_add(1, Ordering::SeqCst)); let req_id = ReqId(self.req_id.fetch_add(1, Ordering::SeqCst));
io.send(*peer_id, packet::REQUEST, { io.send(*peer_id, packet::REQUEST, {
@ -318,7 +328,7 @@ impl LightProtocol {
}); });
// begin timeout. // begin timeout.
peer.pending_requests.insert(req_id, requests, SteadyTime::now()); peer.pending_requests.insert(req_id, requests, cost, SteadyTime::now());
Ok(req_id) Ok(req_id)
} }
} }
@ -401,20 +411,25 @@ impl LightProtocol {
let req_id = ReqId(raw.val_at(0)?); let req_id = ReqId(raw.val_at(0)?);
let cur_credits: U256 = raw.val_at(1)?; let cur_credits: U256 = raw.val_at(1)?;
trace!(target: "pip", "pre-verifying response from peer {}", peer); trace!(target: "pip", "pre-verifying response for {} from peer {}", req_id, peer);
let peers = self.peers.read(); let peers = self.peers.read();
let res = match peers.get(peer) { let res = match peers.get(peer) {
Some(peer_info) => { Some(peer_info) => {
let mut peer_info = peer_info.lock(); let mut peer_info = peer_info.lock();
let req_info = peer_info.pending_requests.remove(&req_id, SteadyTime::now()); let req_info = peer_info.pending_requests.remove(&req_id, SteadyTime::now());
let cumulative_cost = peer_info.pending_requests.cumulative_cost();
let flow_info = peer_info.remote_flow.as_mut(); let flow_info = peer_info.remote_flow.as_mut();
match (req_info, flow_info) { match (req_info, flow_info) {
(Some(_), Some(flow_info)) => { (Some(_), Some(flow_info)) => {
let &mut (ref mut c, ref mut flow) = flow_info; let &mut (ref mut c, ref mut flow) = flow_info;
let actual_credits = ::std::cmp::min(cur_credits, *flow.limit());
c.update_to(actual_credits); // only update if the cumulative cost of the request set is zero.
if cumulative_cost == 0.into() {
let actual_credits = ::std::cmp::min(cur_credits, *flow.limit());
c.update_to(actual_credits);
}
Ok(()) Ok(())
} }
@ -488,6 +503,47 @@ impl LightProtocol {
} }
} }
// propagate transactions to relay peers.
// if we aren't on the mainnet, we just propagate to all relay peers
fn propagate_transactions(&self, io: &IoContext) {
if self.capabilities.read().tx_relay { return }
let ready_transactions = self.provider.ready_transactions();
if ready_transactions.is_empty() { return }
trace!(target: "pip", "propagate transactions: {} ready", ready_transactions.len());
let all_transaction_hashes: HashSet<_> = ready_transactions.iter().map(|tx| tx.hash()).collect();
let mut buf = Vec::new();
let peers = self.peers.read();
for (peer_id, peer_info) in peers.iter() {
let mut peer_info = peer_info.lock();
if !peer_info.capabilities.tx_relay { continue }
let prop_filter = &mut peer_info.propagated_transactions;
*prop_filter = &*prop_filter & &all_transaction_hashes;
// fill the buffer with all non-propagated transactions.
let to_propagate = ready_transactions.iter()
.filter(|tx| prop_filter.insert(tx.hash()))
.map(|tx| &tx.transaction);
buf.extend(to_propagate);
// propagate to the given peer.
if buf.is_empty() { continue }
io.send(*peer_id, packet::SEND_TRANSACTIONS, {
let mut stream = RlpStream::new_list(buf.len());
for pending_tx in buf.drain(..) {
stream.append(pending_tx);
}
stream.out()
})
}
}
/// called when a peer connects. /// called when a peer connects.
pub fn on_connect(&self, peer: &PeerId, io: &IoContext) { pub fn on_connect(&self, peer: &PeerId, io: &IoContext) {
let proto_version = match io.protocol_version(*peer).ok_or(Error::WrongNetwork) { let proto_version = match io.protocol_version(*peer).ok_or(Error::WrongNetwork) {
@ -520,6 +576,7 @@ impl LightProtocol {
last_update: SteadyTime::now(), last_update: SteadyTime::now(),
}); });
trace!(target: "pip", "Sending status to peer {}", peer);
io.send(*peer, packet::STATUS, status_packet); io.send(*peer, packet::STATUS, status_packet);
} }
@ -601,6 +658,7 @@ impl LightProtocol {
last_update: pending.last_update, last_update: pending.last_update,
pending_requests: RequestSet::default(), pending_requests: RequestSet::default(),
failed_requests: Vec::new(), failed_requests: Vec::new(),
propagated_transactions: HashSet::new(),
})); }));
for handler in &self.handlers { for handler in &self.handlers {
@ -683,6 +741,8 @@ impl LightProtocol {
trace!(target: "pip", "Received requests (id: {}) from peer {}", req_id, peer_id); trace!(target: "pip", "Received requests (id: {}) from peer {}", req_id, peer_id);
// deserialize requests, check costs and request validity. // deserialize requests, check costs and request validity.
self.flow_params.recharge(&mut peer.local_credits);
peer.local_credits.deduct_cost(self.flow_params.base_cost())?; peer.local_credits.deduct_cost(self.flow_params.base_cost())?;
for request_rlp in raw.at(1)?.iter().take(MAX_REQUESTS) { for request_rlp in raw.at(1)?.iter().take(MAX_REQUESTS) {
let request: Request = request_rlp.as_val()?; let request: Request = request_rlp.as_val()?;
@ -709,6 +769,7 @@ impl LightProtocol {
}); });
trace!(target: "pip", "Responded to {}/{} requests in packet {}", responses.len(), num_requests, req_id); trace!(target: "pip", "Responded to {}/{} requests in packet {}", responses.len(), num_requests, req_id);
trace!(target: "pip", "Peer {} has {} credits remaining.", peer_id, peer.local_credits.current());
io.respond(packet::RESPONSE, { io.respond(packet::RESPONSE, {
let mut stream = RlpStream::new_list(3); let mut stream = RlpStream::new_list(3);
@ -782,6 +843,8 @@ impl NetworkProtocolHandler for LightProtocol {
.expect("Error registering sync timer."); .expect("Error registering sync timer.");
io.register_timer(TICK_TIMEOUT, TICK_TIMEOUT_INTERVAL_MS) io.register_timer(TICK_TIMEOUT, TICK_TIMEOUT_INTERVAL_MS)
.expect("Error registering sync timer."); .expect("Error registering sync timer.");
io.register_timer(PROPAGATE_TIMEOUT, PROPAGATE_TIMEOUT_INTERVAL_MS)
.expect("Error registering sync timer.");
} }
fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
@ -800,6 +863,7 @@ impl NetworkProtocolHandler for LightProtocol {
match timer { match timer {
TIMEOUT => self.timeout_check(io), TIMEOUT => self.timeout_check(io),
TICK_TIMEOUT => self.tick_handlers(io), TICK_TIMEOUT => self.tick_handlers(io),
PROPAGATE_TIMEOUT => self.propagate_transactions(io),
_ => warn!(target: "pip", "received timeout on unknown token {}", timer), _ => warn!(target: "pip", "received timeout on unknown token {}", timer),
} }
} }

View File

@ -27,22 +27,29 @@ use std::iter::FromIterator;
use request::Request; use request::Request;
use request::Requests; use request::Requests;
use net::{timeout, ReqId}; use net::{timeout, ReqId};
use util::U256;
use time::{Duration, SteadyTime}; use time::{Duration, SteadyTime};
// Request set entry: requests + cost.
#[derive(Debug)]
struct Entry(Requests, U256);
/// Request set. /// Request set.
#[derive(Debug)] #[derive(Debug)]
pub struct RequestSet { pub struct RequestSet {
counter: u64, counter: u64,
cumulative_cost: U256,
base: Option<SteadyTime>, base: Option<SteadyTime>,
ids: HashMap<ReqId, u64>, ids: HashMap<ReqId, u64>,
reqs: BTreeMap<u64, Requests>, reqs: BTreeMap<u64, Entry>,
} }
impl Default for RequestSet { impl Default for RequestSet {
fn default() -> Self { fn default() -> Self {
RequestSet { RequestSet {
counter: 0, counter: 0,
cumulative_cost: 0.into(),
base: None, base: None,
ids: HashMap::new(), ids: HashMap::new(),
reqs: BTreeMap::new(), reqs: BTreeMap::new(),
@ -52,10 +59,12 @@ impl Default for RequestSet {
impl RequestSet { impl RequestSet {
/// Push requests onto the stack. /// Push requests onto the stack.
pub fn insert(&mut self, req_id: ReqId, req: Requests, now: SteadyTime) { pub fn insert(&mut self, req_id: ReqId, req: Requests, cost: U256, now: SteadyTime) {
let counter = self.counter; let counter = self.counter;
self.cumulative_cost = self.cumulative_cost + cost;
self.ids.insert(req_id, counter); self.ids.insert(req_id, counter);
self.reqs.insert(counter, req); self.reqs.insert(counter, Entry(req, cost));
if self.reqs.keys().next().map_or(true, |x| *x == counter) { if self.reqs.keys().next().map_or(true, |x| *x == counter) {
self.base = Some(now); self.base = Some(now);
@ -71,7 +80,7 @@ impl RequestSet {
None => return None, None => return None,
}; };
let req = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed"); let Entry(req, cost) = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed");
match self.reqs.keys().next() { match self.reqs.keys().next() {
Some(k) if *k > id => self.base = Some(now), Some(k) if *k > id => self.base = Some(now),
@ -79,6 +88,7 @@ impl RequestSet {
_ => {} _ => {}
} }
self.cumulative_cost = self.cumulative_cost - cost;
Some(req) Some(req)
} }
@ -93,7 +103,7 @@ impl RequestSet {
let first_req = self.reqs.values().next() let first_req = self.reqs.values().next()
.expect("base existing implies `reqs` non-empty; qed"); .expect("base existing implies `reqs` non-empty; qed");
base + compute_timeout(&first_req) <= now base + compute_timeout(&first_req.0) <= now
} }
/// Collect all pending request ids. /// Collect all pending request ids.
@ -108,6 +118,9 @@ impl RequestSet {
/// Whether the set is empty. /// Whether the set is empty.
pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn is_empty(&self) -> bool { self.len() == 0 }
/// The cumulative cost of all requests in the set.
pub fn cumulative_cost(&self) -> U256 { self.cumulative_cost }
} }
// helper to calculate timeout for a specific set of requests. // helper to calculate timeout for a specific set of requests.
@ -141,8 +154,8 @@ mod tests {
let the_req = RequestBuilder::default().build(); let the_req = RequestBuilder::default().build();
let req_time = compute_timeout(&the_req); let req_time = compute_timeout(&the_req);
req_set.insert(ReqId(0), the_req.clone(), test_begin); req_set.insert(ReqId(0), the_req.clone(), 0.into(), test_begin);
req_set.insert(ReqId(1), the_req, test_begin + Duration::seconds(1)); req_set.insert(ReqId(1), the_req, 0.into(), test_begin + Duration::seconds(1));
assert_eq!(req_set.base, Some(test_begin)); assert_eq!(req_set.base, Some(test_begin));
@ -153,4 +166,22 @@ mod tests {
assert!(!req_set.check_timeout(test_end)); assert!(!req_set.check_timeout(test_end));
assert!(req_set.check_timeout(test_end + Duration::seconds(1))); assert!(req_set.check_timeout(test_end + Duration::seconds(1)));
} }
#[test]
fn cumulative_cost() {
let the_req = RequestBuilder::default().build();
let test_begin = SteadyTime::now();
let test_end = test_begin + Duration::seconds(1);
let mut req_set = RequestSet::default();
for i in 0..5 {
req_set.insert(ReqId(i), the_req.clone(), 1.into(), test_begin);
assert_eq!(req_set.cumulative_cost, (i + 1).into());
}
for i in (0..5).rev() {
assert!(req_set.remove(&ReqId(i), test_end).is_some());
assert_eq!(req_set.cumulative_cost, i.into());
}
}
} }

View File

@ -600,8 +600,8 @@ fn id_guard() {
let mut pending_requests = RequestSet::default(); let mut pending_requests = RequestSet::default();
pending_requests.insert(req_id_1, req.clone(), ::time::SteadyTime::now()); pending_requests.insert(req_id_1, req.clone(), 0.into(), ::time::SteadyTime::now());
pending_requests.insert(req_id_2, req, ::time::SteadyTime::now()); pending_requests.insert(req_id_2, req, 1.into(), ::time::SteadyTime::now());
proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer { proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer {
local_credits: flow_params.create_credits(), local_credits: flow_params.create_credits(),
@ -612,6 +612,7 @@ fn id_guard() {
last_update: ::time::SteadyTime::now(), last_update: ::time::SteadyTime::now(),
pending_requests: pending_requests, pending_requests: pending_requests,
failed_requests: Vec::new(), failed_requests: Vec::new(),
propagated_transactions: Default::default(),
})); }));
// first, malformed responses. // first, malformed responses.

View File

@ -37,7 +37,7 @@ use rlp::RlpStream;
use util::{Bytes, RwLock, Mutex, U256, H256}; use util::{Bytes, RwLock, Mutex, U256, H256};
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP};
use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId}; use net::{self, Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId};
use cache::Cache; use cache::Cache;
use request::{self as basic_request, Request as NetworkRequest, Response as NetworkResponse}; use request::{self as basic_request, Request as NetworkRequest, Response as NetworkResponse};
@ -57,15 +57,15 @@ impl Peer {
self.capabilities.serve_headers && self.status.head_num > req.num(), self.capabilities.serve_headers && self.status.head_num > req.num(),
Pending::HeaderByHash(_, _) => self.capabilities.serve_headers, Pending::HeaderByHash(_, _) => self.capabilities.serve_headers,
Pending::Block(ref req, _) => Pending::Block(ref req, _) =>
self.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x >= req.header.number()), self.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x <= req.header.number()),
Pending::BlockReceipts(ref req, _) => Pending::BlockReceipts(ref req, _) =>
self.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x >= req.0.number()), self.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x <= req.0.number()),
Pending::Account(ref req, _) => Pending::Account(ref req, _) =>
self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= req.header.number()), self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x <= req.header.number()),
Pending::Code(ref req, _) => Pending::Code(ref req, _) =>
self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= req.block_id.1), self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x <= req.block_id.1),
Pending::TxProof(ref req, _) => Pending::TxProof(ref req, _) =>
self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= req.header.number()), self.capabilities.serve_state_since.as_ref().map_or(false, |x| *x <= req.header.number()),
} }
} }
} }
@ -210,7 +210,7 @@ impl OnDemand {
/// it as easily. /// it as easily.
pub fn header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash) -> Receiver<encoded::Header> { pub fn header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash) -> Receiver<encoded::Header> {
let (sender, receiver) = oneshot::channel(); let (sender, receiver) = oneshot::channel();
match self.cache.lock().block_header(&req.0) { match { self.cache.lock().block_header(&req.0) } {
Some(hdr) => sender.send(hdr).expect(RECEIVER_IN_SCOPE), Some(hdr) => sender.send(hdr).expect(RECEIVER_IN_SCOPE),
None => self.dispatch(ctx, Pending::HeaderByHash(req, sender)), None => self.dispatch(ctx, Pending::HeaderByHash(req, sender)),
} }
@ -232,11 +232,13 @@ impl OnDemand {
sender.send(encoded::Block::new(stream.out())).expect(RECEIVER_IN_SCOPE); sender.send(encoded::Block::new(stream.out())).expect(RECEIVER_IN_SCOPE);
} else { } else {
match self.cache.lock().block_body(&req.hash) { match { self.cache.lock().block_body(&req.hash) } {
Some(body) => { Some(body) => {
let mut stream = RlpStream::new_list(3); let mut stream = RlpStream::new_list(3);
let body = body.rlp();
stream.append_raw(&req.header.into_inner(), 1); stream.append_raw(&req.header.into_inner(), 1);
stream.append_raw(&body.into_inner(), 2); stream.append_raw(&body.at(0).as_raw(), 1);
stream.append_raw(&body.at(1).as_raw(), 1);
sender.send(encoded::Block::new(stream.out())).expect(RECEIVER_IN_SCOPE); sender.send(encoded::Block::new(stream.out())).expect(RECEIVER_IN_SCOPE);
} }
@ -255,7 +257,7 @@ impl OnDemand {
if req.0.receipts_root() == SHA3_NULL_RLP { if req.0.receipts_root() == SHA3_NULL_RLP {
sender.send(Vec::new()).expect(RECEIVER_IN_SCOPE); sender.send(Vec::new()).expect(RECEIVER_IN_SCOPE);
} else { } else {
match self.cache.lock().block_receipts(&req.0.hash()) { match { self.cache.lock().block_receipts(&req.0.hash()) } {
Some(receipts) => sender.send(receipts).expect(RECEIVER_IN_SCOPE), Some(receipts) => sender.send(receipts).expect(RECEIVER_IN_SCOPE),
None => self.dispatch(ctx, Pending::BlockReceipts(req, sender)), None => self.dispatch(ctx, Pending::BlockReceipts(req, sender)),
} }
@ -303,23 +305,26 @@ impl OnDemand {
let complete = builder.build(); let complete = builder.build();
let kind = complete.requests()[0].kind();
for (id, peer) in self.peers.read().iter() { for (id, peer) in self.peers.read().iter() {
if !peer.can_handle(&pending) { continue } if !peer.can_handle(&pending) { continue }
match ctx.request_from(*id, complete.clone()) { match ctx.request_from(*id, complete.clone()) {
Ok(req_id) => { Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id); trace!(target: "on_demand", "{}: Assigned {:?} to peer {}",
req_id, kind, id);
self.pending_requests.write().insert( self.pending_requests.write().insert(
req_id, req_id,
pending, pending,
); );
return return
} }
Err(net::Error::NoCredits) => {}
Err(e) => Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e), trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
} }
} }
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending); self.orphaned_requests.write().push(pending);
} }
@ -353,6 +358,7 @@ impl OnDemand {
let to_dispatch = ::std::mem::replace(&mut *self.orphaned_requests.write(), Vec::new()); let to_dispatch = ::std::mem::replace(&mut *self.orphaned_requests.write(), Vec::new());
trace!(target: "on_demand", "Attempting to dispatch {} orphaned requests.", to_dispatch.len());
for mut orphaned in to_dispatch { for mut orphaned in to_dispatch {
let hung_up = match orphaned { let hung_up = match orphaned {
Pending::HeaderProof(_, ref mut sender) => match *sender { Pending::HeaderProof(_, ref mut sender) => match *sender {
@ -397,10 +403,12 @@ impl Handler for OnDemand {
} }
fn on_announcement(&self, ctx: &EventContext, announcement: &Announcement) { fn on_announcement(&self, ctx: &EventContext, announcement: &Announcement) {
let mut peers = self.peers.write(); {
if let Some(ref mut peer) = peers.get_mut(&ctx.peer()) { let mut peers = self.peers.write();
peer.status.update_from(&announcement); if let Some(ref mut peer) = peers.get_mut(&ctx.peer()) {
peer.capabilities.update_from(&announcement); peer.status.update_from(&announcement);
peer.capabilities.update_from(&announcement);
}
} }
self.dispatch_orphaned(ctx.as_basic()); self.dispatch_orphaned(ctx.as_basic());
@ -422,6 +430,8 @@ impl Handler for OnDemand {
} }
}; };
trace!(target: "on_demand", "Handling response for request {}, kind={:?}", req_id, response.kind());
// handle the response appropriately for the request. // handle the response appropriately for the request.
// all branches which do not return early lead to disabling of the peer // all branches which do not return early lead to disabling of the peer
// due to misbehavior. // due to misbehavior.
@ -441,7 +451,7 @@ impl Handler for OnDemand {
} }
return return
} }
Err(e) => warn!("Error handling response for header request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for header request: {:?}", e),
} }
} }
} }
@ -454,7 +464,7 @@ impl Handler for OnDemand {
let _ = sender.send(header); let _ = sender.send(header);
return return
} }
Err(e) => warn!("Error handling response for header request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for header request: {:?}", e),
} }
} }
} }
@ -467,7 +477,7 @@ impl Handler for OnDemand {
let _ = sender.send(block); let _ = sender.send(block);
return return
} }
Err(e) => warn!("Error handling response for block request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for block request: {:?}", e),
} }
} }
} }
@ -480,7 +490,7 @@ impl Handler for OnDemand {
let _ = sender.send(receipts); let _ = sender.send(receipts);
return return
} }
Err(e) => warn!("Error handling response for receipts request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for receipts request: {:?}", e),
} }
} }
} }
@ -493,7 +503,7 @@ impl Handler for OnDemand {
let _ = sender.send(maybe_account); let _ = sender.send(maybe_account);
return return
} }
Err(e) => warn!("Error handling response for state request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for state request: {:?}", e),
} }
} }
} }
@ -504,7 +514,7 @@ impl Handler for OnDemand {
let _ = sender.send(response.code.clone()); let _ = sender.send(response.code.clone());
return return
} }
Err(e) => warn!("Error handling response for code request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for code request: {:?}", e),
} }
} }
} }
@ -519,7 +529,7 @@ impl Handler for OnDemand {
let _ = sender.send(Err(err)); let _ = sender.send(Err(err));
return return
} }
ProvedExecution::BadProof => warn!("Error handling response for transaction proof request"), ProvedExecution::BadProof => warn!(target: "on_demand", "Error handling response for transaction proof request"),
} }
} }
} }

View File

@ -151,7 +151,8 @@ impl Body {
// concatenate the header and the body. // concatenate the header and the body.
let mut stream = RlpStream::new_list(3); let mut stream = RlpStream::new_list(3);
stream.append_raw(self.header.rlp().as_raw(), 1); stream.append_raw(self.header.rlp().as_raw(), 1);
stream.append_raw(&body.rlp().as_raw(), 2); stream.append_raw(body.rlp().at(0).as_raw(), 1);
stream.append_raw(body.rlp().at(1).as_raw(), 1);
Ok(encoded::Block::new(stream.out())) Ok(encoded::Block::new(stream.out()))
} }
@ -243,12 +244,14 @@ impl TransactionProof {
pub fn check_response(&self, state_items: &[DBValue]) -> ProvedExecution { pub fn check_response(&self, state_items: &[DBValue]) -> ProvedExecution {
let root = self.header.state_root(); let root = self.header.state_root();
let mut env_info = self.env_info.clone();
env_info.gas_limit = self.tx.gas.clone();
state::check_proof( state::check_proof(
state_items, state_items,
root, root,
&self.tx, &self.tx,
&*self.engine, &*self.engine,
&self.env_info, &env_info,
) )
} }
} }

View File

@ -244,7 +244,8 @@ pub enum CompleteRequest {
} }
impl Request { impl Request {
fn kind(&self) -> Kind { /// Get the request kind.
pub fn kind(&self) -> Kind {
match *self { match *self {
Request::Headers(_) => Kind::Headers, Request::Headers(_) => Kind::Headers,
Request::HeaderProof(_) => Kind::HeaderProof, Request::HeaderProof(_) => Kind::HeaderProof,
@ -435,7 +436,8 @@ impl Response {
} }
} }
fn kind(&self) -> Kind { /// Inspect the kind of this response.
pub fn kind(&self) -> Kind {
match *self { match *self {
Response::Headers(_) => Kind::Headers, Response::Headers(_) => Kind::Headers,
Response::HeaderProof(_) => Kind::HeaderProof, Response::HeaderProof(_) => Kind::HeaderProof,
@ -726,7 +728,6 @@ pub mod header_proof {
impl Decodable for Response { impl Decodable for Response {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> { fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(Response { Ok(Response {
proof: rlp.list_at(0)?, proof: rlp.list_at(0)?,
hash: rlp.val_at(1)?, hash: rlp.val_at(1)?,
@ -737,12 +738,10 @@ pub mod header_proof {
impl Encodable for Response { impl Encodable for Response {
fn rlp_append(&self, s: &mut RlpStream) { fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(3).begin_list(self.proof.len()); s.begin_list(3)
for item in &self.proof { .append_list::<Vec<u8>,_>(&self.proof[..])
s.append_list(&item); .append(&self.hash)
} .append(&self.td);
s.append(&self.hash).append(&self.td);
} }
} }
} }
@ -826,7 +825,6 @@ pub mod block_receipts {
impl Decodable for Response { impl Decodable for Response {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> { fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(Response { Ok(Response {
receipts: rlp.as_list()?, receipts: rlp.as_list()?,
}) })
@ -923,8 +921,8 @@ pub mod block_body {
use ethcore::transaction::UnverifiedTransaction; use ethcore::transaction::UnverifiedTransaction;
// check body validity. // check body validity.
let _: Vec<FullHeader> = rlp.list_at(0)?; let _: Vec<UnverifiedTransaction> = rlp.list_at(0)?;
let _: Vec<UnverifiedTransaction> = rlp.list_at(1)?; let _: Vec<FullHeader> = rlp.list_at(1)?;
Ok(Response { Ok(Response {
body: encoded::Body::new(rlp.as_raw().to_owned()), body: encoded::Body::new(rlp.as_raw().to_owned()),
@ -1063,12 +1061,9 @@ pub mod account {
impl Encodable for Response { impl Encodable for Response {
fn rlp_append(&self, s: &mut RlpStream) { fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(5).begin_list(self.proof.len()); s.begin_list(5)
for item in &self.proof { .append_list::<Vec<u8>,_>(&self.proof[..])
s.append_list(&item); .append(&self.nonce)
}
s.append(&self.nonce)
.append(&self.balance) .append(&self.balance)
.append(&self.code_hash) .append(&self.code_hash)
.append(&self.storage_root); .append(&self.storage_root);
@ -1207,11 +1202,9 @@ pub mod storage {
impl Encodable for Response { impl Encodable for Response {
fn rlp_append(&self, s: &mut RlpStream) { fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(2).begin_list(self.proof.len()); s.begin_list(2)
for item in &self.proof { .append_list::<Vec<u8>,_>(&self.proof[..])
s.append_list(&item); .append(&self.value);
}
s.append(&self.value);
} }
} }
} }
@ -1486,9 +1479,16 @@ mod tests {
fn check_roundtrip<T>(val: T) fn check_roundtrip<T>(val: T)
where T: ::rlp::Encodable + ::rlp::Decodable + PartialEq + ::std::fmt::Debug where T: ::rlp::Encodable + ::rlp::Decodable + PartialEq + ::std::fmt::Debug
{ {
// check as single value.
let bytes = ::rlp::encode(&val); let bytes = ::rlp::encode(&val);
let new_val: T = ::rlp::decode(&bytes); let new_val: T = ::rlp::decode(&bytes);
assert_eq!(val, new_val); assert_eq!(val, new_val);
// check as list containing single value.
let list = [val];
let bytes = ::rlp::encode_list(&list);
let new_list: Vec<T> = ::rlp::decode_list(&bytes);
assert_eq!(&list, &new_list[..]);
} }
#[test] #[test]
@ -1540,7 +1540,7 @@ mod tests {
let full_req = Request::HeaderProof(req.clone()); let full_req = Request::HeaderProof(req.clone());
let res = HeaderProofResponse { let res = HeaderProofResponse {
proof: Vec::new(), proof: vec![vec![1, 2, 3], vec![4, 5, 6]],
hash: Default::default(), hash: Default::default(),
td: 100.into(), td: 100.into(),
}; };
@ -1572,6 +1572,7 @@ mod tests {
#[test] #[test]
fn body_roundtrip() { fn body_roundtrip() {
use ethcore::transaction::{Transaction, UnverifiedTransaction};
let req = IncompleteBodyRequest { let req = IncompleteBodyRequest {
hash: Field::Scalar(Default::default()), hash: Field::Scalar(Default::default()),
}; };
@ -1579,8 +1580,12 @@ mod tests {
let full_req = Request::Body(req.clone()); let full_req = Request::Body(req.clone());
let res = BodyResponse { let res = BodyResponse {
body: { body: {
let header = ::ethcore::header::Header::default();
let tx = UnverifiedTransaction::from(Transaction::default().fake_sign(Default::default()));
let mut stream = RlpStream::new_list(2); let mut stream = RlpStream::new_list(2);
stream.begin_list(0).begin_list(0); stream.begin_list(2).append(&tx).append(&tx)
.begin_list(1).append(&header);
::ethcore::encoded::Body::new(stream.out()) ::ethcore::encoded::Body::new(stream.out())
}, },
}; };
@ -1601,7 +1606,7 @@ mod tests {
let full_req = Request::Account(req.clone()); let full_req = Request::Account(req.clone());
let res = AccountResponse { let res = AccountResponse {
proof: Vec::new(), proof: vec![vec![1, 2, 3], vec![4, 5, 6]],
nonce: 100.into(), nonce: 100.into(),
balance: 123456.into(), balance: 123456.into(),
code_hash: Default::default(), code_hash: Default::default(),
@ -1625,7 +1630,7 @@ mod tests {
let full_req = Request::Storage(req.clone()); let full_req = Request::Storage(req.clone());
let res = StorageResponse { let res = StorageResponse {
proof: Vec::new(), proof: vec![vec![1, 2, 3], vec![4, 5, 6]],
value: H256::default(), value: H256::default(),
}; };
let full_res = Response::Storage(res.clone()); let full_res = Response::Storage(res.clone());
@ -1707,4 +1712,31 @@ mod tests {
assert_eq!(rlp.val_at::<usize>(0).unwrap(), 100usize); assert_eq!(rlp.val_at::<usize>(0).unwrap(), 100usize);
assert_eq!(rlp.list_at::<Request>(1).unwrap(), reqs); assert_eq!(rlp.list_at::<Request>(1).unwrap(), reqs);
} }
#[test]
fn responses_vec() {
let mut stream = RlpStream::new_list(2);
stream.begin_list(0).begin_list(0);
let body = ::ethcore::encoded::Body::new(stream.out());
let reqs = vec![
Response::Headers(HeadersResponse { headers: vec![] }),
Response::HeaderProof(HeaderProofResponse { proof: vec![], hash: Default::default(), td: 100.into()}),
Response::Receipts(ReceiptsResponse { receipts: vec![Default::default()] }),
Response::Body(BodyResponse { body: body }),
Response::Account(AccountResponse {
proof: vec![],
nonce: 100.into(),
balance: 123.into(),
code_hash: Default::default(),
storage_root: Default::default()
}),
Response::Storage(StorageResponse { proof: vec![], value: H256::default() }),
Response::Code(CodeResponse { code: vec![1, 2, 3, 4, 5] }),
Response::Execution(ExecutionResponse { items: vec![] }),
];
let raw = ::rlp::encode_list(&reqs);
assert_eq!(::rlp::decode_list::<Response>(&raw), reqs);
}
} }

View File

@ -23,6 +23,7 @@ use std::io::Write;
// TODO: `include!` these from files where they're pretty-printed? // TODO: `include!` these from files where they're pretty-printed?
const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#; const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#;
const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#; const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#;
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
fn build_file(name: &str, abi: &str, filename: &str) { fn build_file(name: &str, abi: &str, filename: &str) {
let code = ::native_contract_generator::generate_module(name, abi).unwrap(); let code = ::native_contract_generator::generate_module(name, abi).unwrap();
@ -37,4 +38,5 @@ fn build_file(name: &str, abi: &str, filename: &str) {
fn main() { fn main() {
build_file("Registry", REGISTRY_ABI, "registry.rs"); build_file("Registry", REGISTRY_ABI, "registry.rs");
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
} }

View File

@ -25,6 +25,8 @@ extern crate ethcore_util as util;
mod registry; mod registry;
mod service_transaction; mod service_transaction;
mod secretstore_acl_storage;
pub use self::registry::Registry; pub use self::registry::Registry;
pub use self::service_transaction::ServiceTransactionChecker; pub use self::service_transaction::ServiceTransactionChecker;
pub use self::secretstore_acl_storage::SecretStoreAclStorage;

View File

@ -14,13 +14,9 @@
// 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/>.
//! Adds a seventh column for node information. #![allow(unused_mut, unused_variables, unused_imports)]
use util::migration::ChangeColumns; //! Secret store ACL storage contract.
// TODO: testing.
/// The migration from v10 to v11. include!(concat!(env!("OUT_DIR"), "/secretstore_acl_storage.rs"));
pub const TO_V11: ChangeColumns = ChangeColumns {
pre_columns: Some(6),
post_columns: Some(7),
version: 11,
};

View File

@ -395,7 +395,7 @@ impl Client {
if header.number() < self.engine().params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() { if header.number() < self.engine().params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() {
locked_block = locked_block.strip_receipts(); locked_block = locked_block.strip_receipts();
} }
// Final Verification // Final Verification
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
@ -1627,10 +1627,12 @@ impl ::client::ProvingBlockChainClient for Client {
} }
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> { fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> {
let (state, env_info) = match (self.state_at(id), self.env_info(id)) { let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) {
(Some(s), Some(e)) => (s, e), (Some(s), Some(e)) => (s, e),
_ => return None, _ => return None,
}; };
env_info.gas_limit = transaction.gas.clone();
let mut jdb = self.state_db.lock().journal_db().boxed_clone(); let mut jdb = self.state_db.lock().journal_db().boxed_clone();
let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); let backend = state::backend::Proving::new(jdb.as_hashdb_mut());

View File

@ -26,7 +26,7 @@ use verification::{VerifierType, QueueConfig};
use util::{journaldb, CompactionProfile}; use util::{journaldb, CompactionProfile};
/// Client state db compaction profile /// Client state db compaction profile
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum DatabaseCompactionProfile { pub enum DatabaseCompactionProfile {
/// Try to determine compaction profile automatically /// Try to determine compaction profile automatically
Auto, Auto,

View File

@ -38,8 +38,10 @@ pub const COL_TRACE: Option<u32> = Some(4);
pub const COL_ACCOUNT_BLOOM: Option<u32> = Some(5); pub const COL_ACCOUNT_BLOOM: Option<u32> = Some(5);
/// Column for general information from the local node which can persist. /// Column for general information from the local node which can persist.
pub const COL_NODE_INFO: Option<u32> = Some(6); pub const COL_NODE_INFO: Option<u32> = Some(6);
/// Column for the light client chain.
pub const COL_LIGHT_CHAIN: Option<u32> = Some(7);
/// Number of columns in DB /// Number of columns in DB
pub const NUM_COLUMNS: Option<u32> = Some(7); pub const NUM_COLUMNS: Option<u32> = Some(8);
/// Modes for updating caches. /// Modes for updating caches.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@ -16,6 +16,8 @@
//! Database migrations. //! Database migrations.
use util::migration::ChangeColumns;
pub mod state; pub mod state;
pub mod blocks; pub mod blocks;
pub mod extras; pub mod extras;
@ -27,5 +29,18 @@ pub use self::v9::Extract;
mod v10; mod v10;
pub use self::v10::ToV10; pub use self::v10::ToV10;
mod v11; /// The migration from v10 to v11.
pub use self::v11::TO_V11; /// Adds a column for node info.
pub const TO_V11: ChangeColumns = ChangeColumns {
pre_columns: Some(6),
post_columns: Some(7),
version: 11,
};
/// The migration from v11 to v12.
/// Adds a column for light chain storage.
pub const TO_V12: ChangeColumns = ChangeColumns {
pre_columns: Some(7),
post_columns: Some(8),
version: 12,
};

View File

@ -78,6 +78,12 @@ impl fmt::Display for Error {
} }
} }
impl Into<String> for Error {
fn into(self) -> String {
format!("{}", self)
}
}
impl From<SecpError> for Error { impl From<SecpError> for Error {
fn from(e: SecpError) -> Self { fn from(e: SecpError) -> Self {
Error::Secp(e) Error::Secp(e)

View File

@ -27,6 +27,7 @@ pub fn public_to_address(public: &Public) -> Address {
result result
} }
#[derive(Clone)]
/// secp256k1 key pair /// secp256k1 key pair
pub struct KeyPair { pub struct KeyPair {
secret: Secret, secret: Secret,

View File

@ -92,12 +92,13 @@ pub enum URLHintResult {
} }
/// URLHint Contract interface /// URLHint Contract interface
pub trait URLHint { pub trait URLHint: Send + Sync {
/// Resolves given id to registrar entry. /// Resolves given id to registrar entry.
fn resolve(&self, id: Bytes) -> Option<URLHintResult>; fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
} }
/// `URLHintContract` API /// `URLHintContract` API
#[derive(Clone)]
pub struct URLHintContract { pub struct URLHintContract {
urlhint: Contract, urlhint: Contract,
registrar: Contract, registrar: Contract,

View File

@ -32,12 +32,13 @@ use std::sync::Arc;
use std::net::{SocketAddr, IpAddr}; use std::net::{SocketAddr, IpAddr};
use error::ServerError; use error::ServerError;
use route::Out; use route::Out;
use http::hyper::server::{Listening, Handler, Request, Response}; use http::hyper::server::{Handler, Request, Response};
use http::hyper::net::HttpStream; use http::hyper::net::HttpStream;
use http::hyper::header::{self, Vary, ContentLength, ContentType}; use http::hyper::header::{self, Vary, ContentLength, ContentType};
use http::hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use http::hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode};
use ethcore::client::BlockChainClient; use ethcore::client::BlockChainClient;
pub use http::hyper::server::Listening;
pub use http::{AccessControlAllowOrigin, Host, DomainsValidation}; pub use http::{AccessControlAllowOrigin, Host, DomainsValidation};
/// Request/response handler /// Request/response handler

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "1.7.43", "version": "1.7.46",
"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>",
@ -176,7 +176,7 @@
"geopattern": "1.2.3", "geopattern": "1.2.3",
"isomorphic-fetch": "2.2.1", "isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5", "js-sha3": "0.5.5",
"keythereum": "0.4.3", "keythereum": "0.4.6",
"lodash": "4.17.2", "lodash": "4.17.2",
"loglevel": "1.4.1", "loglevel": "1.4.1",
"marked": "0.3.6", "marked": "0.3.6",

View File

@ -1,2 +1 @@
// test script 9 // test script 10
// trigger rebuild on master 15 Mar 2017, 11:19

View File

@ -14,7 +14,7 @@
// 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/>.
import { keythereum } from '../ethkey'; import { createKeyObject, decryptPrivateKey } from '../ethkey';
export default class Account { export default class Account {
constructor (persist, data) { constructor (persist, data) {
@ -31,12 +31,14 @@ export default class Account {
} }
isValidPassword (password) { isValidPassword (password) {
try { return decryptPrivateKey(this._keyObject, password)
keythereum.recover(Buffer.from(password), this._keyObject); .then((privateKey) => {
return true; if (!privateKey) {
} catch (e) { return false;
return false; }
}
return true;
});
} }
get address () { get address () {
@ -68,21 +70,23 @@ export default class Account {
} }
decryptPrivateKey (password) { decryptPrivateKey (password) {
return keythereum.recover(Buffer.from(password), this._keyObject); return decryptPrivateKey(this._keyObject, password);
}
changePassword (key, password) {
return createKeyObject(key, password).then((keyObject) => {
this._keyObject = keyObject;
this._persist();
});
} }
static fromPrivateKey (persist, key, password) { static fromPrivateKey (persist, key, password) {
const iv = keythereum.crypto.randomBytes(16); return createKeyObject(key, password).then((keyObject) => {
const salt = keythereum.crypto.randomBytes(32); const account = new Account(persist, { keyObject });
// Keythereum will fail if `password` is an empty string return account;
password = Buffer.from(password); });
const keyObject = keythereum.dump(password, key, salt, iv);
const account = new Account(persist, { keyObject });
return account;
} }
toJSON () { toJSON () {

View File

@ -38,14 +38,23 @@ export default class Accounts {
create (secret, password) { create (secret, password) {
const privateKey = Buffer.from(secret.slice(2), 'hex'); const privateKey = Buffer.from(secret.slice(2), 'hex');
const account = Account.fromPrivateKey(this.persist, privateKey, password);
this._store.push(account); return Account
this.lastAddress = account.address; .fromPrivateKey(this.persist, privateKey, password)
.then((account) => {
const { address } = account;
this.persist(); if (this._store.find((account) => account.address === address)) {
throw new Error(`Account ${address} already exists!`);
}
return account.address; this._store.push(account);
this.lastAddress = address;
this.persist();
return account.address;
});
} }
set lastAddress (value) { set lastAddress (value) {
@ -73,28 +82,41 @@ export default class Accounts {
remove (address, password) { remove (address, password) {
address = address.toLowerCase(); address = address.toLowerCase();
const account = this.get(address);
if (!account) {
return false;
}
return account
.isValidPassword(password)
.then((isValid) => {
if (!isValid) {
return false;
}
if (address === this.lastAddress) {
this.lastAddress = NULL_ADDRESS;
}
this.removeUnsafe(address);
return true;
});
}
removeUnsafe (address) {
address = address.toLowerCase();
const index = this._store.findIndex((account) => account.address === address); const index = this._store.findIndex((account) => account.address === address);
if (index === -1) { if (index === -1) {
return false; return;
}
const account = this._store[index];
if (!account.isValidPassword(password)) {
console.log('invalid password');
return false;
}
if (address === this.lastAddress) {
this.lastAddress = NULL_ADDRESS;
} }
this._store.splice(index, 1); this._store.splice(index, 1);
this.persist(); this.persist();
return true;
} }
mapArray (mapper) { mapArray (mapper) {

View File

@ -0,0 +1,19 @@
// 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 function () {
// empty file included while building parity.js (don't include local keygen)
}

View File

@ -14,31 +14,35 @@
// 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/>.
// Allow a web worker in the browser, with a fallback for Node.js import workerPool from './workerPool';
const hasWebWorkers = typeof Worker !== 'undefined';
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
: require('./worker').KeyWorker;
// Local accounts should never be used outside of the browser export function createKeyObject (key, password) {
export let keythereum = null; return workerPool.getWorker().action('createKeyObject', { key, password })
.then((obj) => JSON.parse(obj));
}
if (hasWebWorkers) { export function decryptPrivateKey (keyObject, password) {
require('keythereum/dist/keythereum'); return workerPool
.getWorker()
.action('decryptPrivateKey', { keyObject, password })
.then((privateKey) => {
if (privateKey) {
return Buffer.from(privateKey);
}
keythereum = window.keythereum; return null;
});
} }
export function phraseToAddress (phrase) { export function phraseToAddress (phrase) {
return phraseToWallet(phrase).then((wallet) => wallet.address); return phraseToWallet(phrase)
.then((wallet) => wallet.address);
} }
export function phraseToWallet (phrase) { export function phraseToWallet (phrase) {
return new Promise((resolve, reject) => { return workerPool.getWorker().action('phraseToWallet', phrase);
const worker = new KeyWorker(); }
worker.postMessage(phrase); export function verifySecret (secret) {
worker.onmessage = ({ data }) => { return workerPool.getWorker().action('verifySecret', secret);
resolve(data);
};
});
} }

View File

@ -14,58 +14,104 @@
// 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/>.
import { keccak_256 as keccak256 } from 'js-sha3';
import secp256k1 from 'secp256k1/js'; import secp256k1 from 'secp256k1/js';
import { keccak_256 as keccak256 } from 'js-sha3';
import { bytesToHex } from '~/api/util/format';
const isWorker = typeof self !== 'undefined';
// Stay compatible between environments // Stay compatible between environments
if (typeof self !== 'object') { if (!isWorker) {
const scope = typeof global === 'undefined' ? window : global; const scope = typeof global === 'undefined' ? window : global;
scope.self = scope; scope.self = scope;
} }
function bytesToHex (bytes) { // keythereum should never be used outside of the browser
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join(''); let keythereum = null;
if (isWorker) {
require('keythereum/dist/keythereum');
keythereum = self.keythereum;
} }
// Logic ported from /ethkey/src/brain.rs function route ({ action, payload }) {
function phraseToWallet (phrase) { if (action in actions) {
let secret = keccak256.array(phrase); return actions[action](payload);
for (let i = 0; i < 16384; i++) {
secret = keccak256.array(secret);
} }
while (true) { return null;
secret = keccak256.array(secret); }
const secretBuf = Buffer.from(secret); const actions = {
phraseToWallet (phrase) {
let secret = keccak256.array(phrase);
if (secp256k1.privateKeyVerify(secretBuf)) { for (let i = 0; i < 16384; i++) {
// No compression, slice out last 64 bytes secret = keccak256.array(secret);
const publicBuf = secp256k1.publicKeyCreate(secretBuf, false).slice(-64); }
const address = keccak256.array(publicBuf).slice(12);
if (address[0] !== 0) { while (true) {
continue; secret = keccak256.array(secret);
const secretBuf = Buffer.from(secret);
if (secp256k1.privateKeyVerify(secretBuf)) {
// No compression, slice out last 64 bytes
const publicBuf = secp256k1.publicKeyCreate(secretBuf, false).slice(-64);
const address = keccak256.array(publicBuf).slice(12);
if (address[0] !== 0) {
continue;
}
const wallet = {
secret: bytesToHex(secretBuf),
public: bytesToHex(publicBuf),
address: bytesToHex(address)
};
return wallet;
} }
}
},
const wallet = { verifySecret (secret) {
secret: bytesToHex(secretBuf), const key = Buffer.from(secret.slice(2), 'hex');
public: bytesToHex(publicBuf),
address: bytesToHex(address)
};
return wallet; return secp256k1.privateKeyVerify(key);
},
createKeyObject ({ key, password }) {
key = Buffer.from(key);
password = Buffer.from(password);
const iv = keythereum.crypto.randomBytes(16);
const salt = keythereum.crypto.randomBytes(32);
const keyObject = keythereum.dump(password, key, salt, iv);
return JSON.stringify(keyObject);
},
decryptPrivateKey ({ keyObject, password }) {
password = Buffer.from(password);
try {
const key = keythereum.recover(password, keyObject);
// Convert to array to safely send from the worker
return Array.from(key);
} catch (e) {
return null;
} }
} }
} };
self.onmessage = function ({ data }) { self.onmessage = function ({ data }) {
const wallet = phraseToWallet(data); const result = route(data);
postMessage(wallet); postMessage(result);
close();
}; };
// Emulate a web worker in Node.js // Emulate a web worker in Node.js
@ -73,9 +119,9 @@ class KeyWorker {
postMessage (data) { postMessage (data) {
// Force async // Force async
setTimeout(() => { setTimeout(() => {
const wallet = phraseToWallet(data); const result = route(data);
this.onmessage({ data: wallet }); this.onmessage({ data: result });
}, 0); }, 0);
} }

View File

@ -0,0 +1,61 @@
// 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/>.
// Allow a web worker in the browser, with a fallback for Node.js
const hasWebWorkers = typeof Worker !== 'undefined';
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
: require('./worker').KeyWorker;
class WorkerContainer {
busy = false;
_worker = new KeyWorker();
action (action, payload) {
if (this.busy) {
throw new Error('Cannot issue an action on a busy worker!');
}
this.busy = true;
return new Promise((resolve, reject) => {
this._worker.postMessage({ action, payload });
this._worker.onmessage = ({ data }) => {
this.busy = false;
resolve(data);
};
});
}
}
class WorkerPool {
pool = [];
getWorker () {
let container = this.pool.find((container) => !container.busy);
if (container) {
return container;
}
container = new WorkerContainer();
this.pool.push(container);
return container;
}
}
export default new WorkerPool();

View File

@ -19,7 +19,7 @@ import accounts from './accounts';
import transactions from './transactions'; import transactions from './transactions';
import { Middleware } from '../transport'; import { Middleware } from '../transport';
import { inNumber16 } from '../format/input'; import { inNumber16 } from '../format/input';
import { phraseToWallet, phraseToAddress } from './ethkey'; import { phraseToWallet, phraseToAddress, verifySecret } from './ethkey';
import { randomPhrase } from '@parity/wordlist'; import { randomPhrase } from '@parity/wordlist';
export default class LocalAccountsMiddleware extends Middleware { export default class LocalAccountsMiddleware extends Middleware {
@ -57,6 +57,22 @@ export default class LocalAccountsMiddleware extends Middleware {
}); });
}); });
register('parity_changePassword', ([address, oldPassword, newPassword]) => {
const account = accounts.get(address);
return account
.decryptPrivateKey(oldPassword)
.then((privateKey) => {
if (!privateKey) {
return false;
}
account.changePassword(privateKey, newPassword);
return true;
});
});
register('parity_checkRequest', ([id]) => { register('parity_checkRequest', ([id]) => {
return transactions.hash(id) || Promise.resolve(null); return transactions.hash(id) || Promise.resolve(null);
}); });
@ -84,6 +100,17 @@ export default class LocalAccountsMiddleware extends Middleware {
}); });
}); });
register('parity_newAccountFromSecret', ([secret, password]) => {
return verifySecret(secret)
.then((isValid) => {
if (!isValid) {
throw new Error('Invalid secret key');
}
return accounts.create(secret, password);
});
});
register('parity_setAccountMeta', ([address, meta]) => { register('parity_setAccountMeta', ([address, meta]) => {
accounts.get(address).meta = meta; accounts.get(address).meta = meta;
@ -127,6 +154,12 @@ export default class LocalAccountsMiddleware extends Middleware {
return accounts.remove(address, password); return accounts.remove(address, password);
}); });
register('parity_testPassword', ([address, password]) => {
const account = accounts.get(address);
return account.isValidPassword(password);
});
register('signer_confirmRequest', ([id, modify, password]) => { register('signer_confirmRequest', ([id, modify, password]) => {
const { const {
gasPrice, gasPrice,
@ -137,30 +170,33 @@ export default class LocalAccountsMiddleware extends Middleware {
data data
} = Object.assign(transactions.get(id), modify); } = Object.assign(transactions.get(id), modify);
return this const account = accounts.get(from);
.rpcRequest('parity_nextNonce', [from])
.then((nonce) => {
const tx = new EthereumTx({
nonce,
to,
data,
gasLimit: inNumber16(gasLimit),
gasPrice: inNumber16(gasPrice),
value: inNumber16(value)
});
const account = accounts.get(from);
tx.sign(account.decryptPrivateKey(password)); return Promise.all([
this.rpcRequest('parity_nextNonce', [from]),
const serializedTx = `0x${tx.serialize().toString('hex')}`; account.decryptPrivateKey(password)
])
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]); .then(([nonce, privateKey]) => {
}) const tx = new EthereumTx({
.then((hash) => { nonce,
transactions.confirm(id, hash); to,
data,
return {}; gasLimit: inNumber16(gasLimit),
gasPrice: inNumber16(gasPrice),
value: inNumber16(value)
}); });
tx.sign(privateKey);
const serializedTx = `0x${tx.serialize().toString('hex')}`;
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]);
})
.then((hash) => {
transactions.confirm(id, hash);
return {};
});
}); });
register('signer_rejectRequest', ([id]) => { register('signer_rejectRequest', ([id]) => {

View File

@ -80,12 +80,16 @@ export default class JsonRpcBase extends EventEmitter {
const res = middleware.handle(method, params); const res = middleware.handle(method, params);
if (res != null) { if (res != null) {
const result = this._wrapSuccessResult(res); // If `res` isn't a promise, we need to wrap it
const json = this.encode(method, params); return Promise.resolve(res)
.then((res) => {
const result = this._wrapSuccessResult(res);
const json = this.encode(method, params);
Logging.send(method, params, { json, result }); Logging.send(method, params, { json, result });
return res; return res;
});
} }
} }

View File

@ -17,7 +17,7 @@
import { range } from 'lodash'; import { range } from 'lodash';
export function bytesToHex (bytes) { export function bytesToHex (bytes) {
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join(''); return '0x' + Buffer.from(bytes).toString('hex');
} }
export function cleanupValue (value, type) { export function cleanupValue (value, type) {

View File

@ -23,6 +23,7 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import { Form, Input, IdentityIcon } from '~/ui'; import { Form, Input, IdentityIcon } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength'; import PasswordStrength from '~/ui/Form/PasswordStrength';
import { RefreshIcon } from '~/ui/Icons'; import { RefreshIcon } from '~/ui/Icons';
import Loading from '~/ui/Loading';
import ChangeVault from '../ChangeVault'; import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
@ -170,7 +171,9 @@ export default class CreateAccount extends Component {
const { accounts } = this.state; const { accounts } = this.state;
if (!accounts) { if (!accounts) {
return null; return (
<Loading className={ styles.selector } size={ 1 } />
);
} }
const identities = Object const identities = Object
@ -205,6 +208,14 @@ export default class CreateAccount extends Component {
createIdentities = () => { createIdentities = () => {
const { createStore } = this.props; const { createStore } = this.props;
this.setState({
accounts: null,
selectedAddress: ''
});
createStore.setAddress('');
createStore.setPhrase('');
return createStore return createStore
.createIdentities() .createIdentities()
.then((accounts) => { .then((accounts) => {

View File

@ -58,12 +58,12 @@ describe('modals/CreateAccount/NewAccount', () => {
return instance.componentWillMount(); return instance.componentWillMount();
}); });
it('creates initial accounts', () => { it('resets the accounts', () => {
expect(Object.keys(instance.state.accounts).length).to.equal(7); expect(instance.state.accounts).to.be.null;
}); });
it('sets the initial selected value', () => { it('resets the initial selected value', () => {
expect(instance.state.selectedAddress).to.equal(Object.keys(instance.state.accounts)[0]); expect(instance.state.selectedAddress).to.equal('');
}); });
}); });
}); });

View File

@ -69,7 +69,7 @@ export default class Store {
return !(this.nameError || this.walletFileError); return !(this.nameError || this.walletFileError);
case 'fromNew': case 'fromNew':
return !(this.nameError || this.passwordRepeatError); return !(this.nameError || this.passwordRepeatError) && this.hasAddress;
case 'fromPhrase': case 'fromPhrase':
return !(this.nameError || this.passwordRepeatError); return !(this.nameError || this.passwordRepeatError);
@ -85,6 +85,10 @@ export default class Store {
} }
} }
@computed get hasAddress () {
return !!(this.address);
}
@computed get passwordRepeatError () { @computed get passwordRepeatError () {
return this.password === this.passwordRepeat return this.password === this.passwordRepeat
? null ? null

View File

@ -329,6 +329,7 @@ describe('modals/CreateAccount/Store', () => {
describe('createType === fromNew', () => { describe('createType === fromNew', () => {
beforeEach(() => { beforeEach(() => {
store.setCreateType('fromNew'); store.setCreateType('fromNew');
store.setAddress('0x0000000000000000000000000000000000000000');
}); });
it('returns true on no errors', () => { it('returns true on no errors', () => {
@ -337,11 +338,13 @@ describe('modals/CreateAccount/Store', () => {
it('returns false on nameError', () => { it('returns false on nameError', () => {
store.setName(''); store.setName('');
expect(store.canCreate).to.be.false; expect(store.canCreate).to.be.false;
}); });
it('returns false on passwordRepeatError', () => { it('returns false on passwordRepeatError', () => {
store.setPassword('testing'); store.setPassword('testing');
expect(store.canCreate).to.be.false; expect(store.canCreate).to.be.false;
}); });
}); });

View File

@ -92,9 +92,9 @@ export function generateQr (from, tx, hash, rlp) {
account: from.substr(2), account: from.substr(2),
hash: hash.substr(2), hash: hash.substr(2),
details: { details: {
gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex'))), gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex') || '0')),
gas: inNumber10(inHex(tx.gasLimit.toString('hex'))), gas: inNumber10(inHex(tx.gasLimit.toString('hex') || '0')),
nonce: inNumber10(inHex(tx.nonce.toString('hex'))), nonce: inNumber10(inHex(tx.nonce.toString('hex') || '0')),
to: inAddress(tx.to.toString('hex')), to: inAddress(tx.to.toString('hex')),
value: inHex(tx.value.toString('hex') || '0') value: inHex(tx.value.toString('hex') || '0')
} }

View File

@ -24,9 +24,11 @@ const ENV = process.env.NODE_ENV || 'development';
const isProd = ENV === 'production'; const isProd = ENV === 'production';
const LIBRARY = process.env.LIBRARY; const LIBRARY = process.env.LIBRARY;
if (!LIBRARY) { if (!LIBRARY) {
process.exit(-1); process.exit(-1);
} }
const SRC = LIBRARY.toLowerCase(); const SRC = LIBRARY.toLowerCase();
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC); const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
@ -63,12 +65,18 @@ module.exports = {
'babel-loader?cacheDirectory=true' 'babel-loader?cacheDirectory=true'
], ],
exclude: /node_modules/ exclude: /node_modules/
},
{
test: /\.js$/,
include: /node_modules\/(ethereumjs-tx|@parity\/wordlist)/,
use: 'babel-loader'
} }
] ]
}, },
resolve: { resolve: {
alias: { alias: {
'secp256k1/js': path.resolve(__dirname, '../src/api/local/ethkey/dummy.js'),
'~': path.resolve(__dirname, '../src') '~': path.resolve(__dirname, '../src')
}, },
modules: [ modules: [
@ -85,15 +93,12 @@ module.exports = {
to: 'package.json', to: 'package.json',
transform: function (content, path) { transform: function (content, path) {
const json = JSON.parse(content.toString()); const json = JSON.parse(content.toString());
json.version = packageJson.version;
// Add tests dependencies to Dev Deps
json.devDependencies.chai = packageJson.devDependencies.chai; json.devDependencies.chai = packageJson.devDependencies.chai;
json.devDependencies.mocha = packageJson.devDependencies.mocha; json.devDependencies.mocha = packageJson.devDependencies.mocha;
json.devDependencies.nock = packageJson.devDependencies.nock; json.devDependencies.nock = packageJson.devDependencies.nock;
// Add test script
json.scripts.test = 'mocha \'test/*.spec.js\''; json.scripts.test = 'mocha \'test/*.spec.js\'';
json.version = packageJson.version;
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8'); return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
} }

View File

@ -39,7 +39,7 @@ warp = true
allow_ips = "all" allow_ips = "all"
snapshot_peers = 0 snapshot_peers = 0
max_pending_peers = 64 max_pending_peers = 64
serve_light = true no_serve_light = false
reserved_only = false reserved_only = false
reserved_peers = "./path_to_file" reserved_peers = "./path_to_file"

View File

@ -94,6 +94,7 @@ usage! {
flag_chain: String = "foundation", or |c: &Config| otry!(c.parity).chain.clone(), flag_chain: String = "foundation", or |c: &Config| otry!(c.parity).chain.clone(),
flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(),
flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(), flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(),
flag_light: bool = false, or |c: &Config| otry!(c.parity).light,
// -- Account Options // -- Account Options
flag_unlock: Option<String> = None, flag_unlock: Option<String> = None,
@ -149,6 +150,8 @@ usage! {
flag_reserved_only: bool = false, flag_reserved_only: bool = false,
or |c: &Config| otry!(c.network).reserved_only.clone(), or |c: &Config| otry!(c.network).reserved_only.clone(),
flag_no_ancient_blocks: bool = false, or |_| None, flag_no_ancient_blocks: bool = false, or |_| None,
flag_no_serve_light: bool = false,
or |c: &Config| otry!(c.network).no_serve_light.clone(),
// -- API and Console Options // -- API and Console Options
// RPC // RPC
@ -164,6 +167,8 @@ 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,
or |c: &Config| otry!(c.rpc).threads.map(Some),
// IPC // IPC
flag_no_ipc: bool = false, flag_no_ipc: bool = false,
@ -176,21 +181,8 @@ usage! {
// DAPPS // DAPPS
flag_no_dapps: bool = false, flag_no_dapps: bool = false,
or |c: &Config| otry!(c.dapps).disable.clone(), or |c: &Config| otry!(c.dapps).disable.clone(),
flag_dapps_port: u16 = 8080u16,
or |c: &Config| otry!(c.dapps).port.clone(),
flag_dapps_interface: String = "local",
or |c: &Config| otry!(c.dapps).interface.clone(),
flag_dapps_hosts: String = "none",
or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")),
flag_dapps_cors: Option<String> = None,
or |c: &Config| otry!(c.dapps).cors.clone().map(Some),
flag_dapps_path: String = "$BASE/dapps", flag_dapps_path: String = "$BASE/dapps",
or |c: &Config| otry!(c.dapps).path.clone(), or |c: &Config| otry!(c.dapps).path.clone(),
flag_dapps_user: Option<String> = None,
or |c: &Config| otry!(c.dapps).user.clone().map(Some),
flag_dapps_pass: Option<String> = None,
or |c: &Config| otry!(c.dapps).pass.clone().map(Some),
flag_dapps_apis_all: bool = false, or |_| None,
// Secret Store // Secret Store
flag_no_secretstore: bool = false, flag_no_secretstore: bool = false,
@ -330,6 +322,22 @@ usage! {
or |c: &Config| otry!(c.misc).log_file.clone().map(Some), or |c: &Config| otry!(c.misc).log_file.clone().map(Some),
flag_no_color: bool = false, flag_no_color: bool = false,
or |c: &Config| otry!(c.misc).color.map(|c| !c).clone(), or |c: &Config| otry!(c.misc).color.map(|c| !c).clone(),
// -- Legacy Options supported in configs
flag_dapps_port: Option<u16> = None,
or |c: &Config| otry!(c.dapps).port.clone().map(Some),
flag_dapps_interface: Option<String> = None,
or |c: &Config| otry!(c.dapps).interface.clone().map(Some),
flag_dapps_hosts: Option<String> = None,
or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| Some(vec.join(","))),
flag_dapps_cors: Option<String> = None,
or |c: &Config| otry!(c.dapps).cors.clone().map(Some),
flag_dapps_user: Option<String> = None,
or |c: &Config| otry!(c.dapps).user.clone().map(Some),
flag_dapps_pass: Option<String> = None,
or |c: &Config| otry!(c.dapps).pass.clone().map(Some),
flag_dapps_apis_all: Option<bool> = None, or |_| None,
} }
{ {
// Values with optional default value. // Values with optional default value.
@ -374,6 +382,7 @@ struct Operating {
db_path: Option<String>, db_path: Option<String>,
keys_path: Option<String>, keys_path: Option<String>,
identity: Option<String>, identity: Option<String>,
light: Option<bool>,
} }
#[derive(Default, Debug, PartialEq, RustcDecodable)] #[derive(Default, Debug, PartialEq, RustcDecodable)]
@ -409,6 +418,7 @@ struct Network {
node_key: Option<String>, node_key: Option<String>,
reserved_peers: Option<String>, reserved_peers: Option<String>,
reserved_only: Option<bool>, reserved_only: Option<bool>,
no_serve_light: Option<bool>,
} }
#[derive(Default, Debug, PartialEq, RustcDecodable)] #[derive(Default, Debug, PartialEq, RustcDecodable)]
@ -419,6 +429,7 @@ 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>,
} }
#[derive(Default, Debug, PartialEq, RustcDecodable)] #[derive(Default, Debug, PartialEq, RustcDecodable)]
@ -633,6 +644,7 @@ mod tests {
flag_db_path: Some("$HOME/.parity/chains".into()), flag_db_path: Some("$HOME/.parity/chains".into()),
flag_keys_path: "$HOME/.parity/keys".into(), flag_keys_path: "$HOME/.parity/keys".into(),
flag_identity: "".into(), flag_identity: "".into(),
flag_light: false,
// -- Account Options // -- Account Options
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()), flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
@ -663,6 +675,7 @@ mod tests {
flag_reserved_peers: Some("./path_to_file".into()), flag_reserved_peers: Some("./path_to_file".into()),
flag_reserved_only: false, flag_reserved_only: false,
flag_no_ancient_blocks: false, flag_no_ancient_blocks: false,
flag_no_serve_light: false,
// -- API and Console Options // -- API and Console Options
// RPC // RPC
@ -672,6 +685,7 @@ mod tests {
flag_jsonrpc_cors: Some("null".into()), flag_jsonrpc_cors: Some("null".into()),
flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".into(), flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".into(),
flag_jsonrpc_hosts: "none".into(), flag_jsonrpc_hosts: "none".into(),
flag_jsonrpc_threads: None,
// IPC // IPC
flag_no_ipc: false, flag_no_ipc: false,
@ -679,15 +693,8 @@ mod tests {
flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(), flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(),
// DAPPS // DAPPS
flag_no_dapps: false,
flag_dapps_port: 8080u16,
flag_dapps_interface: "local".into(),
flag_dapps_hosts: "none".into(),
flag_dapps_cors: None,
flag_dapps_path: "$HOME/.parity/dapps".into(), flag_dapps_path: "$HOME/.parity/dapps".into(),
flag_dapps_user: Some("test_user".into()), flag_no_dapps: false,
flag_dapps_pass: Some("test_pass".into()),
flag_dapps_apis_all: false,
flag_no_secretstore: false, flag_no_secretstore: false,
flag_secretstore_port: 8082u16, flag_secretstore_port: 8082u16,
@ -792,6 +799,14 @@ mod tests {
flag_extradata: None, flag_extradata: None,
flag_cache: None, flag_cache: None,
flag_warp: Some(true), flag_warp: Some(true),
// Legacy-Dapps
flag_dapps_port: Some(8080),
flag_dapps_interface: Some("local".into()),
flag_dapps_hosts: Some("none".into()),
flag_dapps_cors: None,
flag_dapps_user: Some("test_user".into()),
flag_dapps_pass: Some("test_pass".into()),
flag_dapps_apis_all: None,
// -- Miscellaneous Options // -- Miscellaneous Options
flag_version: false, flag_version: false,
@ -836,6 +851,7 @@ mod tests {
db_path: None, db_path: None,
keys_path: None, keys_path: None,
identity: None, identity: None,
light: None,
}), }),
account: Some(Account { account: Some(Account {
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]), unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
@ -865,6 +881,7 @@ mod tests {
node_key: None, node_key: None,
reserved_peers: Some("./path/to/reserved_peers".into()), reserved_peers: Some("./path/to/reserved_peers".into()),
reserved_only: Some(true), reserved_only: Some(true),
no_serve_light: None,
}), }),
rpc: Some(Rpc { rpc: Some(Rpc {
disable: Some(true), disable: Some(true),
@ -873,6 +890,7 @@ mod tests {
cors: None, cors: None,
apis: None, apis: None,
hosts: None, hosts: None,
threads: None,
}), }),
ipc: Some(Ipc { ipc: Some(Ipc {
disable: None, disable: None,

View File

@ -70,6 +70,11 @@ Operating Options:
--keys-path PATH Specify the path for JSON key files to be found --keys-path PATH Specify the path for JSON key files to be found
(default: {flag_keys_path}). (default: {flag_keys_path}).
--identity NAME Specify your node's name. (default: {flag_identity}) --identity NAME Specify your node's name. (default: {flag_identity})
--light Experimental: run in light client mode. Light clients
synchronize a bare minimum of data and fetch necessary
data on-demand from the network. Much lower in storage,
potentially higher in bandwidth. Has no effect with
subcommands (default: {flag_light}).
Account Options: Account Options:
--unlock ACCOUNTS Unlock ACCOUNTS for the duration of the execution. --unlock ACCOUNTS Unlock ACCOUNTS for the duration of the execution.
@ -129,6 +134,7 @@ Networking Options:
--max-pending-peers NUM Allow up to NUM pending connections. (default: {flag_max_pending_peers}) --max-pending-peers NUM Allow up to NUM pending connections. (default: {flag_max_pending_peers})
--no-ancient-blocks Disable downloading old blocks after snapshot restoration --no-ancient-blocks Disable downloading old blocks after snapshot restoration
or warp sync. (default: {flag_no_ancient_blocks}) or warp sync. (default: {flag_no_ancient_blocks})
--no-serve-light Disable serving of light peers. (default: {flag_no_serve_light})
API and Console Options: API and Console Options:
--no-jsonrpc Disable the JSON-RPC API server. (default: {flag_no_jsonrpc}) --no-jsonrpc Disable the JSON-RPC API server. (default: {flag_no_jsonrpc})
@ -149,6 +155,8 @@ 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.
Requires Dapps server to be disabled using --no-dapps. (default: {flag_jsonrpc_threads:?})
--no-ipc Disable JSON-RPC over IPC service. (default: {flag_no_ipc}) --no-ipc Disable JSON-RPC over IPC service. (default: {flag_no_ipc})
--ipc-path PATH Specify custom path for JSON-RPC over IPC service --ipc-path PATH Specify custom path for JSON-RPC over IPC service
@ -157,29 +165,8 @@ API and Console Options:
IPC (default: {flag_ipc_apis}). IPC (default: {flag_ipc_apis}).
--no-dapps Disable the Dapps server (e.g. status page). (default: {flag_no_dapps}) --no-dapps Disable the Dapps server (e.g. status page). (default: {flag_no_dapps})
--dapps-port PORT Specify the port portion of the Dapps server
(default: {flag_dapps_port}).
--dapps-interface IP Specify the hostname portion of the Dapps
server, IP should be an interface's IP address,
or local (default: {flag_dapps_interface}).
--dapps-hosts HOSTS List of allowed Host header values. This option will
validate the Host header sent by the browser, it
is additional security against some attack
vectors. Special options: "all", "none",
(default: {flag_dapps_hosts}).
--dapps-cors URL Specify CORS headers for Dapps server APIs.
(default: {flag_dapps_cors:?})
--dapps-user USERNAME Specify username for Dapps server. It will be
used in HTTP Basic Authentication Scheme.
If --dapps-pass is not specified you will be
asked for password on startup. (default: {flag_dapps_user:?})
--dapps-pass PASSWORD Specify password for Dapps server. Use only in
conjunction with --dapps-user. (default: {flag_dapps_pass:?})
--dapps-path PATH Specify directory where dapps should be installed. --dapps-path PATH Specify directory where dapps should be installed.
(default: {flag_dapps_path}) (default: {flag_dapps_path})
--dapps-apis-all Expose all possible RPC APIs on Dapps port.
WARNING: INSECURE. Used only for development.
(default: {flag_dapps_apis_all})
--ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api}) --ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api})
--ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen.
(default: {flag_ipfs_api_port}) (default: {flag_ipfs_api_port})
@ -392,6 +379,13 @@ Legacy Options:
--jsonrpc-off Equivalent to --no-jsonrpc. --jsonrpc-off Equivalent to --no-jsonrpc.
-w --webapp Does nothing; dapps server is on by default now. -w --webapp Does nothing; dapps server is on by default now.
--dapps-off Equivalent to --no-dapps. --dapps-off Equivalent to --no-dapps.
--dapps-user USERNAME Dapps server authentication has been removed. (default: {flag_dapps_user:?})
--dapps-pass PASSWORD Dapps server authentication has been removed. (default: {flag_dapps_pass:?})
--dapps-apis-all Dapps server is merged with RPC server. Use --jsonrpc-apis. (default: {flag_dapps_apis_all:?})
--dapps-cors URL Dapps server is merged with RPC server. Use --jsonrpc-cors. (default: {flag_dapps_cors:?})
--dapps-hosts HOSTS Dapps server is merged with RPC server. Use --jsonrpc-hosts. (default: {flag_dapps_hosts:?})
--dapps-interface IP Dapps server is merged with RPC server. Use --jsonrpc-interface. (default: {flag_dapps_interface:?})
--dapps-port PORT Dapps server is merged with RPC server. Use --jsonrpc-port. (default: {flag_dapps_port:?})
--rpc Does nothing; JSON-RPC is on by default now. --rpc Does nothing; JSON-RPC is on by default now.
--warp Does nothing; Warp sync is on by default. (default: {flag_warp}) --warp Does nothing; Warp sync is on by default. (default: {flag_warp})
--rpcaddr IP Equivalent to --jsonrpc-interface IP. --rpcaddr IP Equivalent to --jsonrpc-interface IP.

View File

@ -132,12 +132,17 @@ impl Configuration {
let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive); let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive);
let geth_compatibility = self.args.flag_geth; let geth_compatibility = self.args.flag_geth;
let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let ui_address = self.ui_port().map(|port| (self.ui_interface(), port));
let dapps_conf = self.dapps_config(); let mut dapps_conf = self.dapps_config();
let ipfs_conf = self.ipfs_config(); let ipfs_conf = self.ipfs_config();
let signer_conf = self.signer_config(); let signer_conf = self.signer_config();
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 {
dapps_conf.enabled = false;
writeln!(&mut stderr(), "Warning: Disabling Dapps server because fast RPC server was enabled.").expect("Error writing to stderr.")
}
let cmd = if self.args.flag_version { let cmd = if self.args.flag_version {
Cmd::Version Cmd::Version
} else if self.args.cmd_signer { } else if self.args.cmd_signer {
@ -377,6 +382,8 @@ impl Configuration {
check_seal: !self.args.flag_no_seal_check, check_seal: !self.args.flag_no_seal_check,
download_old_blocks: !self.args.flag_no_ancient_blocks, download_old_blocks: !self.args.flag_no_ancient_blocks,
verifier_settings: verifier_settings, verifier_settings: verifier_settings,
serve_light: !self.args.flag_no_serve_light,
light: self.args.flag_light,
}; };
Cmd::Run(run_cmd) Cmd::Run(run_cmd)
}; };
@ -554,19 +561,12 @@ impl Configuration {
fn dapps_config(&self) -> DappsConfiguration { fn dapps_config(&self) -> DappsConfiguration {
DappsConfiguration { DappsConfiguration {
enabled: self.dapps_enabled(), enabled: self.dapps_enabled(),
interface: self.dapps_interface(),
port: self.args.flag_dapps_port,
hosts: self.dapps_hosts(),
cors: self.dapps_cors(),
user: self.args.flag_dapps_user.clone(),
pass: self.args.flag_dapps_pass.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![]
}, },
all_apis: self.args.flag_dapps_apis_all,
} }
} }
@ -746,14 +746,10 @@ impl Configuration {
Self::cors(self.args.flag_ipfs_api_cors.as_ref()) Self::cors(self.args.flag_ipfs_api_cors.as_ref())
} }
fn dapps_cors(&self) -> Option<Vec<String>> {
Self::cors(self.args.flag_dapps_cors.as_ref())
}
fn hosts(hosts: &str) -> Option<Vec<String>> { fn hosts(hosts: &str) -> Option<Vec<String>> {
match hosts { match hosts {
"none" => return Some(Vec::new()), "none" => return Some(Vec::new()),
"all" => return None, "*" | "all" | "any" => return None,
_ => {} _ => {}
} }
let hosts = hosts.split(',').map(Into::into).collect(); let hosts = hosts.split(',').map(Into::into).collect();
@ -764,10 +760,6 @@ impl Configuration {
Self::hosts(&self.args.flag_jsonrpc_hosts) Self::hosts(&self.args.flag_jsonrpc_hosts)
} }
fn dapps_hosts(&self) -> Option<Vec<String>> {
Self::hosts(&self.args.flag_dapps_hosts)
}
fn ipfs_hosts(&self) -> Option<Vec<String>> { fn ipfs_hosts(&self) -> Option<Vec<String>> {
Self::hosts(&self.args.flag_ipfs_api_hosts) Self::hosts(&self.args.flag_ipfs_api_hosts)
} }
@ -793,12 +785,17 @@ impl Configuration {
fn http_config(&self) -> Result<HttpConfiguration, String> { fn http_config(&self) -> Result<HttpConfiguration, String> {
let conf = HttpConfiguration { let conf = HttpConfiguration {
enabled: !self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc, enabled: self.rpc_enabled(),
interface: self.rpc_interface(), interface: self.rpc_interface(),
port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port), port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port),
apis: self.rpc_apis().parse()?, apis: self.rpc_apis().parse()?,
hosts: self.rpc_hosts(), hosts: self.rpc_hosts(),
cors: self.rpc_cors(), cors: self.rpc_cors(),
threads: match self.args.flag_jsonrpc_threads {
Some(threads) if threads > 0 => Some(threads),
None => None,
_ => return Err("--jsonrpc-threads number needs to be positive.".into()),
}
}; };
Ok(conf) Ok(conf)
@ -809,7 +806,7 @@ impl Configuration {
name: self.args.flag_identity.clone(), name: self.args.flag_identity.clone(),
chain: self.chain(), chain: self.chain(),
network_port: self.args.flag_port, network_port: self.args.flag_port,
rpc_enabled: !self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc, rpc_enabled: self.rpc_enabled(),
rpc_interface: self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone()), rpc_interface: self.args.flag_rpcaddr.clone().unwrap_or(self.args.flag_jsonrpc_interface.clone()),
rpc_port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port), rpc_port: self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port),
} }
@ -916,13 +913,6 @@ impl Configuration {
Self::interface(&self.network_settings().rpc_interface) Self::interface(&self.network_settings().rpc_interface)
} }
fn dapps_interface(&self) -> String {
match self.args.flag_dapps_interface.as_str() {
"local" => "127.0.0.1",
x => x,
}.into()
}
fn ipfs_interface(&self) -> String { fn ipfs_interface(&self) -> String {
Self::interface(&self.args.flag_ipfs_api_interface) Self::interface(&self.args.flag_ipfs_api_interface)
} }
@ -938,8 +928,12 @@ impl Configuration {
Self::interface(&self.args.flag_stratum_interface) Self::interface(&self.args.flag_stratum_interface)
} }
fn rpc_enabled(&self) -> bool {
!self.args.flag_jsonrpc_off && !self.args.flag_no_jsonrpc
}
fn dapps_enabled(&self) -> bool { fn dapps_enabled(&self) -> bool {
!self.args.flag_dapps_off && !self.args.flag_no_dapps && cfg!(feature = "dapps") !self.args.flag_dapps_off && !self.args.flag_no_dapps && self.rpc_enabled() && cfg!(feature = "dapps")
} }
fn secretstore_enabled(&self) -> bool { fn secretstore_enabled(&self) -> bool {
@ -1209,6 +1203,8 @@ mod tests {
check_seal: true, check_seal: true,
download_old_blocks: true, download_old_blocks: true,
verifier_settings: Default::default(), verifier_settings: Default::default(),
serve_light: true,
light: false,
}; };
expected.secretstore_conf.enabled = cfg!(feature = "secretstore"); expected.secretstore_conf.enabled = cfg!(feature = "secretstore");
assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected)); assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected));
@ -1317,23 +1313,6 @@ mod tests {
assert_eq!(conf3.rpc_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); assert_eq!(conf3.rpc_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()]));
} }
#[test]
fn should_parse_dapps_hosts() {
// given
// when
let conf0 = parse(&["parity"]);
let conf1 = parse(&["parity", "--dapps-hosts", "none"]);
let conf2 = parse(&["parity", "--dapps-hosts", "all"]);
let conf3 = parse(&["parity", "--dapps-hosts", "ethcore.io,something.io"]);
// then
assert_eq!(conf0.dapps_hosts(), Some(Vec::new()));
assert_eq!(conf1.dapps_hosts(), Some(Vec::new()));
assert_eq!(conf2.dapps_hosts(), None);
assert_eq!(conf3.dapps_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()]));
}
#[test] #[test]
fn should_parse_ipfs_hosts() { fn should_parse_ipfs_hosts() {
// given // given

View File

@ -18,26 +18,20 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use dir::default_data_path; use dir::default_data_path;
use ethcore::client::Client; use ethcore::client::{Client, BlockChainClient, BlockId};
use ethcore_rpc::informant::RpcStats; use ethcore::transaction::{Transaction, Action};
use ethsync::SyncProvider;
use hash_fetch::fetch::Client as FetchClient; use hash_fetch::fetch::Client as FetchClient;
use hash_fetch::urlhint::ContractClient;
use helpers::replace_home; use helpers::replace_home;
use rpc_apis::{self, SignerService}; use rpc_apis::SignerService;
use parity_reactor; use parity_reactor;
use util::{Bytes, Address, U256};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Configuration { pub struct Configuration {
pub enabled: bool, pub enabled: bool,
pub interface: String,
pub port: u16,
pub hosts: Option<Vec<String>>,
pub cors: Option<Vec<String>>,
pub user: Option<String>,
pub pass: Option<String>,
pub dapps_path: PathBuf, pub dapps_path: PathBuf,
pub extra_dapps: Vec<PathBuf>, pub extra_dapps: Vec<PathBuf>,
pub all_apis: bool,
} }
impl Default for Configuration { impl Default for Configuration {
@ -45,80 +39,94 @@ impl Default for Configuration {
let data_dir = default_data_path(); let data_dir = default_data_path();
Configuration { Configuration {
enabled: true, enabled: true,
interface: "127.0.0.1".into(),
port: 8080,
hosts: Some(Vec::new()),
cors: None,
user: None,
pass: None,
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
extra_dapps: vec![], extra_dapps: vec![],
all_apis: false,
} }
} }
} }
pub struct Dependencies { /// Registrar implementation of the full client.
pub apis: Arc<rpc_apis::Dependencies>, pub struct FullRegistrar {
/// Handle to the full client.
pub client: Arc<Client>, pub client: Arc<Client>,
pub sync: Arc<SyncProvider>, }
impl ContractClient for FullRegistrar {
fn registrar(&self) -> Result<Address, String> {
self.client.additional_params().get("registrar")
.ok_or_else(|| "Registrar not defined.".into())
.and_then(|registrar| {
registrar.parse().map_err(|e| format!("Invalid registrar address: {:?}", e))
})
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
let from = Address::default();
let transaction = Transaction {
nonce: self.client.latest_nonce(&from),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
value: U256::default(),
data: data,
}.fake_sign(from);
self.client.call(&transaction, BlockId::Latest, Default::default())
.map_err(|e| format!("{:?}", e))
.map(|executed| {
executed.output
})
}
}
// TODO: light client implementation forwarding to OnDemand and waiting for future
// to resolve.
pub struct Dependencies {
pub sync_status: Arc<::parity_dapps::SyncStatus>,
pub contract_client: Arc<ContractClient>,
pub remote: parity_reactor::TokioRemote, pub remote: parity_reactor::TokioRemote,
pub fetch: FetchClient, pub fetch: FetchClient,
pub signer: Arc<SignerService>, pub signer: Arc<SignerService>,
pub stats: Arc<RpcStats>,
} }
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> { pub fn new(configuration: Configuration, deps: Dependencies)
-> Result<Option<Middleware>, String>
{
if !configuration.enabled { if !configuration.enabled {
return Ok(None); return Ok(None);
} }
let url = format!("{}:{}", configuration.interface, configuration.port); dapps_middleware(
let addr = url.parse().map_err(|_| format!("Invalid Webapps listen host/port given: {}", url))?;
let auth = configuration.user.as_ref().map(|username| {
let password = configuration.pass.as_ref().map_or_else(|| {
use rpassword::read_password;
println!("Type password for WebApps server (user: {}): ", username);
let pass = read_password().unwrap();
println!("OK, got it. Starting server...");
pass
}, |pass| pass.to_owned());
(username.to_owned(), password)
});
Ok(Some(setup_dapps_server(
deps, deps,
configuration.dapps_path, configuration.dapps_path,
configuration.extra_dapps, configuration.extra_dapps,
&addr, ).map(Some)
configuration.hosts,
configuration.cors,
auth,
configuration.all_apis,
)?))
} }
pub use self::server::WebappServer; pub use self::server::Middleware;
pub use self::server::setup_dapps_server; pub use self::server::dapps_middleware;
#[cfg(not(feature = "dapps"))] #[cfg(not(feature = "dapps"))]
mod server { mod server {
use super::Dependencies; use super::Dependencies;
use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use ethcore_rpc::{hyper, RequestMiddleware, RequestMiddlewareAction};
pub struct WebappServer; pub struct Middleware;
pub fn setup_dapps_server(
impl RequestMiddleware for Middleware {
fn on_request(
&self, req: &hyper::server::Request<hyper::net::HttpStream>, control: &hyper::Control
) -> RequestMiddlewareAction {
unreachable!()
}
}
pub fn dapps_middleware(
_deps: Dependencies, _deps: Dependencies,
_dapps_path: PathBuf, _dapps_path: PathBuf,
_extra_dapps: Vec<PathBuf>, _extra_dapps: Vec<PathBuf>,
_url: &SocketAddr, ) -> Result<Middleware, String> {
_allowed_hosts: Option<Vec<String>>,
_cors: Option<Vec<String>>,
_auth: Option<(String, String)>,
_all_apis: bool,
) -> Result<WebappServer, String> {
Err("Your Parity version has been compiled without WebApps support.".into()) Err("Your Parity version has been compiled without WebApps support.".into())
} }
} }
@ -128,109 +136,31 @@ mod server {
use super::Dependencies; use super::Dependencies;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::net::SocketAddr;
use std::io;
use util::{Bytes, Address, U256};
use ansi_term::Colour; use hash_fetch::fetch::Client as FetchClient;
use ethcore::transaction::{Transaction, Action}; use parity_dapps;
use ethcore::client::{Client, BlockChainClient, BlockId};
use ethcore_dapps::{AccessControlAllowOrigin, Host};
use ethcore_rpc::is_major_importing;
use hash_fetch::urlhint::ContractClient;
use parity_reactor; use parity_reactor;
use rpc_apis;
pub use ethcore_dapps::Server as WebappServer; pub type Middleware = parity_dapps::Middleware<FetchClient>;
pub fn setup_dapps_server( pub fn dapps_middleware(
deps: Dependencies, deps: Dependencies,
dapps_path: PathBuf, dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>, extra_dapps: Vec<PathBuf>,
url: &SocketAddr, ) -> Result<Middleware, String> {
allowed_hosts: Option<Vec<String>>,
cors: Option<Vec<String>>,
auth: Option<(String, String)>,
all_apis: bool,
) -> Result<WebappServer, String> {
use ethcore_dapps as dapps;
let server = dapps::ServerBuilder::new(
&dapps_path,
Arc::new(Registrar { client: deps.client.clone() }),
parity_reactor::Remote::new(deps.remote.clone()),
);
let allowed_hosts: Option<Vec<_>> = allowed_hosts.map(|hosts| hosts.into_iter().map(Host::from).collect());
let cors: Option<Vec<_>> = cors.map(|cors| cors.into_iter().map(AccessControlAllowOrigin::from).collect());
let sync = deps.sync.clone();
let client = deps.client.clone();
let signer = deps.signer.clone(); let signer = deps.signer.clone();
let server = server let parity_remote = parity_reactor::Remote::new(deps.remote.clone());
.fetch(deps.fetch.clone()) let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token));
.sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())))
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
.extra_dapps(&extra_dapps)
.signer_address(deps.signer.address())
.allowed_hosts(allowed_hosts.into())
.extra_cors_headers(cors.into());
let api_set = if all_apis { Ok(parity_dapps::Middleware::new(
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed.")); parity_remote,
info!("If you do not intend this, exit now."); deps.signer.address(),
rpc_apis::ApiSet::SafeContext dapps_path,
} else { extra_dapps,
rpc_apis::ApiSet::UnsafeContext deps.contract_client,
}; deps.sync_status,
let apis = rpc_apis::setup_rpc(deps.stats, deps.apis.clone(), api_set); web_proxy_tokens,
let start_result = match auth { deps.fetch.clone(),
None => { ))
server.start_unsecured_http(url, apis, deps.remote)
},
Some((username, password)) => {
server.start_basic_auth_http(url, &username, &password, apis, deps.remote)
},
};
match start_result {
Err(dapps::ServerError::IoError(err)) => match err.kind() {
io::ErrorKind::AddrInUse => Err(format!("WebApps address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --dapps-port and --dapps-interface options.", url)),
_ => Err(format!("WebApps io error: {}", err)),
},
Err(e) => Err(format!("WebApps error: {:?}", e)),
Ok(server) => Ok(server),
}
}
struct Registrar {
client: Arc<Client>,
}
impl ContractClient for Registrar {
fn registrar(&self) -> Result<Address, String> {
self.client.additional_params().get("registrar")
.ok_or_else(|| "Registrar not defined.".into())
.and_then(|registrar| {
registrar.parse().map_err(|e| format!("Invalid registrar address: {:?}", e))
})
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
let from = Address::default();
let transaction = Transaction {
nonce: self.client.latest_nonce(&from),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
value: U256::default(),
data: data,
}.fake_sign(from);
self.client.call(&transaction, BlockId::Latest, Default::default())
.map_err(|e| format!("{:?}", e))
.map(|executed| {
executed.output
})
}
} }
} }

View File

@ -21,94 +21,89 @@ use cli::Args;
pub enum Deprecated { pub enum Deprecated {
DoesNothing(&'static str), DoesNothing(&'static str),
Replaced(&'static str, &'static str), Replaced(&'static str, &'static str),
Removed(&'static str),
} }
impl fmt::Display for Deprecated { impl fmt::Display for Deprecated {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
Deprecated::DoesNothing(s) => write!(f, "Option '{}' does nothing. It's on by default", s), Deprecated::DoesNothing(s) => write!(f, "Option '{}' does nothing. It's on by default.", s),
Deprecated::Replaced(old, new) => write!(f, "Option '{}' is deprecated. Please use '{}' instead", old, new), Deprecated::Replaced(old, new) => write!(f, "Option '{}' is deprecated. Please use '{}' instead.", old, new),
Deprecated::Removed(s) => write!(f, "Option '{}' has been removed and is no longer supported.", s)
} }
} }
} }
impl Deprecated {
fn jsonrpc() -> Self {
Deprecated::DoesNothing("--jsonrpc")
}
fn rpc() -> Self {
Deprecated::DoesNothing("--rpc")
}
fn jsonrpc_off() -> Self {
Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc")
}
fn webapp() -> Self {
Deprecated::DoesNothing("--webapp")
}
fn dapps_off() -> Self {
Deprecated::Replaced("--dapps-off", "--no-dapps")
}
fn ipcdisable() -> Self {
Deprecated::Replaced("--ipcdisable", "--no-ipc")
}
fn ipc_off() -> Self {
Deprecated::Replaced("--ipc-off", "--no-ipc")
}
fn etherbase() -> Self {
Deprecated::Replaced("--etherbase", "--author")
}
fn extradata() -> Self {
Deprecated::Replaced("--extradata", "--extra-data")
}
}
pub fn find_deprecated(args: &Args) -> Vec<Deprecated> { pub fn find_deprecated(args: &Args) -> Vec<Deprecated> {
let mut result = vec![]; let mut result = vec![];
if args.flag_jsonrpc { if args.flag_jsonrpc {
result.push(Deprecated::jsonrpc()); result.push(Deprecated::DoesNothing("--jsonrpc"));
} }
if args.flag_rpc { if args.flag_rpc {
result.push(Deprecated::rpc()); result.push(Deprecated::DoesNothing("--rpc"));
} }
if args.flag_jsonrpc_off { if args.flag_jsonrpc_off {
result.push(Deprecated::jsonrpc_off()); result.push(Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc"));
} }
if args.flag_webapp { if args.flag_webapp {
result.push(Deprecated::webapp()) result.push(Deprecated::DoesNothing("--webapp"));
} }
if args.flag_dapps_off { if args.flag_dapps_off {
result.push(Deprecated::dapps_off()); result.push(Deprecated::Replaced("--dapps-off", "--no-dapps"));
} }
if args.flag_ipcdisable { if args.flag_ipcdisable {
result.push(Deprecated::ipcdisable()); result.push(Deprecated::Replaced("--ipcdisable", "--no-ipc"));
} }
if args.flag_ipc_off { if args.flag_ipc_off {
result.push(Deprecated::ipc_off()); result.push(Deprecated::Replaced("--ipc-off", "--no-ipc"));
} }
if args.flag_etherbase.is_some() { if args.flag_etherbase.is_some() {
result.push(Deprecated::etherbase()); result.push(Deprecated::Replaced("--etherbase", "--author"));
} }
if args.flag_extradata.is_some() { if args.flag_extradata.is_some() {
result.push(Deprecated::extradata()); result.push(Deprecated::Replaced("--extradata", "--extra-data"));
} }
// Removed in 1.7
if args.flag_dapps_port.is_some() {
result.push(Deprecated::Replaced("--dapps-port", "--jsonrpc-port"));
}
if args.flag_dapps_interface.is_some() {
result.push(Deprecated::Replaced("--dapps-interface", "--jsonrpc-interface"));
}
if args.flag_dapps_hosts.is_some() {
result.push(Deprecated::Replaced("--dapps-hosts", "--jsonrpc-hosts"));
}
if args.flag_dapps_cors.is_some() {
result.push(Deprecated::Replaced("--dapps-cors", "--jsonrpc-cors"));
}
if args.flag_dapps_user.is_some() {
result.push(Deprecated::Removed("--dapps-user"));
}
if args.flag_dapps_pass.is_some() {
result.push(Deprecated::Removed("--dapps-pass"));
}
if args.flag_dapps_apis_all.is_some() {
result.push(Deprecated::Replaced("--dapps-apis-all", "--jsonrpc-apis"));
}
// Removed in 1.8
result result
} }
@ -131,17 +126,31 @@ mod tests {
args.flag_ipc_off = true; args.flag_ipc_off = true;
args.flag_etherbase = Some(Default::default()); args.flag_etherbase = Some(Default::default());
args.flag_extradata = Some(Default::default()); args.flag_extradata = Some(Default::default());
args.flag_dapps_port = Some(Default::default());
args.flag_dapps_interface = Some(Default::default());
args.flag_dapps_hosts = Some(Default::default());
args.flag_dapps_cors = Some(Default::default());
args.flag_dapps_user = Some(Default::default());
args.flag_dapps_pass = Some(Default::default());
args.flag_dapps_apis_all = Some(Default::default());
args args
}), vec![ }), vec![
Deprecated::jsonrpc(), Deprecated::DoesNothing("--jsonrpc"),
Deprecated::rpc(), Deprecated::DoesNothing("--rpc"),
Deprecated::jsonrpc_off(), Deprecated::Replaced("--jsonrpc-off", "--no-jsonrpc"),
Deprecated::webapp(), Deprecated::DoesNothing("--webapp"),
Deprecated::dapps_off(), Deprecated::Replaced("--dapps-off", "--no-dapps"),
Deprecated::ipcdisable(), Deprecated::Replaced("--ipcdisable", "--no-ipc"),
Deprecated::ipc_off(), Deprecated::Replaced("--ipc-off", "--no-ipc"),
Deprecated::etherbase(), Deprecated::Replaced("--etherbase", "--author"),
Deprecated::extradata(), Deprecated::Replaced("--extradata", "--extra-data"),
Deprecated::Replaced("--dapps-port", "--jsonrpc-port"),
Deprecated::Replaced("--dapps-interface", "--jsonrpc-interface"),
Deprecated::Replaced("--dapps-hosts", "--jsonrpc-hosts"),
Deprecated::Replaced("--dapps-cors", "--jsonrpc-cors"),
Deprecated::Removed("--dapps-user"),
Deprecated::Removed("--dapps-pass"),
Deprecated::Replaced("--dapps-apis-all", "--jsonrpc-apis"),
]); ]);
} }
} }

View File

@ -15,10 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc; use std::sync::Arc;
use parity_ipfs_api::{self, AccessControlAllowOrigin, Host}; use parity_ipfs_api::{self, AccessControlAllowOrigin, Host, Listening};
use parity_ipfs_api::error::ServerError; use parity_ipfs_api::error::ServerError;
use ethcore::client::BlockChainClient; use ethcore::client::BlockChainClient;
use hyper::server::Listening;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Configuration { pub struct Configuration {

View File

@ -0,0 +1,21 @@
// 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/>.
//! Utilities and helpers for the light client.
mod queue_cull;
pub use self::queue_cull::QueueCull;

View File

@ -0,0 +1,99 @@
// 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/>.
//! Service for culling the light client's transaction queue.
use std::sync::Arc;
use std::time::Duration;
use ethcore::service::ClientIoMessage;
use ethsync::LightSync;
use io::{IoContext, IoHandler, TimerToken};
use light::client::Client;
use light::on_demand::{request, OnDemand};
use light::TransactionQueue;
use futures::{future, stream, Future, Stream};
use parity_reactor::Remote;
use util::RwLock;
// Attepmt to cull once every 10 minutes.
const TOKEN: TimerToken = 1;
const TIMEOUT_MS: u64 = 1000 * 60 * 10;
// But make each attempt last only 9 minutes
const PURGE_TIMEOUT_MS: u64 = 1000 * 60 * 9;
/// Periodically culls the transaction queue of mined transactions.
pub struct QueueCull {
/// A handle to the client, for getting the latest block header.
pub client: Arc<Client>,
/// A handle to the sync service.
pub sync: Arc<LightSync>,
/// The on-demand request service.
pub on_demand: Arc<OnDemand>,
/// The transaction queue.
pub txq: Arc<RwLock<TransactionQueue>>,
/// Event loop remote.
pub remote: Remote,
}
impl IoHandler<ClientIoMessage> for QueueCull {
fn initialize(&self, io: &IoContext<ClientIoMessage>) {
io.register_timer(TOKEN, TIMEOUT_MS).expect("Error registering timer");
}
fn timeout(&self, _io: &IoContext<ClientIoMessage>, timer: TimerToken) {
if timer != TOKEN { return }
let senders = self.txq.read().queued_senders();
if senders.is_empty() { return }
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
let best_header = self.client.best_block_header();
let start_nonce = self.client.engine().account_start_nonce();
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
self.remote.spawn_with_timeout(move || {
let maybe_fetching = sync.with_context(move |ctx| {
// fetch the nonce of each sender in the queue.
let nonce_futures = senders.iter()
.map(|&address| request::Account { header: best_header.clone(), address: address })
.map(|request| on_demand.account(ctx, request))
.map(move |fut| fut.map(move |x| x.map(|acc| acc.nonce).unwrap_or(start_nonce)))
.zip(senders.iter())
.map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce)));
// as they come in, update each sender to the new nonce.
stream::futures_unordered(nonce_futures)
.fold(txq, |txq, (address, nonce)| {
txq.write().cull(address, nonce);
future::ok(txq)
})
.map(|_| ()) // finally, discard the txq handle and log errors.
.map_err(|_| debug!(target: "cull", "OnDemand prematurely closed channel."))
});
match maybe_fetching {
Some(fut) => fut.boxed(),
None => future::ok(()).boxed(),
}
}, Duration::from_millis(PURGE_TIMEOUT_MS), || {})
}
}

View File

@ -28,7 +28,7 @@ extern crate ctrlc;
extern crate docopt; extern crate docopt;
extern crate env_logger; extern crate env_logger;
extern crate fdlimit; extern crate fdlimit;
extern crate hyper; extern crate futures;
extern crate isatty; extern crate isatty;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate num_cpus; extern crate num_cpus;
@ -54,6 +54,7 @@ extern crate ethcore_logger;
extern crate ethcore_rpc; extern crate ethcore_rpc;
extern crate ethcore_signer; extern crate ethcore_signer;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate ethkey;
extern crate ethsync; extern crate ethsync;
extern crate parity_hash_fetch as hash_fetch; extern crate parity_hash_fetch as hash_fetch;
extern crate parity_ipfs_api; extern crate parity_ipfs_api;
@ -73,7 +74,11 @@ extern crate ethcore_stratum;
extern crate ethcore_secretstore; extern crate ethcore_secretstore;
#[cfg(feature = "dapps")] #[cfg(feature = "dapps")]
extern crate ethcore_dapps; extern crate parity_dapps;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
#[cfg(windows)] extern crate ws2_32; #[cfg(windows)] extern crate ws2_32;
#[cfg(windows)] extern crate winapi; #[cfg(windows)] extern crate winapi;
@ -101,6 +106,7 @@ mod deprecated;
mod dir; mod dir;
mod helpers; mod helpers;
mod informant; mod informant;
mod light_helpers;
mod migration; mod migration;
mod modules; mod modules;
mod params; mod params;

View File

@ -30,7 +30,7 @@ use ethcore::migrations::Extract;
/// Database is assumed to be at default version, when no version file is found. /// Database is assumed to be at default version, when no version file is found.
const DEFAULT_VERSION: u32 = 5; const DEFAULT_VERSION: u32 = 5;
/// Current version of database models. /// Current version of database models.
const CURRENT_VERSION: u32 = 11; const CURRENT_VERSION: u32 = 12;
/// First version of the consolidated database. /// First version of the consolidated database.
const CONSOLIDATION_VERSION: u32 = 9; const CONSOLIDATION_VERSION: u32 = 9;
/// Defines how many items are migrated to the new version of database at once. /// Defines how many items are migrated to the new version of database at once.
@ -147,6 +147,7 @@ fn consolidated_database_migrations(compaction_profile: &CompactionProfile) -> R
let mut manager = MigrationManager::new(default_migration_settings(compaction_profile)); let mut manager = MigrationManager::new(default_migration_settings(compaction_profile));
manager.add_migration(migrations::ToV10::new()).map_err(|_| Error::MigrationImpossible)?; manager.add_migration(migrations::ToV10::new()).map_err(|_| Error::MigrationImpossible)?;
manager.add_migration(migrations::TO_V11).map_err(|_| Error::MigrationImpossible)?; manager.add_migration(migrations::TO_V11).map_err(|_| Error::MigrationImpossible)?;
manager.add_migration(migrations::TO_V12).map_err(|_| Error::MigrationImpossible)?;
Ok(manager) Ok(manager)
} }

View File

@ -14,24 +14,21 @@
// 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/>.
use std::fmt; use std::{io, fmt};
use std::sync::Arc; use std::sync::Arc;
use std::net::SocketAddr;
use std::io;
use dapps;
use dir::default_data_path; use dir::default_data_path;
use ethcore_rpc::{self as rpc, HttpServerError, Metadata, Origin, AccessControlAllowOrigin, Host};
use ethcore_rpc::informant::{RpcStats, Middleware}; use ethcore_rpc::informant::{RpcStats, Middleware};
use ethcore_rpc::{self as rpc, HttpServerError, Metadata, Origin, AccessControlAllowOrigin, Host};
use helpers::parity_ipc_path; use helpers::parity_ipc_path;
use hyper;
use jsonrpc_core::MetaIoHandler; use jsonrpc_core::MetaIoHandler;
use rpc_apis;
use rpc_apis::ApiSet;
use parity_reactor::TokioRemote; use parity_reactor::TokioRemote;
use rpc_apis::{self, ApiSet};
pub use ethcore_rpc::{IpcServer, HttpServer}; pub use ethcore_rpc::{IpcServer, HttpServer, RequestMiddleware};
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct HttpConfiguration { pub struct HttpConfiguration {
pub enabled: bool, pub enabled: bool,
pub interface: String, pub interface: String,
@ -39,6 +36,7 @@ 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>,
} }
impl Default for HttpConfiguration { impl Default for HttpConfiguration {
@ -50,6 +48,7 @@ impl Default for HttpConfiguration {
apis: ApiSet::UnsafeContext, apis: ApiSet::UnsafeContext,
cors: None, cors: None,
hosts: Some(Vec::new()), hosts: Some(Vec::new()),
threads: None,
} }
} }
} }
@ -82,20 +81,24 @@ impl fmt::Display for IpcConfiguration {
} }
} }
pub struct Dependencies { pub struct Dependencies<D: rpc_apis::Dependencies> {
pub apis: Arc<rpc_apis::Dependencies>, pub apis: Arc<D>,
pub remote: TokioRemote, pub remote: TokioRemote,
pub stats: Arc<RpcStats>, pub stats: Arc<RpcStats>,
} }
pub struct RpcExtractor; pub struct RpcExtractor;
impl rpc::HttpMetaExtractor<Metadata> for RpcExtractor { impl rpc::HttpMetaExtractor for RpcExtractor {
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> Metadata { type Metadata = Metadata;
let origin = req.headers().get::<hyper::header::Origin>()
.map(|origin| format!("{}://{}", origin.scheme, origin.host)) fn read_metadata(&self, origin: String, dapps_origin: Option<String>) -> Metadata {
.unwrap_or_else(|| "unknown".into());
let mut metadata = Metadata::default(); let mut metadata = Metadata::default();
metadata.origin = Origin::Rpc(origin);
metadata.origin = match (origin.as_str(), dapps_origin) {
("null", Some(dapp)) => Origin::Dapps(dapp.into()),
_ => Origin::Rpc(origin),
};
metadata metadata
} }
} }
@ -109,52 +112,101 @@ impl rpc::IpcMetaExtractor<Metadata> for RpcExtractor {
} }
} }
pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result<Option<HttpServer>, String> { fn setup_apis<D>(apis: ApiSet, deps: &Dependencies<D>) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
where D: rpc_apis::Dependencies
{
rpc_apis::setup_rpc(deps.stats.clone(), &*deps.apis, apis)
}
pub fn new_http<D: rpc_apis::Dependencies>(
conf: HttpConfiguration,
deps: &Dependencies<D>,
middleware: Option<dapps::Middleware>
) -> Result<Option<HttpServer>, String> {
if !conf.enabled { if !conf.enabled {
return Ok(None); return Ok(None);
} }
let url = format!("{}:{}", conf.interface, conf.port); let url = format!("{}:{}", conf.interface, conf.port);
let addr = url.parse().map_err(|_| format!("Invalid JSONRPC listen host/port given: {}", url))?; let addr = url.parse().map_err(|_| format!("Invalid JSONRPC listen host/port given: {}", url))?;
Ok(Some(setup_http_rpc_server(deps, &addr, conf.cors, conf.hosts, conf.apis)?)) let handler = setup_apis(conf.apis, deps);
} let remote = deps.remote.clone();
fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler<Metadata, Middleware> { let cors_domains: Option<Vec<_>> = conf.cors.map(|domains| domains.into_iter().map(AccessControlAllowOrigin::from).collect());
rpc_apis::setup_rpc(deps.stats.clone(), deps.apis.clone(), apis) let allowed_hosts: Option<Vec<_>> = conf.hosts.map(|hosts| hosts.into_iter().map(Host::from).collect());
}
let start_result = rpc::start_http(
&addr,
cors_domains.into(),
allowed_hosts.into(),
handler,
remote,
RpcExtractor,
match (conf.threads, middleware) {
(Some(threads), None) => rpc::HttpSettings::Threads(threads),
(None, middleware) => rpc::HttpSettings::Dapps(middleware),
(Some(_), Some(_)) => {
return Err("Dapps and fast multi-threaded RPC server cannot be enabled at the same time.".into())
},
}
);
pub fn setup_http_rpc_server(
dependencies: &Dependencies,
url: &SocketAddr,
cors_domains: Option<Vec<String>>,
allowed_hosts: Option<Vec<String>>,
apis: ApiSet
) -> Result<HttpServer, String> {
let handler = setup_apis(apis, dependencies);
let remote = dependencies.remote.clone();
let cors_domains: Option<Vec<_>> = cors_domains.map(|domains| domains.into_iter().map(AccessControlAllowOrigin::from).collect());
let allowed_hosts: Option<Vec<_>> = allowed_hosts.map(|hosts| hosts.into_iter().map(Host::from).collect());
let start_result = rpc::start_http(url, cors_domains.into(), allowed_hosts.into(), handler, remote, RpcExtractor);
match start_result { match start_result {
Err(HttpServerError::IoError(err)) => match err.kind() { Ok(server) => Ok(Some(server)),
io::ErrorKind::AddrInUse => Err(format!("RPC address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --jsonrpc-port and --jsonrpc-interface options.", url)), Err(HttpServerError::Io(err)) => match err.kind() {
io::ErrorKind::AddrInUse => Err(
format!("RPC address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --jsonrpc-port and --jsonrpc-interface options.", url)
),
_ => Err(format!("RPC io error: {}", err)), _ => Err(format!("RPC io error: {}", err)),
}, },
Err(e) => Err(format!("RPC error: {:?}", e)), Err(e) => Err(format!("RPC error: {:?}", e)),
Ok(server) => Ok(server),
} }
} }
pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Result<Option<IpcServer>, String> { pub fn new_ipc<D: rpc_apis::Dependencies>(
if !conf.enabled { return Ok(None); } conf: IpcConfiguration,
Ok(Some(setup_ipc_rpc_server(deps, &conf.socket_addr, conf.apis)?)) dependencies: &Dependencies<D>
} ) -> Result<Option<IpcServer>, String> {
if !conf.enabled {
pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: ApiSet) -> Result<IpcServer, String> { return Ok(None);
let handler = setup_apis(apis, dependencies); }
let handler = setup_apis(conf.apis, dependencies);
let remote = dependencies.remote.clone(); let remote = dependencies.remote.clone();
match rpc::start_ipc(addr, handler, remote, RpcExtractor) { match rpc::start_ipc(&conf.socket_addr, handler, remote, RpcExtractor) {
Ok(server) => Ok(Some(server)),
Err(io_error) => Err(format!("RPC io error: {}", io_error)), Err(io_error) => Err(format!("RPC io error: {}", io_error)),
Ok(server) => Ok(server) }
}
#[cfg(test)]
mod tests {
use super::RpcExtractor;
use ethcore_rpc::{HttpMetaExtractor, Origin};
#[test]
fn should_extract_rpc_origin() {
// given
let extractor = RpcExtractor;
// when
let meta = extractor.read_metadata("http://parity.io".into(), None);
let meta1 = extractor.read_metadata("http://parity.io".into(), Some("ignored".into()));
// then
assert_eq!(meta.origin, Origin::Rpc("http://parity.io".into()));
assert_eq!(meta1.origin, Origin::Rpc("http://parity.io".into()));
}
#[test]
fn should_dapps_origin() {
// given
let extractor = RpcExtractor;
let dapp = "https://wallet.ethereum.org".to_owned();
// when
let meta = extractor.read_metadata("null".into(), Some(dapp.clone()));
// then
assert_eq!(meta.origin, Origin::Dapps(dapp.into()));
} }
} }

View File

@ -27,12 +27,14 @@ use ethcore::client::Client;
use ethcore::miner::{Miner, ExternalMiner}; use ethcore::miner::{Miner, ExternalMiner};
use ethcore::snapshot::SnapshotService; use ethcore::snapshot::SnapshotService;
use ethcore_rpc::{Metadata, NetworkSettings}; use ethcore_rpc::{Metadata, NetworkSettings};
use ethcore_rpc::informant::{Middleware, RpcStats, ClientNotifier}; use ethcore_rpc::informant::{ActivityNotifier, Middleware, RpcStats, ClientNotifier};
use ethcore_rpc::dispatch::FullDispatcher; use ethcore_rpc::dispatch::{FullDispatcher, LightDispatcher};
use ethsync::{ManageNetwork, SyncProvider}; use ethsync::{ManageNetwork, SyncProvider, LightSync};
use hash_fetch::fetch::Client as FetchClient; use hash_fetch::fetch::Client as FetchClient;
use jsonrpc_core::{MetaIoHandler}; use jsonrpc_core::{MetaIoHandler};
use light::{TransactionQueue as LightTransactionQueue, Cache as LightDataCache};
use updater::Updater; use updater::Updater;
use util::{Mutex, RwLock};
use ethcore_logger::RotatingLogger; use ethcore_logger::RotatingLogger;
#[derive(Debug, PartialEq, Clone, Eq, Hash)] #[derive(Debug, PartialEq, Clone, Eq, Hash)]
@ -81,7 +83,7 @@ impl FromStr for Api {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum ApiSet { pub enum ApiSet {
SafeContext, SafeContext,
UnsafeContext, UnsafeContext,
@ -112,25 +114,6 @@ impl FromStr for ApiSet {
} }
} }
pub struct Dependencies {
pub signer_service: Arc<SignerService>,
pub client: Arc<Client>,
pub snapshot: Arc<SnapshotService>,
pub sync: Arc<SyncProvider>,
pub net: Arc<ManageNetwork>,
pub secret_store: Option<Arc<AccountProvider>>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub net_service: Arc<ManageNetwork>,
pub updater: Arc<Updater>,
pub geth_compatibility: bool,
pub dapps_interface: Option<String>,
pub dapps_port: Option<u16>,
pub fetch: FetchClient,
}
fn to_modules(apis: &[Api]) -> BTreeMap<String, String> { fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
let mut modules = BTreeMap::new(); let mut modules = BTreeMap::new();
for api in apis { for api in apis {
@ -151,6 +134,274 @@ fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
modules modules
} }
/// RPC dependencies can be used to initialize RPC endpoints from APIs.
pub trait Dependencies {
type Notifier: ActivityNotifier;
/// Create the activity notifier.
fn activity_notifier(&self) -> Self::Notifier;
/// Extend the given I/O handler with endpoints for each API.
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware<Self::Notifier>>, apis: &[Api]);
}
/// RPC dependencies for a full node.
pub struct FullDependencies {
pub signer_service: Arc<SignerService>,
pub client: Arc<Client>,
pub snapshot: Arc<SnapshotService>,
pub sync: Arc<SyncProvider>,
pub net: Arc<ManageNetwork>,
pub secret_store: Option<Arc<AccountProvider>>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub net_service: Arc<ManageNetwork>,
pub updater: Arc<Updater>,
pub geth_compatibility: bool,
pub dapps_interface: Option<String>,
pub dapps_port: Option<u16>,
pub fetch: FetchClient,
}
impl Dependencies for FullDependencies {
type Notifier = ClientNotifier;
fn activity_notifier(&self) -> ClientNotifier {
ClientNotifier {
client: self.client.clone(),
}
}
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware>, apis: &[Api]) {
use ethcore_rpc::v1::*;
macro_rules! add_signing_methods {
($namespace:ident, $handler:expr, $deps:expr) => {
{
let deps = &$deps;
let dispatcher = FullDispatcher::new(Arc::downgrade(&deps.client), Arc::downgrade(&deps.miner));
if deps.signer_service.is_enabled() {
$handler.extend_with($namespace::to_delegate(SigningQueueClient::new(&deps.signer_service, dispatcher, &deps.secret_store)))
} else {
$handler.extend_with($namespace::to_delegate(SigningUnsafeClient::new(&deps.secret_store, dispatcher)))
}
}
}
}
let dispatcher = FullDispatcher::new(Arc::downgrade(&self.client), Arc::downgrade(&self.miner));
for api in apis {
match *api {
Api::Web3 => {
handler.extend_with(Web3Client::new().to_delegate());
},
Api::Net => {
handler.extend_with(NetClient::new(&self.sync).to_delegate());
},
Api::Eth => {
let client = EthClient::new(
&self.client,
&self.snapshot,
&self.sync,
&self.secret_store,
&self.miner,
&self.external_miner,
EthClientOptions {
pending_nonce_from_queue: self.geth_compatibility,
allow_pending_receipt_query: !self.geth_compatibility,
send_block_number_in_get_work: !self.geth_compatibility,
}
);
handler.extend_with(client.to_delegate());
let filter_client = EthFilterClient::new(&self.client, &self.miner);
handler.extend_with(filter_client.to_delegate());
add_signing_methods!(EthSigning, handler, self);
},
Api::Personal => {
handler.extend_with(PersonalClient::new(&self.secret_store, dispatcher.clone(), self.geth_compatibility).to_delegate());
},
Api::Signer => {
handler.extend_with(SignerClient::new(&self.secret_store, dispatcher.clone(), &self.signer_service).to_delegate());
},
Api::Parity => {
let signer = match self.signer_service.is_enabled() {
true => Some(self.signer_service.clone()),
false => None,
};
handler.extend_with(ParityClient::new(
&self.client,
&self.miner,
&self.sync,
&self.updater,
&self.net_service,
&self.secret_store,
self.logger.clone(),
self.settings.clone(),
signer,
self.dapps_interface.clone(),
self.dapps_port,
).to_delegate());
add_signing_methods!(EthSigning, handler, self);
add_signing_methods!(ParitySigning, handler, self);
},
Api::ParityAccounts => {
handler.extend_with(ParityAccountsClient::new(&self.secret_store).to_delegate());
},
Api::ParitySet => {
handler.extend_with(ParitySetClient::new(
&self.client,
&self.miner,
&self.updater,
&self.net_service,
self.fetch.clone(),
).to_delegate())
},
Api::Traces => {
handler.extend_with(TracesClient::new(&self.client, &self.miner).to_delegate())
},
Api::Rpc => {
let modules = to_modules(&apis);
handler.extend_with(RpcClient::new(modules).to_delegate());
}
}
}
}
}
/// Light client notifier. Doesn't do anything yet, but might in the future.
pub struct LightClientNotifier;
impl ActivityNotifier for LightClientNotifier {
fn active(&self) {}
}
/// RPC dependencies for a light client.
pub struct LightDependencies {
pub signer_service: Arc<SignerService>,
pub client: Arc<::light::client::Client>,
pub sync: Arc<LightSync>,
pub net: Arc<ManageNetwork>,
pub secret_store: Arc<AccountProvider>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub on_demand: Arc<::light::on_demand::OnDemand>,
pub cache: Arc<Mutex<LightDataCache>>,
pub transaction_queue: Arc<RwLock<LightTransactionQueue>>,
pub dapps_interface: Option<String>,
pub dapps_port: Option<u16>,
pub fetch: FetchClient,
pub geth_compatibility: bool,
}
impl Dependencies for LightDependencies {
type Notifier = LightClientNotifier;
fn activity_notifier(&self) -> Self::Notifier { LightClientNotifier }
fn extend_with_set(&self, handler: &mut MetaIoHandler<Metadata, Middleware<Self::Notifier>>, apis: &[Api]) {
use ethcore_rpc::v1::*;
let dispatcher = LightDispatcher::new(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone(),
self.transaction_queue.clone(),
);
macro_rules! add_signing_methods {
($namespace:ident, $handler:expr, $deps:expr) => {
{
let deps = &$deps;
let dispatcher = dispatcher.clone();
let secret_store = Some(deps.secret_store.clone());
if deps.signer_service.is_enabled() {
$handler.extend_with($namespace::to_delegate(
SigningQueueClient::new(&deps.signer_service, dispatcher, &secret_store)
))
} else {
$handler.extend_with(
$namespace::to_delegate(SigningUnsafeClient::new(&secret_store, dispatcher))
)
}
}
}
}
for api in apis {
match *api {
Api::Web3 => {
handler.extend_with(Web3Client::new().to_delegate());
},
Api::Net => {
handler.extend_with(light::NetClient::new(self.sync.clone()).to_delegate());
},
Api::Eth => {
let client = light::EthClient::new(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.transaction_queue.clone(),
self.secret_store.clone(),
self.cache.clone(),
);
handler.extend_with(client.to_delegate());
// TODO: filters.
add_signing_methods!(EthSigning, handler, self);
},
Api::Personal => {
let secret_store = Some(self.secret_store.clone());
handler.extend_with(PersonalClient::new(&secret_store, dispatcher.clone(), self.geth_compatibility).to_delegate());
},
Api::Signer => {
let secret_store = Some(self.secret_store.clone());
handler.extend_with(SignerClient::new(&secret_store, dispatcher.clone(), &self.signer_service).to_delegate());
},
Api::Parity => {
let signer = match self.signer_service.is_enabled() {
true => Some(self.signer_service.clone()),
false => None,
};
handler.extend_with(light::ParityClient::new(
Arc::new(dispatcher.clone()),
self.secret_store.clone(),
self.logger.clone(),
self.settings.clone(),
signer,
self.dapps_interface.clone(),
self.dapps_port,
).to_delegate());
add_signing_methods!(EthSigning, handler, self);
add_signing_methods!(ParitySigning, handler, self);
},
Api::ParityAccounts => {
let secret_store = Some(self.secret_store.clone());
handler.extend_with(ParityAccountsClient::new(&secret_store).to_delegate());
},
Api::ParitySet => {
handler.extend_with(light::ParitySetClient::new(
self.sync.clone(),
self.fetch.clone(),
).to_delegate())
},
Api::Traces => {
handler.extend_with(light::TracesClient.to_delegate())
},
Api::Rpc => {
let modules = to_modules(&apis);
handler.extend_with(RpcClient::new(modules).to_delegate());
}
}
}
}
}
impl ApiSet { impl ApiSet {
pub fn list_apis(&self) -> HashSet<Api> { pub fn list_apis(&self) -> HashSet<Api> {
let mut safe_list = vec![Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc] let mut safe_list = vec![Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc]
@ -172,110 +423,12 @@ impl ApiSet {
} }
} }
macro_rules! add_signing_methods { pub fn setup_rpc<D: Dependencies>(stats: Arc<RpcStats>, deps: &D, apis: ApiSet) -> MetaIoHandler<Metadata, Middleware<D::Notifier>> {
($namespace:ident, $handler:expr, $deps:expr) => { let mut handler = MetaIoHandler::with_middleware(Middleware::new(stats, deps.activity_notifier()));
{
let handler = &mut $handler;
let deps = &$deps;
let dispatcher = FullDispatcher::new(Arc::downgrade(&deps.client), Arc::downgrade(&deps.miner));
if deps.signer_service.is_enabled() {
handler.extend_with($namespace::to_delegate(SigningQueueClient::new(&deps.signer_service, dispatcher, &deps.secret_store)))
} else {
handler.extend_with($namespace::to_delegate(SigningUnsafeClient::new(&deps.secret_store, dispatcher)))
}
}
}
}
pub fn setup_rpc(stats: Arc<RpcStats>, deps: Arc<Dependencies>, apis: ApiSet) -> MetaIoHandler<Metadata, Middleware> {
use ethcore_rpc::v1::*;
let mut handler = MetaIoHandler::with_middleware(Middleware::new(stats, ClientNotifier {
client: deps.client.clone(),
}));
// it's turned into vector, cause ont of the cases requires &[] // it's turned into vector, cause ont of the cases requires &[]
let apis = apis.list_apis().into_iter().collect::<Vec<_>>(); let apis = apis.list_apis().into_iter().collect::<Vec<_>>();
let dispatcher = FullDispatcher::new(Arc::downgrade(&deps.client), Arc::downgrade(&deps.miner)); deps.extend_with_set(&mut handler, &apis[..]);
for api in &apis {
match *api {
Api::Web3 => {
handler.extend_with(Web3Client::new().to_delegate());
},
Api::Net => {
handler.extend_with(NetClient::new(&deps.sync).to_delegate());
},
Api::Eth => {
let client = EthClient::new(
&deps.client,
&deps.snapshot,
&deps.sync,
&deps.secret_store,
&deps.miner,
&deps.external_miner,
EthClientOptions {
pending_nonce_from_queue: deps.geth_compatibility,
allow_pending_receipt_query: !deps.geth_compatibility,
send_block_number_in_get_work: !deps.geth_compatibility,
}
);
handler.extend_with(client.to_delegate());
let filter_client = EthFilterClient::new(&deps.client, &deps.miner);
handler.extend_with(filter_client.to_delegate());
add_signing_methods!(EthSigning, handler, deps);
},
Api::Personal => {
handler.extend_with(PersonalClient::new(&deps.secret_store, dispatcher.clone(), deps.geth_compatibility).to_delegate());
},
Api::Signer => {
handler.extend_with(SignerClient::new(&deps.secret_store, dispatcher.clone(), &deps.signer_service).to_delegate());
},
Api::Parity => {
let signer = match deps.signer_service.is_enabled() {
true => Some(deps.signer_service.clone()),
false => None,
};
handler.extend_with(ParityClient::new(
&deps.client,
&deps.miner,
&deps.sync,
&deps.updater,
&deps.net_service,
&deps.secret_store,
deps.logger.clone(),
deps.settings.clone(),
signer,
deps.dapps_interface.clone(),
deps.dapps_port,
).to_delegate());
add_signing_methods!(EthSigning, handler, deps);
add_signing_methods!(ParitySigning, handler, deps);
},
Api::ParityAccounts => {
handler.extend_with(ParityAccountsClient::new(&deps.secret_store).to_delegate());
},
Api::ParitySet => {
handler.extend_with(ParitySetClient::new(
&deps.client,
&deps.miner,
&deps.updater,
&deps.net_service,
deps.fetch.clone(),
).to_delegate())
},
Api::Traces => {
handler.extend_with(TracesClient::new(&deps.client, &deps.miner).to_delegate())
},
Api::Rpc => {
let modules = to_modules(&apis);
handler.extend_with(RpcClient::new(modules).to_delegate());
}
}
}
handler handler
} }

View File

@ -30,13 +30,13 @@ use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions}; use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
use ethcore::snapshot; use ethcore::snapshot;
use ethcore::verification::queue::VerifierSettings; use ethcore::verification::queue::VerifierSettings;
use light::Cache as LightDataCache;
use ethsync::SyncConfig; use ethsync::SyncConfig;
use informant::Informant; use informant::Informant;
use updater::{UpdatePolicy, Updater}; use updater::{UpdatePolicy, Updater};
use parity_reactor::EventLoop; use parity_reactor::EventLoop;
use hash_fetch::fetch::{Fetch, Client as FetchClient}; use hash_fetch::fetch::{Fetch, Client as FetchClient};
use rpc::{HttpConfiguration, IpcConfiguration};
use params::{ use params::{
SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch,
tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool
@ -61,6 +61,10 @@ const SNAPSHOT_PERIOD: u64 = 10000;
// how many blocks to wait before starting a periodic snapshot. // how many blocks to wait before starting a periodic snapshot.
const SNAPSHOT_HISTORY: u64 = 100; const SNAPSHOT_HISTORY: u64 = 100;
// Number of minutes before a given gas price corpus should expire.
// Light client only.
const GAS_CORPUS_EXPIRATION_MINUTES: i64 = 60 * 6;
// Pops along with error messages when a password is missing or invalid. // Pops along with error messages when a password is missing or invalid.
const VERIFY_PASSWORD_HINT: &'static str = "Make sure valid password is present in files passed using `--password` or in the configuration file."; const VERIFY_PASSWORD_HINT: &'static str = "Make sure valid password is present in files passed using `--password` or in the configuration file.";
@ -76,8 +80,8 @@ pub struct RunCmd {
pub daemon: Option<String>, pub daemon: Option<String>,
pub logger_config: LogConfig, pub logger_config: LogConfig,
pub miner_options: MinerOptions, pub miner_options: MinerOptions,
pub http_conf: HttpConfiguration, pub http_conf: rpc::HttpConfiguration,
pub ipc_conf: IpcConfiguration, pub ipc_conf: rpc::IpcConfiguration,
pub net_conf: NetworkConfiguration, pub net_conf: NetworkConfiguration,
pub network_id: Option<u64>, pub network_id: Option<u64>,
pub warp_sync: bool, pub warp_sync: bool,
@ -108,13 +112,11 @@ pub struct RunCmd {
pub check_seal: bool, pub check_seal: bool,
pub download_old_blocks: bool, pub download_old_blocks: bool,
pub verifier_settings: VerifierSettings, pub verifier_settings: VerifierSettings,
pub serve_light: bool,
pub light: bool,
} }
pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> { pub fn open_ui(signer_conf: &signer::Configuration) -> Result<(), String> {
if !dapps_conf.enabled {
return Err("Cannot use UI command with Dapps turned off.".into())
}
if !signer_conf.enabled { if !signer_conf.enabled {
return Err("Cannot use UI command with UI turned off.".into()) return Err("Cannot use UI command with UI turned off.".into())
} }
@ -127,12 +129,12 @@ pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configur
Ok(()) Ok(())
} }
pub fn open_dapp(dapps_conf: &dapps::Configuration, dapp: &str) -> Result<(), String> { pub fn open_dapp(dapps_conf: &dapps::Configuration, rpc_conf: &rpc::HttpConfiguration, dapp: &str) -> Result<(), String> {
if !dapps_conf.enabled { if !dapps_conf.enabled {
return Err("Cannot use DAPP command with Dapps turned off.".into()) return Err("Cannot use DAPP command with Dapps turned off.".into())
} }
let url = format!("http://{}:{}/{}/", dapps_conf.interface, dapps_conf.port, dapp); let url = format!("http://{}:{}/{}/", rpc_conf.interface, rpc_conf.port, dapp);
url::open(&url); url::open(&url);
Ok(()) Ok(())
} }
@ -153,21 +155,191 @@ impl ::local_store::NodeInfo for FullNodeInfo {
} }
} }
// helper for light execution.
fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> {
use light::client as light_client;
use ethsync::{LightSyncParams, LightSync, ManageNetwork};
use util::RwLock;
let panic_handler = PanicHandler::new_in_arc();
// load spec
let spec = cmd.spec.spec()?;
// load genesis hash
let genesis_hash = spec.genesis_header().hash();
// database paths
let db_dirs = cmd.dirs.database(genesis_hash, cmd.spec.legacy_fork_name(), spec.data_dir.clone());
// user defaults path
let user_defaults_path = db_dirs.user_defaults_path();
// load user defaults
let user_defaults = UserDefaults::load(&user_defaults_path)?;
// select pruning algorithm
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
let compaction = cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path());
// execute upgrades
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, compaction.clone())?;
// create dirs used by parity
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?;
info!("Starting {}", Colour::White.bold().paint(version()));
info!("Running in experimental {} mode.", Colour::Blue.bold().paint("Light Client"));
// start client and create transaction queue.
let mut config = light_client::Config {
queue: Default::default(),
chain_column: ::ethcore::db::COL_LIGHT_CHAIN,
db_cache_size: Some(cmd.cache_config.blockchain() as usize * 1024 * 1024),
db_compaction: compaction,
db_wal: cmd.wal,
};
config.queue.max_mem_use = cmd.cache_config.queue() as usize * 1024 * 1024;
config.queue.verifier_settings = cmd.verifier_settings;
let service = light_client::Service::start(config, &spec, &db_dirs.client_path(algorithm))
.map_err(|e| format!("Error starting light client: {}", e))?;
let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default()));
let provider = ::light::provider::LightProvider::new(service.client().clone(), txq.clone());
// start network.
// set up bootnodes
let mut net_conf = cmd.net_conf;
if !cmd.custom_bootnodes {
net_conf.boot_nodes = spec.nodes.clone();
}
// TODO: configurable cache size.
let cache = LightDataCache::new(Default::default(), ::time::Duration::minutes(GAS_CORPUS_EXPIRATION_MINUTES));
let cache = Arc::new(::util::Mutex::new(cache));
// start on_demand service.
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone()));
// set network path.
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
let sync_params = LightSyncParams {
network_config: net_conf.into_basic().map_err(|e| format!("Failed to produce network config: {}", e))?,
client: Arc::new(provider),
network_id: cmd.network_id.unwrap_or(spec.network_id()),
subprotocol_name: ::ethsync::LIGHT_PROTOCOL,
handlers: vec![on_demand.clone()],
};
let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?;
let light_sync = Arc::new(light_sync);
// spin up event loop
let event_loop = EventLoop::spawn();
// queue cull service.
let queue_cull = Arc::new(::light_helpers::QueueCull {
client: service.client().clone(),
sync: light_sync.clone(),
on_demand: on_demand.clone(),
txq: txq.clone(),
remote: event_loop.remote(),
});
service.register_handler(queue_cull).map_err(|e| format!("Error attaching service: {:?}", e))?;
// start the network.
light_sync.start_network();
// fetch service
let fetch = FetchClient::new().map_err(|e| format!("Error starting fetch client: {:?}", e))?;
let passwords = passwords_from_files(&cmd.acc_conf.password_files)?;
// prepare account provider
let account_provider = Arc::new(prepare_account_provider(&cmd.spec, &cmd.dirs, &spec.data_dir, cmd.acc_conf, &passwords)?);
let rpc_stats = Arc::new(informant::RpcStats::default());
let signer_path = cmd.signer_conf.signer_path.clone();
// start RPCs
let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies {
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
}, cmd.ui_address)),
client: service.client().clone(),
sync: light_sync.clone(),
net: light_sync.clone(),
secret_store: account_provider,
logger: logger,
settings: Arc::new(cmd.net_settings),
on_demand: on_demand,
cache: cache,
transaction_queue: txq,
dapps_interface: match cmd.dapps_conf.enabled {
true => Some(cmd.http_conf.interface.clone()),
false => None,
},
dapps_port: match cmd.dapps_conf.enabled {
true => Some(cmd.http_conf.port),
false => None,
},
fetch: fetch,
geth_compatibility: cmd.geth_compatibility,
});
let dependencies = rpc::Dependencies {
apis: deps_for_rpc_apis.clone(),
remote: event_loop.raw_remote(),
stats: rpc_stats.clone(),
};
// start rpc servers
let _http_server = rpc::new_http(cmd.http_conf, &dependencies, None)?;
let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
// the signer server
let signer_deps = signer::Dependencies {
apis: deps_for_rpc_apis.clone(),
remote: event_loop.raw_remote(),
rpc_stats: rpc_stats.clone(),
};
let signing_queue = deps_for_rpc_apis.signer_service.queue();
let _signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
// TODO: Dapps
// minimal informant thread. Just prints block number every 5 seconds.
// TODO: integrate with informant.rs
let informant_client = service.client().clone();
::std::thread::spawn(move || loop {
info!("#{}", informant_client.best_block_header().number());
::std::thread::sleep(::std::time::Duration::from_secs(5));
});
// wait for ctrl-c.
Ok(wait_for_exit(panic_handler, None, None, can_restart))
}
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> { pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> {
if cmd.ui && cmd.dapps_conf.enabled { if cmd.ui && cmd.dapps_conf.enabled {
// Check if Parity is already running // Check if Parity is already running
let addr = format!("{}:{}", cmd.dapps_conf.interface, cmd.dapps_conf.port); let addr = format!("{}:{}", cmd.signer_conf.interface, cmd.signer_conf.port);
if !TcpListener::bind(&addr as &str).is_ok() { if !TcpListener::bind(&addr as &str).is_ok() {
return open_ui(&cmd.dapps_conf, &cmd.signer_conf).map(|_| (false, None)); return open_ui(&cmd.signer_conf).map(|_| (false, None));
} }
} }
// increase max number of open files
raise_fd_limit();
// run as light client.
if cmd.light {
return execute_light(cmd, can_restart, logger);
}
// set up panic handler // set up panic handler
let panic_handler = PanicHandler::new_in_arc(); let panic_handler = PanicHandler::new_in_arc();
// increase max number of open files
raise_fd_limit();
// load spec // load spec
let spec = cmd.spec.spec()?; let spec = cmd.spec.spec()?;
@ -249,6 +421,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
sync_config.fork_block = spec.fork_block(); sync_config.fork_block = spec.fork_block();
sync_config.warp_sync = cmd.warp_sync; sync_config.warp_sync = cmd.warp_sync;
sync_config.download_old_blocks = cmd.download_old_blocks; sync_config.download_old_blocks = cmd.download_old_blocks;
sync_config.serve_light = cmd.serve_light;
let passwords = passwords_from_files(&cmd.acc_conf.password_files)?; let passwords = passwords_from_files(&cmd.acc_conf.password_files)?;
@ -412,7 +585,8 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
true => None, true => None,
false => Some(account_provider.clone()) false => Some(account_provider.clone())
}; };
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
signer_service: Arc::new(rpc_apis::SignerService::new(move || { signer_service: Arc::new(rpc_apis::SignerService::new(move || {
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
}, cmd.ui_address)), }, cmd.ui_address)),
@ -429,11 +603,11 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
updater: updater.clone(), updater: updater.clone(),
geth_compatibility: cmd.geth_compatibility, geth_compatibility: cmd.geth_compatibility,
dapps_interface: match cmd.dapps_conf.enabled { dapps_interface: match cmd.dapps_conf.enabled {
true => Some(cmd.dapps_conf.interface.clone()), true => Some(cmd.http_conf.interface.clone()),
false => None, false => None,
}, },
dapps_port: match cmd.dapps_conf.enabled { dapps_port: match cmd.dapps_conf.enabled {
true => Some(cmd.dapps_conf.port), true => Some(cmd.http_conf.port),
false => None, false => None,
}, },
fetch: fetch.clone(), fetch: fetch.clone(),
@ -445,21 +619,24 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
stats: rpc_stats.clone(), stats: rpc_stats.clone(),
}; };
// start rpc servers
let http_server = rpc::new_http(cmd.http_conf, &dependencies)?;
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
// the dapps server // the dapps server
let dapps_deps = dapps::Dependencies { let dapps_deps = {
apis: deps_for_rpc_apis.clone(), let (sync, client) = (sync_provider.clone(), client.clone());
client: client.clone(), let contract_client = Arc::new(::dapps::FullRegistrar { client: client.clone() });
sync: sync_provider.clone(),
remote: event_loop.raw_remote(), dapps::Dependencies {
fetch: fetch.clone(), sync_status: Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())),
signer: deps_for_rpc_apis.signer_service.clone(), contract_client: contract_client,
stats: rpc_stats.clone(), remote: event_loop.raw_remote(),
fetch: fetch.clone(),
signer: deps_for_rpc_apis.signer_service.clone(),
}
}; };
let dapps_server = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?;
// start rpc servers
let http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
// the signer server // the signer server
let signer_deps = signer::Dependencies { let signer_deps = signer::Dependencies {
@ -467,10 +644,13 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
remote: event_loop.raw_remote(), remote: event_loop.raw_remote(),
rpc_stats: rpc_stats.clone(), rpc_stats: rpc_stats.clone(),
}; };
let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; let signing_queue = deps_for_rpc_apis.signer_service.queue();
let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
// secret store key server // secret store key server
let secretstore_deps = secretstore::Dependencies { }; let secretstore_deps = secretstore::Dependencies {
client: client.clone(),
};
let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps);
// the ipfs server // the ipfs server
@ -524,18 +704,18 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
// start ui // start ui
if cmd.ui { if cmd.ui {
open_ui(&cmd.dapps_conf, &cmd.signer_conf)?; open_ui(&cmd.signer_conf)?;
} }
if let Some(dapp) = cmd.dapp { if let Some(dapp) = cmd.dapp {
open_dapp(&cmd.dapps_conf, &dapp)?; open_dapp(&cmd.dapps_conf, &cmd.http_conf, &dapp)?;
} }
// Handle exit // Handle exit
let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart); let restart = wait_for_exit(panic_handler, Some(updater), Some(client), can_restart);
// drop this stuff as soon as exit detected. // drop this stuff as soon as exit detected.
drop((http_server, ipc_server, dapps_server, signer_server, secretstore_key_server, ipfs_server, event_loop)); drop((http_server, ipc_server, signer_server, secretstore_key_server, ipfs_server, event_loop));
info!("Finishing work, please wait..."); info!("Finishing work, please wait...");

View File

@ -14,7 +14,9 @@
// 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/>.
use std::sync::Arc;
use dir::default_data_path; use dir::default_data_path;
use ethcore::client::Client;
use helpers::replace_home; use helpers::replace_home;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -30,10 +32,10 @@ pub struct Configuration {
pub data_path: String, pub data_path: String,
} }
#[derive(Debug, PartialEq, Clone)]
/// Secret store dependencies /// Secret store dependencies
pub struct Dependencies { pub struct Dependencies {
// the only dependency will be BlockChainClient /// Blockchain client.
pub client: Arc<Client>,
} }
#[cfg(not(feature = "secretstore"))] #[cfg(not(feature = "secretstore"))]
@ -53,6 +55,7 @@ mod server {
#[cfg(feature="secretstore")] #[cfg(feature="secretstore")]
mod server { mod server {
use ethkey;
use ethcore_secretstore; use ethcore_secretstore;
use super::{Configuration, Dependencies}; use super::{Configuration, Dependencies};
@ -63,14 +66,39 @@ mod server {
impl KeyServer { impl KeyServer {
/// Create new key server /// Create new key server
pub fn new(conf: Configuration, _deps: Dependencies) -> Result<Self, String> { pub fn new(conf: Configuration, deps: Dependencies) -> Result<Self, String> {
let key_pairs = vec![
ethkey::KeyPair::from_secret("6c26a76e9b31048d170873a791401c7e799a11f0cefc0171cc31a49800967509".parse().unwrap()).unwrap(),
ethkey::KeyPair::from_secret("7e94018b3731afdb3b4e6f4c3e179475640166da12e1d1b0c7d80729b1a5b452".parse().unwrap()).unwrap(),
ethkey::KeyPair::from_secret("5ab6ed2a52c33142380032c39a03a86b12eacb3fa4b53bc16d84f51318156f8c".parse().unwrap()).unwrap(),
];
let conf = ethcore_secretstore::ServiceConfiguration { let conf = ethcore_secretstore::ServiceConfiguration {
listener_addr: conf.interface, listener_address: ethcore_secretstore::NodeAddress {
listener_port: conf.port, address: conf.interface.clone(),
data_path: conf.data_path, port: conf.port,
},
data_path: conf.data_path.clone(),
// TODO: this is test configuration. how it will be configured in production?
cluster_config: ethcore_secretstore::ClusterConfiguration {
threads: 4,
self_private: (***key_pairs[(conf.port - 8082) as usize].secret()).into(),
listener_address: ethcore_secretstore::NodeAddress {
address: conf.interface.clone(),
port: conf.port + 10,
},
nodes: key_pairs.iter().enumerate().map(|(i, kp)| (kp.public().clone(),
ethcore_secretstore::NodeAddress {
address: conf.interface.clone(),
port: 8082 + 10 + (i as u16),
})).collect(),
allow_connecting_to_higher_nodes: true,
encryption_config: ethcore_secretstore::EncryptionConfiguration {
key_check_timeout_ms: 1000,
},
}
}; };
let key_server = ethcore_secretstore::start(conf) let key_server = ethcore_secretstore::start(deps.client, conf)
.map_err(Into::<String>::into)?; .map_err(Into::<String>::into)?;
Ok(KeyServer { Ok(KeyServer {

View File

@ -23,7 +23,7 @@ pub use ethcore_signer::Server as SignerServer;
use ansi_term::Colour; use ansi_term::Colour;
use dir::default_data_path; use dir::default_data_path;
use ethcore_rpc::informant::RpcStats; use ethcore_rpc::informant::RpcStats;
use ethcore_rpc; use ethcore_rpc::{self, ConfirmationsQueue};
use ethcore_signer as signer; use ethcore_signer as signer;
use helpers::replace_home; use helpers::replace_home;
use parity_reactor::TokioRemote; use parity_reactor::TokioRemote;
@ -55,8 +55,8 @@ impl Default for Configuration {
} }
} }
pub struct Dependencies { pub struct Dependencies<D: rpc_apis::Dependencies> {
pub apis: Arc<rpc_apis::Dependencies>, pub apis: Arc<D>,
pub remote: TokioRemote, pub remote: TokioRemote,
pub rpc_stats: Arc<RpcStats>, pub rpc_stats: Arc<RpcStats>,
} }
@ -77,11 +77,15 @@ impl signer::MetaExtractor<ethcore_rpc::Metadata> for StandardExtractor {
} }
} }
pub fn start(conf: Configuration, deps: Dependencies) -> Result<Option<SignerServer>, String> { pub fn start<D: rpc_apis::Dependencies>(
conf: Configuration,
queue: Arc<ConfirmationsQueue>,
deps: Dependencies<D>,
) -> Result<Option<SignerServer>, String> {
if !conf.enabled { if !conf.enabled {
Ok(None) Ok(None)
} else { } else {
Ok(Some(do_start(conf, deps)?)) Ok(Some(do_start(conf, queue, deps)?))
} }
} }
@ -125,14 +129,18 @@ pub fn generate_new_token(path: String) -> io::Result<String> {
Ok(code) Ok(code)
} }
fn do_start(conf: Configuration, deps: Dependencies) -> Result<SignerServer, String> { fn do_start<D: rpc_apis::Dependencies>(
conf: Configuration,
queue: Arc<ConfirmationsQueue>,
deps: Dependencies<D>
) -> Result<SignerServer, String> {
let addr = format!("{}:{}", conf.interface, conf.port) let addr = format!("{}:{}", conf.interface, conf.port)
.parse() .parse()
.map_err(|_| format!("Invalid port specified: {}", conf.port))?; .map_err(|_| format!("Invalid port specified: {}", conf.port))?;
let start_result = { let start_result = {
let server = signer::ServerBuilder::new( let server = signer::ServerBuilder::new(
deps.apis.signer_service.queue(), queue,
codes_path(conf.signer_path), codes_path(conf.signer_path),
); );
if conf.skip_origin_validation { if conf.skip_origin_validation {
@ -141,7 +149,7 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result<SignerServer, Str
} }
let server = server.skip_origin_validation(conf.skip_origin_validation); let server = server.skip_origin_validation(conf.skip_origin_validation);
let server = server.stats(deps.rpc_stats.clone()); let server = server.stats(deps.rpc_stats.clone());
let handler = rpc_apis::setup_rpc(deps.rpc_stats, deps.apis, rpc_apis::ApiSet::SafeContext); let handler = rpc_apis::setup_rpc(deps.rpc_stats, &*deps.apis, rpc_apis::ApiSet::SafeContext);
let remote = deps.remote.clone(); let remote = deps.remote.clone();
server.start_with_extractor(addr, handler, remote, StandardExtractor) server.start_with_extractor(addr, handler, remote, StandardExtractor)
}; };

View File

@ -21,6 +21,7 @@ transient-hashmap = "0.4"
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-minihttp-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-ipc-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }

View File

@ -29,8 +29,9 @@ extern crate time;
extern crate transient_hashmap; extern crate transient_hashmap;
extern crate jsonrpc_core; extern crate jsonrpc_core;
pub extern crate jsonrpc_http_server as http; extern crate jsonrpc_http_server as http;
pub extern crate jsonrpc_ipc_server as ipc; extern crate jsonrpc_minihttp_server as minihttp;
extern crate jsonrpc_ipc_server as ipc;
extern crate ethash; extern crate ethash;
extern crate ethcore; extern crate ethcore;
@ -62,10 +63,15 @@ extern crate ethjson;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;
mod metadata;
pub mod v1; pub mod v1;
pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext}; pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext};
pub use http::{HttpMetaExtractor, Server as HttpServer, Error as HttpServerError, AccessControlAllowOrigin, Host}; pub use http::{
hyper,
RequestMiddleware, RequestMiddlewareAction,
AccessControlAllowOrigin, Host,
};
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch}; pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant, dispatch};
pub use v1::block_import::is_major_importing; pub use v1::block_import::is_major_importing;
@ -73,26 +79,98 @@ pub use v1::block_import::is_major_importing;
use std::net::SocketAddr; use std::net::SocketAddr;
use http::tokio_core; use http::tokio_core;
/// RPC HTTP Server instance
pub enum HttpServer {
/// Fast MiniHTTP variant
Mini(minihttp::Server),
/// Hyper variant
Hyper(http::Server),
}
/// RPC HTTP Server error
#[derive(Debug)]
pub enum HttpServerError {
/// IO error
Io(::std::io::Error),
/// Other hyper error
Hyper(hyper::Error),
}
impl From<http::Error> for HttpServerError {
fn from(e: http::Error) -> Self {
use self::HttpServerError::*;
match e {
http::Error::Io(io) => Io(io),
http::Error::Other(hyper) => Hyper(hyper),
}
}
}
impl From<minihttp::Error> for HttpServerError {
fn from(e: minihttp::Error) -> Self {
use self::HttpServerError::*;
match e {
minihttp::Error::Io(io) => Io(io),
}
}
}
/// HTTP RPC server impl-independent metadata extractor
pub trait HttpMetaExtractor: Send + Sync + 'static {
/// Type of Metadata
type Metadata: jsonrpc_core::Metadata;
/// Extracts metadata from given params.
fn read_metadata(&self, origin: String, dapps_origin: Option<String>) -> Self::Metadata;
}
/// HTTP server implementation-specific settings.
pub enum HttpSettings<R: RequestMiddleware> {
/// Enable fast minihttp server with given number of threads.
Threads(usize),
/// Enable standard server with optional dapps middleware.
Dapps(Option<R>),
}
/// Start http server asynchronously and returns result with `Server` handle on success or an error. /// Start http server asynchronously and returns result with `Server` handle on success or an error.
pub fn start_http<M, S, H, T>( pub fn start_http<M, S, H, T, R>(
addr: &SocketAddr, addr: &SocketAddr,
cors_domains: http::DomainsValidation<http::AccessControlAllowOrigin>, cors_domains: http::DomainsValidation<http::AccessControlAllowOrigin>,
allowed_hosts: http::DomainsValidation<http::Host>, allowed_hosts: http::DomainsValidation<http::Host>,
handler: H, handler: H,
remote: tokio_core::reactor::Remote, remote: tokio_core::reactor::Remote,
extractor: T, extractor: T,
settings: HttpSettings<R>,
) -> Result<HttpServer, HttpServerError> where ) -> Result<HttpServer, HttpServerError> where
M: jsonrpc_core::Metadata, M: jsonrpc_core::Metadata,
S: jsonrpc_core::Middleware<M>, S: jsonrpc_core::Middleware<M>,
H: Into<jsonrpc_core::MetaIoHandler<M, S>>, H: Into<jsonrpc_core::MetaIoHandler<M, S>>,
T: HttpMetaExtractor<M>, T: HttpMetaExtractor<Metadata=M>,
R: RequestMiddleware,
{ {
http::ServerBuilder::new(handler) Ok(match settings {
.event_loop_remote(remote) HttpSettings::Dapps(middleware) => {
.meta_extractor(extractor) let mut builder = http::ServerBuilder::new(handler)
.cors(cors_domains.into()) .event_loop_remote(remote)
.allowed_hosts(allowed_hosts.into()) .meta_extractor(metadata::HyperMetaExtractor::new(extractor))
.start_http(addr) .cors(cors_domains.into())
.allowed_hosts(allowed_hosts.into());
if let Some(dapps) = middleware {
builder = builder.request_middleware(dapps)
}
builder.start_http(addr)
.map(HttpServer::Hyper)?
},
HttpSettings::Threads(threads) => {
minihttp::ServerBuilder::new(handler)
.threads(threads)
.meta_extractor(metadata::MiniMetaExtractor::new(extractor))
.cors(cors_domains.into())
.allowed_hosts(allowed_hosts.into())
.start_http(addr)
.map(HttpServer::Mini)?
},
})
} }
/// Start ipc server asynchronously and returns result with `Server` handle on success or an error. /// Start ipc server asynchronously and returns result with `Server` handle on success or an error.

74
rpc/src/metadata.rs Normal file
View File

@ -0,0 +1,74 @@
// 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/>.
use jsonrpc_core;
use http;
use hyper;
use minihttp;
use HttpMetaExtractor;
pub struct HyperMetaExtractor<T> {
extractor: T,
}
impl<T> HyperMetaExtractor<T> {
pub fn new(extractor: T) -> Self {
HyperMetaExtractor {
extractor: extractor,
}
}
}
impl<M, T> http::MetaExtractor<M> for HyperMetaExtractor<T> where
T: HttpMetaExtractor<Metadata = M>,
M: jsonrpc_core::Metadata,
{
fn read_metadata(&self, req: &hyper::server::Request<hyper::net::HttpStream>) -> M {
let origin = req.headers().get::<hyper::header::Origin>()
.map(|origin| format!("{}://{}", origin.scheme, origin.host))
.unwrap_or_else(|| "unknown".into());
let dapps_origin = req.headers().get_raw("x-parity-origin")
.and_then(|raw| raw.one())
.map(|raw| String::from_utf8_lossy(raw).into_owned());
self.extractor.read_metadata(origin, dapps_origin)
}
}
pub struct MiniMetaExtractor<T> {
extractor: T,
}
impl<T> MiniMetaExtractor<T> {
pub fn new(extractor: T) -> Self {
MiniMetaExtractor {
extractor: extractor,
}
}
}
impl<M, T> minihttp::MetaExtractor<M> for MiniMetaExtractor<T> where
T: HttpMetaExtractor<Metadata = M>,
M: jsonrpc_core::Metadata,
{
fn read_metadata(&self, req: &minihttp::Req) -> M {
let origin = req.header("origin")
.unwrap_or_else(|| "unknown")
.to_owned();
let dapps_origin = req.header("x-parity-origin").map(|h| h.to_owned());
self.extractor.read_metadata(origin, dapps_origin)
}
}

View File

@ -207,7 +207,6 @@ pub fn fetch_gas_price_corpus(
} }
/// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network. /// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network.
/// Light client `ETH` RPC.
#[derive(Clone)] #[derive(Clone)]
pub struct LightDispatcher { pub struct LightDispatcher {
/// Sync service. /// Sync service.

View File

@ -108,7 +108,22 @@ impl Eth for EthClient {
} }
fn syncing(&self) -> Result<SyncStatus, Error> { fn syncing(&self) -> Result<SyncStatus, Error> {
rpc_unimplemented!() if self.sync.is_major_importing() {
let chain_info = self.client.chain_info();
let current_block = U256::from(chain_info.best_block_number);
let highest_block = self.sync.highest_block().map(U256::from)
.unwrap_or_else(|| current_block.clone());
Ok(SyncStatus::Info(SyncInfo {
starting_block: U256::from(self.sync.start_block()).into(),
current_block: current_block.into(),
highest_block: highest_block.into(),
warp_chunks_amount: None,
warp_chunks_processed: None,
}))
} else {
Ok(SyncStatus::None)
}
} }
fn author(&self, _meta: Self::Metadata) -> BoxFuture<RpcH160, Error> { fn author(&self, _meta: Self::Metadata) -> BoxFuture<RpcH160, Error> {

View File

@ -23,7 +23,10 @@ pub mod eth;
pub mod parity; pub mod parity;
pub mod parity_set; pub mod parity_set;
pub mod trace; pub mod trace;
pub mod net;
pub use self::eth::EthClient; pub use self::eth::EthClient;
pub use self::parity::ParityClient; pub use self::parity::ParityClient;
pub use self::parity_set::ParitySetClient; pub use self::parity_set::ParitySetClient;
pub use self::net::NetClient;
pub use self::trace::TracesClient;

View File

@ -0,0 +1,49 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Net rpc implementation.
use std::sync::Arc;
use jsonrpc_core::Error;
use ethsync::LightSyncProvider;
use v1::traits::Net;
/// Net rpc implementation.
pub struct NetClient<S: ?Sized> {
sync: Arc<S>
}
impl<S: ?Sized> NetClient<S> where S: LightSyncProvider {
/// Creates new NetClient.
pub fn new(sync: Arc<S>) -> Self {
NetClient {
sync: sync,
}
}
}
impl<S: ?Sized + Sync + Send + 'static> Net for NetClient<S> where S: LightSyncProvider {
fn version(&self) -> Result<String, Error> {
Ok(format!("{}", self.sync.network_id()).to_owned())
}
fn peer_count(&self) -> Result<String, Error> {
Ok(format!("0x{:x}", self.sync.peer_numbers().connected as u64).to_owned())
}
fn is_listening(&self) -> Result<bool, Error> {
Ok(true)
}
}

View File

@ -21,7 +21,7 @@ use ethsync::SyncProvider;
use v1::traits::Net; use v1::traits::Net;
/// Net rpc implementation. /// Net rpc implementation.
pub struct NetClient<S: ?Sized> where S: SyncProvider { pub struct NetClient<S: ?Sized> {
sync: Weak<S> sync: Weak<S>
} }

View File

@ -83,7 +83,7 @@ impl SyncProvider for TestSyncProvider {
difficulty: Some(40.into()), difficulty: Some(40.into()),
head: 50.into(), head: 50.into(),
}), }),
les_info: None, pip_info: None,
}, },
PeerInfo { PeerInfo {
id: None, id: None,
@ -96,7 +96,7 @@ impl SyncProvider for TestSyncProvider {
difficulty: None, difficulty: None,
head: 60.into() head: 60.into()
}), }),
les_info: None, pip_info: None,
} }
] ]
} }

View File

@ -304,7 +304,7 @@ fn rpc_parity_net_peers() {
let io = deps.default_client(); let io = deps.default_client();
let request = r#"{"jsonrpc": "2.0", "method": "parity_netPeers", "params":[], "id": 1}"#; let request = r#"{"jsonrpc": "2.0", "method": "parity_netPeers", "params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":{"active":0,"connected":120,"max":50,"peers":[{"caps":["eth/62","eth/63"],"id":"node1","name":"Parity/1","network":{"localAddress":"127.0.0.1:8888","remoteAddress":"127.0.0.1:7777"},"protocols":{"eth":{"difficulty":"0x28","head":"0000000000000000000000000000000000000000000000000000000000000032","version":62},"les":null}},{"caps":["eth/63","eth/64"],"id":null,"name":"Parity/2","network":{"localAddress":"127.0.0.1:3333","remoteAddress":"Handshake"},"protocols":{"eth":{"difficulty":null,"head":"000000000000000000000000000000000000000000000000000000000000003c","version":64},"les":null}}]},"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":{"active":0,"connected":120,"max":50,"peers":[{"caps":["eth/62","eth/63"],"id":"node1","name":"Parity/1","network":{"localAddress":"127.0.0.1:8888","remoteAddress":"127.0.0.1:7777"},"protocols":{"eth":{"difficulty":"0x28","head":"0000000000000000000000000000000000000000000000000000000000000032","version":62},"pip":null}},{"caps":["eth/63","eth/64"],"id":null,"name":"Parity/2","network":{"localAddress":"127.0.0.1:3333","remoteAddress":"Handshake"},"protocols":{"eth":{"difficulty":null,"head":"000000000000000000000000000000000000000000000000000000000000003c","version":64},"pip":null}}]},"id":1}"#;
assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
} }

View File

@ -65,7 +65,7 @@ pub use self::receipt::Receipt;
pub use self::rpc_settings::RpcSettings; pub use self::rpc_settings::RpcSettings;
pub use self::sync::{ pub use self::sync::{
SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo,
TransactionStats, ChainStatus, EthProtocolInfo, LesProtocolInfo, TransactionStats, ChainStatus, EthProtocolInfo, PipProtocolInfo,
}; };
pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace::{LocalizedTrace, TraceResults};
pub use self::trace_filter::TraceFilter; pub use self::trace_filter::TraceFilter;

View File

@ -83,8 +83,8 @@ pub struct PeerNetworkInfo {
pub struct PeerProtocolsInfo { pub struct PeerProtocolsInfo {
/// Ethereum protocol information /// Ethereum protocol information
pub eth: Option<EthProtocolInfo>, pub eth: Option<EthProtocolInfo>,
/// LES protocol information. /// PIP protocol information.
pub les: Option<LesProtocolInfo>, pub pip: Option<PipProtocolInfo>,
} }
/// Peer Ethereum protocol information /// Peer Ethereum protocol information
@ -108,10 +108,10 @@ impl From<ethsync::EthProtocolInfo> for EthProtocolInfo {
} }
} }
/// Peer LES protocol information /// Peer PIP protocol information
#[derive(Default, Debug, Serialize)] #[derive(Default, Debug, Serialize)]
pub struct LesProtocolInfo { pub struct PipProtocolInfo {
/// Negotiated LES protocol version /// Negotiated PIP protocol version
pub version: u32, pub version: u32,
/// Peer total difficulty /// Peer total difficulty
pub difficulty: U256, pub difficulty: U256,
@ -119,9 +119,9 @@ pub struct LesProtocolInfo {
pub head: String, pub head: String,
} }
impl From<ethsync::LesProtocolInfo> for LesProtocolInfo { impl From<ethsync::PipProtocolInfo> for PipProtocolInfo {
fn from(info: ethsync::LesProtocolInfo) -> Self { fn from(info: ethsync::PipProtocolInfo) -> Self {
LesProtocolInfo { PipProtocolInfo {
version: info.version, version: info.version,
difficulty: info.difficulty.into(), difficulty: info.difficulty.into(),
head: info.head.hex(), head: info.head.hex(),
@ -171,7 +171,7 @@ impl From<SyncPeerInfo> for PeerInfo {
}, },
protocols: PeerProtocolsInfo { protocols: PeerProtocolsInfo {
eth: p.eth_info.map(Into::into), eth: p.eth_info.map(Into::into),
les: p.les_info.map(Into::into), pip: p.pip_info.map(Into::into),
}, },
} }
} }

View File

@ -32,7 +32,6 @@ $HOME/.cargo,\
$HOME/.multirust,\ $HOME/.multirust,\
rocksdb,\ rocksdb,\
secp256k1,\ secp256k1,\
src/tests,\
util/json-tests,\ util/json-tests,\
util/src/network/tests,\ util/src/network/tests,\
ethcore/src/evm/tests,\ ethcore/src/evm/tests,\

View File

@ -5,7 +5,7 @@ export TARGETS="
-p ethash \ -p ethash \
-p ethcore \ -p ethcore \
-p ethcore-bigint\ -p ethcore-bigint\
-p ethcore-dapps \ -p parity-dapps \
-p ethcore-rpc \ -p ethcore-rpc \
-p ethcore-signer \ -p ethcore-signer \
-p ethcore-util \ -p ethcore-util \

View File

@ -10,16 +10,29 @@ build = "build.rs"
ethcore-ipc-codegen = { path = "../ipc/codegen" } ethcore-ipc-codegen = { path = "../ipc/codegen" }
[dependencies] [dependencies]
byteorder = "1.0"
log = "0.3" log = "0.3"
parking_lot = "0.4" parking_lot = "0.4"
hyper = { version = "0.10", default-features = false } hyper = { version = "0.10", default-features = false }
serde = "0.9"
serde_json = "0.9"
serde_derive = "0.9"
futures = "0.1"
futures-cpupool = "0.1"
rustc-serialize = "0.3"
tokio-core = "0.1"
tokio-service = "0.1"
tokio-proto = "0.1"
url = "1.0" url = "1.0"
ethabi = "1.0.0"
ethcore = { path = "../ethcore" }
ethcore-devtools = { path = "../devtools" } ethcore-devtools = { path = "../devtools" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
ethcore-ipc = { path = "../ipc/rpc" } ethcore-ipc = { path = "../ipc/rpc" }
ethcore-ipc-nano = { path = "../ipc/nano" } ethcore-ipc-nano = { path = "../ipc/nano" }
ethcrypto = { path = "../ethcrypto" } ethcrypto = { path = "../ethcrypto" }
ethkey = { path = "../ethkey" } ethkey = { path = "../ethkey" }
native-contracts = { path = "../ethcore/native_contracts" }
[profile.release] [profile.release]
debug = true debug = true

View File

@ -14,38 +14,92 @@
// 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/>.
use std::collections::{HashMap, HashSet}; use std::sync::Arc;
use parking_lot::RwLock; use futures::{future, Future};
use parking_lot::Mutex;
use ethkey::public_to_address;
use ethcore::client::{Client, BlockChainClient, BlockId};
use native_contracts::SecretStoreAclStorage;
use types::all::{Error, DocumentAddress, Public}; use types::all::{Error, DocumentAddress, Public};
const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checker";
/// ACL storage of Secret Store /// ACL storage of Secret Store
pub trait AclStorage: Send + Sync { pub trait AclStorage: Send + Sync {
/// Check if requestor with `public` key can access document with hash `document` /// Check if requestor with `public` key can access document with hash `document`
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>; fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>;
} }
/// Dummy ACL storage implementation /// On-chain ACL storage implementation.
#[derive(Default, Debug)] pub struct OnChainAclStorage {
pub struct DummyAclStorage { /// Blockchain client.
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>, client: Arc<Client>,
/// On-chain contract.
contract: Mutex<Option<SecretStoreAclStorage>>,
} }
impl DummyAclStorage { impl OnChainAclStorage {
#[cfg(test)] pub fn new(client: Arc<Client>) -> Self {
/// Prohibit given requestor access to given document OnChainAclStorage {
pub fn prohibit(&self, public: Public, document: DocumentAddress) { client: client,
self.prohibited.write() contract: Mutex::new(None),
.entry(public) }
.or_insert_with(Default::default)
.insert(document);
} }
} }
impl AclStorage for DummyAclStorage { impl AclStorage for OnChainAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> { fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
Ok(self.prohibited.read() let mut contract = self.contract.lock();
.get(public) if !contract.is_some() {
.map(|docs| !docs.contains(document)) *contract = self.client.registry_address(ACL_CHECKER_CONTRACT_REGISTRY_NAME.to_owned())
.unwrap_or(true)) .and_then(|contract_addr| {
trace!(target: "secretstore", "Configuring for ACL checker contract from {}", contract_addr);
Some(SecretStoreAclStorage::new(contract_addr))
})
}
if let Some(ref contract) = *contract {
let address = public_to_address(&public);
let do_call = |a, d| future::done(self.client.call_contract(BlockId::Latest, a, d));
contract.check_permissions(do_call, address, document.clone())
.map_err(|err| Error::Internal(err))
.wait()
} else {
Err(Error::Internal("ACL checker contract is not configured".to_owned()))
}
}
}
#[cfg(test)]
pub mod tests {
use std::collections::{HashMap, HashSet};
use parking_lot::RwLock;
use types::all::{Error, DocumentAddress, Public};
use super::AclStorage;
#[derive(Default, Debug)]
/// Dummy ACL storage implementation
pub struct DummyAclStorage {
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>,
}
impl DummyAclStorage {
#[cfg(test)]
/// Prohibit given requestor access to given document
pub fn prohibit(&self, public: Public, document: DocumentAddress) {
self.prohibited.write()
.entry(public)
.or_insert_with(Default::default)
.insert(document);
}
}
impl AclStorage for DummyAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
Ok(self.prohibited.read()
.get(public)
.map(|docs| !docs.contains(document))
.unwrap_or(true))
}
} }
} }

View File

@ -14,7 +14,6 @@
// 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/>.
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use hyper::header; use hyper::header;
use hyper::uri::RequestUri; use hyper::uri::RequestUri;
@ -39,7 +38,9 @@ pub struct KeyServerHttpListener<T: KeyServer + 'static> {
enum Request { enum Request {
/// Invalid request /// Invalid request
Invalid, Invalid,
/// Request encryption key of given document for given requestor /// Generate encryption key.
GenerateDocumentKey(DocumentAddress, RequestSignature, usize),
/// Request encryption key of given document for given requestor.
GetDocumentKey(DocumentAddress, RequestSignature), GetDocumentKey(DocumentAddress, RequestSignature),
} }
@ -63,9 +64,9 @@ impl<T> KeyServerHttpListener<T> where T: KeyServer + 'static {
handler: shared_handler.clone(), handler: shared_handler.clone(),
}; };
let listener_addr: &str = &format!("{}:{}", config.listener_addr, config.listener_port); let listener_addr: &str = &format!("{}:{}", config.listener_address.address, config.listener_address.port);
let http_server = HttpServer::http(&listener_addr).unwrap(); let http_server = HttpServer::http(&listener_addr).expect("cannot start HttpServer");
let http_server = http_server.handle(handler).unwrap(); let http_server = http_server.handle(handler).expect("cannot start HttpServer");
let listener = KeyServerHttpListener { let listener = KeyServerHttpListener {
_http_server: http_server, _http_server: http_server,
handler: shared_handler, handler: shared_handler,
@ -75,6 +76,10 @@ impl<T> KeyServerHttpListener<T> where T: KeyServer + 'static {
} }
impl<T> KeyServer for KeyServerHttpListener<T> where T: KeyServer + 'static { impl<T> KeyServer for KeyServerHttpListener<T> where T: KeyServer + 'static {
fn generate_document_key(&self, signature: &RequestSignature, document: &DocumentAddress, threshold: usize) -> Result<DocumentEncryptedKey, Error> {
self.handler.key_server.generate_document_key(signature, document, threshold)
}
fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> { fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> {
self.handler.key_server.document_key(signature, document) self.handler.key_server.document_key(signature, document)
} }
@ -82,95 +87,103 @@ impl<T> KeyServer for KeyServerHttpListener<T> where T: KeyServer + 'static {
impl<T> HttpHandler for KeyServerHttpHandler<T> where T: KeyServer + 'static { impl<T> HttpHandler for KeyServerHttpHandler<T> where T: KeyServer + 'static {
fn handle(&self, req: HttpRequest, mut res: HttpResponse) { fn handle(&self, req: HttpRequest, mut res: HttpResponse) {
if req.method != HttpMethod::Get {
warn!(target: "secretstore", "Ignoring {}-request {}", req.method, req.uri);
*res.status_mut() = HttpStatusCode::NotFound;
return;
}
if req.headers.has::<header::Origin>() { if req.headers.has::<header::Origin>() {
warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri); warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri);
*res.status_mut() = HttpStatusCode::NotFound; *res.status_mut() = HttpStatusCode::NotFound;
return; return;
} }
match req.uri { let req_method = req.method.clone();
RequestUri::AbsolutePath(ref path) => match parse_request(&path) { let req_uri = req.uri.clone();
Request::GetDocumentKey(document, signature) => { match &req_uri {
let document_key = self.handler.key_server.document_key(&signature, &document) &RequestUri::AbsolutePath(ref path) => match parse_request(&req_method, &path) {
Request::GenerateDocumentKey(document, signature, threshold) => {
return_document_key(req, res, self.handler.key_server.generate_document_key(&signature, &document, threshold)
.map_err(|err| { .map_err(|err| {
warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req.uri, err); warn!(target: "secretstore", "GenerateDocumentKey request {} has failed with: {}", req_uri, err);
err err
}); }));
match document_key { },
Ok(document_key) => { Request::GetDocumentKey(document, signature) => {
let document_key = document_key.to_hex().into_bytes(); return_document_key(req, res, self.handler.key_server.document_key(&signature, &document)
res.headers_mut().set(header::ContentType::plaintext()); .map_err(|err| {
if let Err(err) = res.send(&document_key) { warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req_uri, err);
// nothing to do, but log error err
warn!(target: "secretstore", "GetDocumentKey request {} response has failed with: {}", req.uri, err); }));
}
},
Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest,
Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden,
Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound,
Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
}
}, },
Request::Invalid => { Request::Invalid => {
warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri);
*res.status_mut() = HttpStatusCode::BadRequest; *res.status_mut() = HttpStatusCode::BadRequest;
}, },
}, },
_ => { _ => {
warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri);
*res.status_mut() = HttpStatusCode::NotFound; *res.status_mut() = HttpStatusCode::NotFound;
}, },
}; };
} }
} }
fn parse_request(uri_path: &str) -> Request { fn return_document_key(req: HttpRequest, mut res: HttpResponse, document_key: Result<DocumentEncryptedKey, Error>) {
match document_key {
Ok(document_key) => {
let document_key = document_key.to_hex().into_bytes();
res.headers_mut().set(header::ContentType::plaintext());
if let Err(err) = res.send(&document_key) {
// nothing to do, but to log an error
warn!(target: "secretstore", "response to request {} has failed with: {}", req.uri, err);
}
},
Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest,
Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden,
Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound,
Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
}
}
fn parse_request(method: &HttpMethod, uri_path: &str) -> Request {
let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() { let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() {
Ok(path) => path, Ok(path) => path,
Err(_) => return Request::Invalid, Err(_) => return Request::Invalid,
}; };
let path: Vec<String> = uri_path.trim_left_matches('/').split('/').map(Into::into).collect(); let path: Vec<String> = uri_path.trim_left_matches('/').split('/').map(Into::into).collect();
if path.len() != 2 || path[0].is_empty() || path[1].is_empty() { if path.len() < 2 || path[0].is_empty() || path[1].is_empty() {
return Request::Invalid; return Request::Invalid;
} }
let document = DocumentAddress::from_str(&path[0]); let args_len = path.len();
let signature = RequestSignature::from_str(&path[1]); let document = path[0].parse();
match (document, signature) { let signature = path[1].parse();
(Ok(document), Ok(signature)) => Request::GetDocumentKey(document, signature), let threshold = (if args_len > 2 { &path[2] } else { "" }).parse();
match (args_len, method, document, signature, threshold) {
(3, &HttpMethod::Post, Ok(document), Ok(signature), Ok(threshold)) => Request::GenerateDocumentKey(document, signature, threshold),
(2, &HttpMethod::Get, Ok(document), Ok(signature), _) => Request::GetDocumentKey(document, signature),
_ => Request::Invalid, _ => Request::Invalid,
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use hyper::method::Method as HttpMethod;
use super::super::RequestSignature;
use super::{parse_request, Request}; use super::{parse_request, Request};
#[test] #[test]
fn parse_request_successful() { fn parse_request_successful() {
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"),
Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(),
RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); "a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap()));
assert_eq!(parse_request("/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), assert_eq!(parse_request(&HttpMethod::Get, "/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"),
Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(),
RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); "a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap()));
} }
#[test] #[test]
fn parse_request_failed() { fn parse_request_failed() {
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid); assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid);
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid); assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid);
assert_eq!(parse_request("/a/b"), Request::Invalid); assert_eq!(parse_request(&HttpMethod::Get, "/a/b"), Request::Invalid);
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid); assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid);
} }
} }

View File

@ -14,42 +14,78 @@
// 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/>.
use std::thread;
use std::sync::Arc;
use std::sync::mpsc;
use futures::{self, Future};
use parking_lot::Mutex;
use tokio_core::reactor::Core;
use ethcrypto; use ethcrypto;
use ethkey; use ethkey;
use super::acl_storage::AclStorage; use super::acl_storage::AclStorage;
use super::key_storage::KeyStorage; use super::key_storage::KeyStorage;
use key_server_cluster::ClusterCore;
use traits::KeyServer; use traits::KeyServer;
use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey, ClusterConfiguration};
use key_server_cluster::{ClusterClient, ClusterConfiguration as NetClusterConfiguration};
/// Secret store key server implementation /// Secret store key server implementation
pub struct KeyServerImpl<T: AclStorage, U: KeyStorage> { pub struct KeyServerImpl {
acl_storage: T, data: Arc<Mutex<KeyServerCore>>,
key_storage: U,
} }
impl<T, U> KeyServerImpl<T, U> where T: AclStorage, U: KeyStorage { /// Secret store key server data.
pub struct KeyServerCore {
close: Option<futures::Complete<()>>,
handle: Option<thread::JoinHandle<()>>,
cluster: Option<Arc<ClusterClient>>,
}
impl KeyServerImpl {
/// Create new key server instance /// Create new key server instance
pub fn new(acl_storage: T, key_storage: U) -> Self { pub fn new(config: &ClusterConfiguration, acl_storage: Arc<AclStorage>, key_storage: Arc<KeyStorage>) -> Result<Self, Error> {
KeyServerImpl { Ok(KeyServerImpl {
acl_storage: acl_storage, data: Arc::new(Mutex::new(KeyServerCore::new(config, acl_storage, key_storage)?)),
key_storage: key_storage, })
} }
#[cfg(test)]
/// Get cluster client reference.
pub fn cluster(&self) -> Arc<ClusterClient> {
self.data.lock().cluster.clone()
.expect("cluster can be None in test cfg only; test cfg is for correct tests; qed")
} }
} }
impl<T, U> KeyServer for KeyServerImpl<T, U> where T: AclStorage, U: KeyStorage { impl KeyServer for KeyServerImpl {
fn generate_document_key(&self, signature: &RequestSignature, document: &DocumentAddress, threshold: usize) -> Result<DocumentEncryptedKey, Error> {
// recover requestor' public key from signature
let public = ethkey::recover(signature, document)
.map_err(|_| Error::BadSignature)?;
// generate document key
let data = self.data.lock();
let encryption_session = data.cluster.as_ref().expect("cluster can be None in test cfg only; test cfg is for correct tests; qed")
.new_encryption_session(document.clone(), threshold)?;
let document_key = encryption_session.wait()?;
// encrypt document key with requestor public key
let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key)
.map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?;
Ok(document_key)
}
fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> { fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> {
// recover requestor' public key from signature // recover requestor' public key from signature
let public = ethkey::recover(signature, document) let public = ethkey::recover(signature, document)
.map_err(|_| Error::BadSignature)?; .map_err(|_| Error::BadSignature)?;
// check that requestor has access to the document // decrypt document key
if !self.acl_storage.check(&public, document)? { let data = self.data.lock();
return Err(Error::AccessDenied); let decryption_session = data.cluster.as_ref().expect("cluster can be None in test cfg only; test cfg is for correct tests; qed")
} .new_decryption_session(document.clone(), signature.clone())?;
let document_key = decryption_session.wait()?;
// read unencrypted document key
let document_key = self.key_storage.get(document)?;
// encrypt document key with requestor public key // encrypt document key with requestor public key
let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key)
.map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?;
@ -57,68 +93,132 @@ impl<T, U> KeyServer for KeyServerImpl<T, U> where T: AclStorage, U: KeyStorage
} }
} }
impl KeyServerCore {
pub fn new(config: &ClusterConfiguration, acl_storage: Arc<AclStorage>, key_storage: Arc<KeyStorage>) -> Result<Self, Error> {
let config = NetClusterConfiguration {
threads: config.threads,
self_key_pair: ethkey::KeyPair::from_secret_slice(&config.self_private)?,
listen_address: (config.listener_address.address.clone(), config.listener_address.port),
nodes: config.nodes.iter()
.map(|(node_id, node_address)| (node_id.clone(), (node_address.address.clone(), node_address.port)))
.collect(),
allow_connecting_to_higher_nodes: config.allow_connecting_to_higher_nodes,
encryption_config: config.encryption_config.clone(),
acl_storage: acl_storage,
key_storage: key_storage,
};
let (stop, stopped) = futures::oneshot();
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let mut el = match Core::new() {
Ok(el) => el,
Err(e) => {
tx.send(Err(Error::Internal(format!("error initializing event loop: {}", e)))).expect("Rx is blocking upper thread.");
return;
},
};
let cluster = ClusterCore::new(el.handle(), config);
let cluster_client = cluster.and_then(|c| c.run().map(|_| c.client()));
tx.send(cluster_client.map_err(Into::into)).expect("Rx is blocking upper thread.");
let _ = el.run(futures::empty().select(stopped));
});
let cluster = rx.recv().map_err(|e| Error::Internal(format!("error initializing event loop: {}", e)))??;
Ok(KeyServerCore {
close: Some(stop),
handle: Some(handle),
cluster: Some(cluster),
})
}
}
impl Drop for KeyServerCore {
fn drop(&mut self) {
self.close.take().map(|v| v.send(()));
self.handle.take().map(|h| h.join());
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use std::time;
use std::sync::Arc;
use ethcrypto; use ethcrypto;
use ethkey::{self, Secret}; use ethkey::{self, Random, Generator};
use acl_storage::DummyAclStorage; use acl_storage::tests::DummyAclStorage;
use key_storage::KeyStorage;
use key_storage::tests::DummyKeyStorage; use key_storage::tests::DummyKeyStorage;
use super::super::{Error, RequestSignature, DocumentAddress}; use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey};
use super::super::{RequestSignature, DocumentAddress};
use super::{KeyServer, KeyServerImpl}; use super::{KeyServer, KeyServerImpl};
const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001"; const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001";
const DOCUMENT2: &'static str = "0000000000000000000000000000000000000000000000000000000000000002";
const KEY1: &'static str = "key1";
const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11"; const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11";
const PUBLIC2: &'static str = "dfe62f56bb05fbd85b485bac749f3410309e24b352bac082468ce151e9ddb94fa7b5b730027fe1c7c5f3d5927621d269f91aceb5caa3c7fe944677a22f88a318";
const PRIVATE2: &'static str = "0eb3816f4f705fa0fd952fb27b71b8c0606f09f4743b5b65cbc375bd569632f2";
fn create_key_server() -> KeyServerImpl<DummyAclStorage, DummyKeyStorage> {
let acl_storage = DummyAclStorage::default();
let key_storage = DummyKeyStorage::default();
key_storage.insert(DOCUMENT1.into(), KEY1.into()).unwrap();
acl_storage.prohibit(PUBLIC2.into(), DOCUMENT1.into());
KeyServerImpl::new(acl_storage, key_storage)
}
fn make_signature(secret: &str, document: &'static str) -> RequestSignature { fn make_signature(secret: &str, document: &'static str) -> RequestSignature {
let secret = Secret::from_str(secret).unwrap(); let secret = secret.parse().unwrap();
let document: DocumentAddress = document.into(); let document: DocumentAddress = document.into();
ethkey::sign(&secret, &document).unwrap() ethkey::sign(&secret, &document).unwrap()
} }
#[test] fn decrypt_document_key(secret: &str, document_key: DocumentEncryptedKey) -> DocumentKey {
fn document_key_succeeds() { let secret = secret.parse().unwrap();
let key_server = create_key_server(); ethcrypto::ecies::decrypt_single_message(&secret, &document_key).unwrap()
let signature = make_signature(PRIVATE1, DOCUMENT1);
let document_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap();
let document_key = ethcrypto::ecies::decrypt_single_message(&Secret::from_str(PRIVATE1).unwrap(), &document_key);
assert_eq!(document_key, Ok(KEY1.into()));
} }
#[test] #[test]
fn document_key_fails_when_bad_signature() { fn document_key_generation_and_retrievement_works_over_network() {
let key_server = create_key_server(); //::util::log::init_log();
let signature = RequestSignature::default();
let document_key = key_server.document_key(&signature, &DOCUMENT1.into());
assert_eq!(document_key, Err(Error::BadSignature));
}
#[test] let num_nodes = 3;
fn document_key_fails_when_acl_check_fails() { let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect();
let key_server = create_key_server(); let configs: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration {
let signature = make_signature(PRIVATE2, DOCUMENT1); threads: 1,
let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); self_private: (***key_pairs[i].secret()).into(),
assert_eq!(document_key, Err(Error::AccessDenied)); listener_address: NodeAddress {
} address: "127.0.0.1".into(),
port: 6060 + (i as u16),
},
nodes: key_pairs.iter().enumerate().map(|(j, kp)| (kp.public().clone(),
NodeAddress {
address: "127.0.0.1".into(),
port: 6060 + (j as u16),
})).collect(),
allow_connecting_to_higher_nodes: false,
encryption_config: EncryptionConfiguration {
key_check_timeout_ms: 10,
},
}).collect();
let key_servers: Vec<_> = configs.into_iter().map(|cfg|
KeyServerImpl::new(&cfg, Arc::new(DummyAclStorage::default()), Arc::new(DummyKeyStorage::default())).unwrap()
).collect();
#[test] // wait until connections are established
fn document_key_fails_when_document_not_found() { let start = time::Instant::now();
let key_server = create_key_server(); loop {
let signature = make_signature(PRIVATE1, DOCUMENT2); if key_servers.iter().all(|ks| ks.cluster().cluster_state().connected.len() == num_nodes - 1) {
let document_key = key_server.document_key(&signature, &DOCUMENT2.into()); break;
assert_eq!(document_key, Err(Error::DocumentNotFound)); }
if time::Instant::now() - start > time::Duration::from_millis(30000) {
panic!("connections are not established in 30000ms");
}
}
let test_cases = [0, 1, 2];
for threshold in &test_cases {
// generate document key
// TODO: it is an error that we can regenerate key for the same DOCUMENT
let signature = make_signature(PRIVATE1, DOCUMENT1);
let generated_key = key_servers[0].generate_document_key(&signature, &DOCUMENT1.into(), *threshold).unwrap();
let generated_key = decrypt_document_key(PRIVATE1, generated_key);
// now let's try to retrieve key back
for key_server in key_servers.iter() {
let retrieved_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap();
let retrieved_key = decrypt_document_key(PRIVATE1, retrieved_key);
assert_eq!(retrieved_key, generated_key);
}
}
} }
} }

View File

@ -14,11 +14,42 @@
// 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/>.
use key_server_cluster::{Error, NodeId}; use std::io;
use key_server_cluster::message::Message; use std::time;
use std::sync::Arc;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::collections::btree_map::Entry;
use std::net::{SocketAddr, IpAddr};
use futures::{finished, failed, Future, Stream, BoxFuture};
use futures_cpupool::CpuPool;
use parking_lot::{RwLock, Mutex};
use tokio_core::io::IoFuture;
use tokio_core::reactor::{Handle, Remote, Timeout, Interval};
use tokio_core::net::{TcpListener, TcpStream};
use ethkey::{Secret, KeyPair, Signature, Random, Generator};
use key_server_cluster::{Error, NodeId, SessionId, EncryptionConfiguration, AclStorage, KeyStorage};
use key_server_cluster::message::{self, Message, ClusterMessage, EncryptionMessage, DecryptionMessage};
use key_server_cluster::decryption_session::{SessionImpl as DecryptionSessionImpl, DecryptionSessionId,
SessionParams as DecryptionSessionParams, Session as DecryptionSession};
use key_server_cluster::encryption_session::{SessionImpl as EncryptionSessionImpl, SessionState as EncryptionSessionState,
SessionParams as EncryptionSessionParams, Session as EncryptionSession};
use key_server_cluster::io::{DeadlineStatus, ReadMessage, SharedTcpStream, read_encrypted_message, WriteMessage, write_encrypted_message};
use key_server_cluster::net::{accept_connection as net_accept_connection, connect as net_connect, Connection as NetConnection};
pub type BoxedEmptyFuture = BoxFuture<(), ()>;
/// Cluster interface for external clients.
pub trait ClusterClient: Send + Sync {
/// Get cluster state.
fn cluster_state(&self) -> ClusterState;
/// Start new encryption session.
fn new_encryption_session(&self, session_id: SessionId, threshold: usize) -> Result<Arc<EncryptionSession>, Error>;
/// Start new decryption session.
fn new_decryption_session(&self, session_id: SessionId, requestor_signature: Signature) -> Result<Arc<DecryptionSession>, Error>;
}
/// Cluster access for single encryption/decryption participant. /// Cluster access for single encryption/decryption participant.
pub trait Cluster { pub trait Cluster: Send + Sync {
/// Broadcast message to all other nodes. /// Broadcast message to all other nodes.
fn broadcast(&self, message: Message) -> Result<(), Error>; fn broadcast(&self, message: Message) -> Result<(), Error>;
/// Send message to given node. /// Send message to given node.
@ -27,13 +58,841 @@ pub trait Cluster {
fn blacklist(&self, node: &NodeId); fn blacklist(&self, node: &NodeId);
} }
#[derive(Clone)]
/// Cluster initialization parameters.
pub struct ClusterConfiguration {
/// Number of threads reserved by cluster.
pub threads: usize,
/// Allow connecting to 'higher' nodes.
pub allow_connecting_to_higher_nodes: bool,
/// KeyPair this node holds.
pub self_key_pair: KeyPair,
/// Interface to listen to.
pub listen_address: (String, u16),
/// Cluster nodes.
pub nodes: BTreeMap<NodeId, (String, u16)>,
/// Encryption session configuration.
pub encryption_config: EncryptionConfiguration,
/// Reference to key storage
pub key_storage: Arc<KeyStorage>,
/// Reference to ACL storage
pub acl_storage: Arc<AclStorage>,
}
/// Cluster state.
pub struct ClusterState {
/// Nodes, to which connections are established.
pub connected: BTreeSet<NodeId>,
}
/// Network cluster implementation.
pub struct ClusterCore {
/// Handle to the event loop.
handle: Handle,
/// Listen address.
listen_address: SocketAddr,
/// Cluster data.
data: Arc<ClusterData>,
}
/// Network cluster client interface implementation.
pub struct ClusterClientImpl {
/// Cluster data.
data: Arc<ClusterData>,
}
/// Network cluster view. It is a communication channel, required in single session.
pub struct ClusterView {
core: Arc<Mutex<ClusterViewCore>>,
}
/// Cross-thread shareable cluster data.
pub struct ClusterData {
/// Cluster configuration.
config: ClusterConfiguration,
/// Handle to the event loop.
handle: Remote,
/// Handle to the cpu thread pool.
pool: CpuPool,
/// KeyPair this node holds.
self_key_pair: KeyPair,
/// Connections data.
connections: ClusterConnections,
/// Active sessions data.
sessions: ClusterSessions,
}
/// Connections that are forming the cluster.
pub struct ClusterConnections {
/// Self node id.
pub self_node_id: NodeId,
/// All known other key servers.
pub nodes: BTreeMap<NodeId, SocketAddr>,
/// Active connections to key servers.
pub connections: RwLock<BTreeMap<NodeId, Arc<Connection>>>,
}
/// Active sessions on this cluster.
pub struct ClusterSessions {
/// Self node id.
pub self_node_id: NodeId,
/// Reference to key storage
pub key_storage: Arc<KeyStorage>,
/// Reference to ACL storage
pub acl_storage: Arc<AclStorage>,
/// Active encryption sessions.
pub encryption_sessions: RwLock<BTreeMap<SessionId, QueuedEncryptionSession>>,
/// Active decryption sessions.
pub decryption_sessions: RwLock<BTreeMap<DecryptionSessionId, QueuedDecryptionSession>>,
}
/// Encryption session and its message queue.
pub struct QueuedEncryptionSession {
/// Encryption session.
pub session: Arc<EncryptionSessionImpl>,
/// Messages queue.
pub queue: VecDeque<(NodeId, EncryptionMessage)>,
}
/// Decryption session and its message queue.
pub struct QueuedDecryptionSession {
/// Decryption session.
pub session: Arc<DecryptionSessionImpl>,
/// Messages queue.
pub queue: VecDeque<(NodeId, DecryptionMessage)>,
}
/// Cluster view core.
struct ClusterViewCore {
/// Cluster reference.
cluster: Arc<ClusterData>,
/// Subset of nodes, required for this session.
nodes: BTreeSet<NodeId>,
}
/// Connection to single node.
pub struct Connection {
/// Node id.
node_id: NodeId,
/// Node address.
node_address: SocketAddr,
/// Is inbound connection?
is_inbound: bool,
/// Tcp stream.
stream: SharedTcpStream,
/// Connection key.
key: Secret,
/// Last message time.
last_message_time: Mutex<time::Instant>,
}
impl ClusterCore {
pub fn new(handle: Handle, config: ClusterConfiguration) -> Result<Arc<Self>, Error> {
let listen_address = make_socket_address(&config.listen_address.0, config.listen_address.1)?;
let connections = ClusterConnections::new(&config)?;
let sessions = ClusterSessions::new(&config);
let data = ClusterData::new(&handle, config, connections, sessions);
Ok(Arc::new(ClusterCore {
handle: handle,
listen_address: listen_address,
data: data,
}))
}
/// Create new client interface.
pub fn client(&self) -> Arc<ClusterClient> {
Arc::new(ClusterClientImpl::new(self.data.clone()))
}
#[cfg(test)]
/// Get cluster configuration.
pub fn config(&self) -> &ClusterConfiguration {
&self.data.config
}
#[cfg(test)]
/// Get connection to given node.
pub fn connection(&self, node: &NodeId) -> Option<Arc<Connection>> {
self.data.connection(node)
}
/// Run cluster
pub fn run(&self) -> Result<(), Error> {
// try to connect to every other peer
ClusterCore::connect_disconnected_nodes(self.data.clone());
// schedule maintain procedures
ClusterCore::schedule_maintain(&self.handle, self.data.clone());
// start listening for incoming connections
self.handle.spawn(ClusterCore::listen(&self.handle, self.data.clone(), self.listen_address.clone())?);
Ok(())
}
/// Connect to peer.
fn connect(data: Arc<ClusterData>, node_address: SocketAddr) {
data.handle.clone().spawn(move |handle| {
data.pool.clone().spawn(ClusterCore::connect_future(handle, data, node_address))
})
}
/// Connect to socket using given context and handle.
fn connect_future(handle: &Handle, data: Arc<ClusterData>, node_address: SocketAddr) -> BoxedEmptyFuture {
let disconnected_nodes = data.connections.disconnected_nodes().keys().cloned().collect();
net_connect(&node_address, handle, data.self_key_pair.clone(), disconnected_nodes)
.then(move |result| ClusterCore::process_connection_result(data, false, result))
.then(|_| finished(()))
.boxed()
}
/// Start listening for incoming connections.
fn listen(handle: &Handle, data: Arc<ClusterData>, listen_address: SocketAddr) -> Result<BoxedEmptyFuture, Error> {
Ok(TcpListener::bind(&listen_address, &handle)?
.incoming()
.and_then(move |(stream, node_address)| {
ClusterCore::accept_connection(data.clone(), stream, node_address);
Ok(())
})
.for_each(|_| Ok(()))
.then(|_| finished(()))
.boxed())
}
/// Accept connection.
fn accept_connection(data: Arc<ClusterData>, stream: TcpStream, node_address: SocketAddr) {
data.handle.clone().spawn(move |handle| {
data.pool.clone().spawn(ClusterCore::accept_connection_future(handle, data, stream, node_address))
})
}
/// Accept connection future.
fn accept_connection_future(handle: &Handle, data: Arc<ClusterData>, stream: TcpStream, node_address: SocketAddr) -> BoxedEmptyFuture {
let disconnected_nodes = data.connections.disconnected_nodes().keys().cloned().collect();
net_accept_connection(node_address, stream, handle, data.self_key_pair.clone(), disconnected_nodes)
.then(move |result| ClusterCore::process_connection_result(data, true, result))
.then(|_| finished(()))
.boxed()
}
/// Schedule mainatain procedures.
fn schedule_maintain(handle: &Handle, data: Arc<ClusterData>) {
// TODO: per-session timeouts (node can respond to messages, but ignore sessions messages)
let (d1, d2, d3) = (data.clone(), data.clone(), data.clone());
let interval: BoxedEmptyFuture = Interval::new(time::Duration::new(10, 0), handle)
.expect("failed to create interval")
.and_then(move |_| Ok(trace!(target: "secretstore_net", "{}: executing maintain procedures", d1.self_key_pair.public())))
.and_then(move |_| Ok(ClusterCore::keep_alive(d2.clone())))
.and_then(move |_| Ok(ClusterCore::connect_disconnected_nodes(d3.clone())))
.for_each(|_| Ok(()))
.then(|_| finished(()))
.boxed();
data.spawn(interval);
}
/// Called for every incomming mesage.
fn process_connection_messages(data: Arc<ClusterData>, connection: Arc<Connection>) -> IoFuture<Result<(), Error>> {
connection
.read_message()
.then(move |result|
match result {
Ok((_, Ok(message))) => {
ClusterCore::process_connection_message(data.clone(), connection.clone(), message);
// continue serving connection
data.spawn(ClusterCore::process_connection_messages(data.clone(), connection));
finished(Ok(())).boxed()
},
Ok((_, Err(err))) => {
warn!(target: "secretstore_net", "{}: protocol error {} when reading message from node {}", data.self_key_pair.public(), err, connection.node_id());
// continue serving connection
data.spawn(ClusterCore::process_connection_messages(data.clone(), connection));
finished(Err(err)).boxed()
},
Err(err) => {
warn!(target: "secretstore_net", "{}: network error {} when reading message from node {}", data.self_key_pair.public(), err, connection.node_id());
// close connection
data.connections.remove(connection.node_id(), connection.is_inbound());
failed(err).boxed()
},
}
).boxed()
}
/// Send keepalive messages to every othe node.
fn keep_alive(data: Arc<ClusterData>) {
for connection in data.connections.active_connections() {
let last_message_diff = time::Instant::now() - connection.last_message_time();
if last_message_diff > time::Duration::from_secs(60) {
data.connections.remove(connection.node_id(), connection.is_inbound());
data.sessions.on_connection_timeout(connection.node_id());
}
else if last_message_diff > time::Duration::from_secs(30) {
data.spawn(connection.send_message(Message::Cluster(ClusterMessage::KeepAlive(message::KeepAlive {}))));
}
}
}
/// Try to connect to every disconnected node.
fn connect_disconnected_nodes(data: Arc<ClusterData>) {
for (node_id, node_address) in data.connections.disconnected_nodes() {
if data.config.allow_connecting_to_higher_nodes || data.self_key_pair.public() < &node_id {
ClusterCore::connect(data.clone(), node_address);
}
}
}
/// Process connection future result.
fn process_connection_result(data: Arc<ClusterData>, is_inbound: bool, result: Result<DeadlineStatus<Result<NetConnection, Error>>, io::Error>) -> IoFuture<Result<(), Error>> {
match result {
Ok(DeadlineStatus::Meet(Ok(connection))) => {
let connection = Connection::new(is_inbound, connection);
if data.connections.insert(connection.clone()) {
ClusterCore::process_connection_messages(data.clone(), connection)
} else {
finished(Ok(())).boxed()
}
},
Ok(DeadlineStatus::Meet(Err(_))) => {
finished(Ok(())).boxed()
},
Ok(DeadlineStatus::Timeout) => {
finished(Ok(())).boxed()
},
Err(_) => {
// network error
finished(Ok(())).boxed()
},
}
}
/// Process single message from the connection.
fn process_connection_message(data: Arc<ClusterData>, connection: Arc<Connection>, message: Message) {
connection.set_last_message_time(time::Instant::now());
trace!(target: "secretstore_net", "{}: processing message {} from {}", data.self_key_pair.public(), message, connection.node_id());
match message {
Message::Encryption(message) => ClusterCore::process_encryption_message(data, connection, message),
Message::Decryption(message) => ClusterCore::process_decryption_message(data, connection, message),
Message::Cluster(message) => ClusterCore::process_cluster_message(data, connection, message),
}
}
/// Process single encryption message from the connection.
fn process_encryption_message(data: Arc<ClusterData>, connection: Arc<Connection>, mut message: EncryptionMessage) {
let mut sender = connection.node_id().clone();
let mut is_queued_message = false;
let session_id = message.session_id().clone();
let key_check_timeout_ms = data.config.encryption_config.key_check_timeout_ms;
loop {
let result = match message {
EncryptionMessage::InitializeSession(ref message) => {
let mut connected_nodes = data.connections.connected_nodes();
connected_nodes.insert(data.self_key_pair.public().clone());
let cluster = Arc::new(ClusterView::new(data.clone(), connected_nodes));
let session_id: SessionId = message.session.clone().into();
data.sessions.new_encryption_session(sender.clone(), session_id.clone(), cluster)
.and_then(|s| s.on_initialize_session(sender.clone(), message))
},
EncryptionMessage::ConfirmInitialization(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_confirm_initialization(sender.clone(), message)),
EncryptionMessage::CompleteInitialization(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_complete_initialization(sender.clone(), message)),
EncryptionMessage::KeysDissemination(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| {
// TODO: move this logic to session (or session connector)
let is_in_key_check_state = s.state() == EncryptionSessionState::KeyCheck;
let result = s.on_keys_dissemination(sender.clone(), message);
if !is_in_key_check_state && s.state() == EncryptionSessionState::KeyCheck {
let session = s.clone();
let d = data.clone();
data.handle.spawn(move |handle|
Timeout::new(time::Duration::new(key_check_timeout_ms / 1000, 0), handle)
.expect("failed to create timeout")
.and_then(move |_| {
if let Err(error) = session.start_key_generation_phase() {
session.on_session_error(d.self_key_pair.public().clone(), &message::SessionError {
session: session.id().clone().into(),
error: error.into(),
});
}
Ok(())
})
.then(|_| finished(()))
);
}
result
}),
EncryptionMessage::Complaint(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_complaint(sender.clone(), message)),
EncryptionMessage::ComplaintResponse(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_complaint_response(sender.clone(), message)),
EncryptionMessage::PublicKeyShare(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_public_key_share(sender.clone(), message)),
EncryptionMessage::SessionError(ref message) => {
if let Some(s) = data.sessions.encryption_session(&*message.session) {
data.sessions.remove_encryption_session(s.id());
s.on_session_error(sender.clone(), message);
}
Ok(())
},
EncryptionMessage::SessionCompleted(ref message) => data.sessions.encryption_session(&*message.session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| {
let result = s.on_session_completed(sender.clone(), message);
if result.is_ok() && s.state() == EncryptionSessionState::Finished {
data.sessions.remove_encryption_session(s.id());
}
result
}),
};
match result {
Err(Error::TooEarlyForRequest) => {
data.sessions.enqueue_encryption_message(&session_id, sender, message, is_queued_message);
break;
},
Err(err) => {
warn!(target: "secretstore_net", "{}: error {} when processing message {} from node {}", data.self_key_pair.public(), err, message, sender);
if let Some(connection) = data.connections.get(&sender) {
data.spawn(connection.send_message(Message::Encryption(EncryptionMessage::SessionError(message::SessionError {
session: session_id.clone().into(),
error: format!("{:?}", err),
}))));
}
if err != Error::InvalidSessionId {
data.sessions.remove_encryption_session(&session_id);
}
break;
},
_ => {
match data.sessions.dequeue_encryption_message(&session_id) {
Some((msg_sender, msg)) => {
is_queued_message = true;
sender = msg_sender;
message = msg;
},
None => break,
}
},
}
}
}
/// Process single decryption message from the connection.
fn process_decryption_message(data: Arc<ClusterData>, connection: Arc<Connection>, mut message: DecryptionMessage) {
let mut sender = connection.node_id().clone();
let mut is_queued_message = false;
let session_id = message.session_id().clone();
let sub_session_id = message.sub_session_id().clone();
loop {
let result = match message {
DecryptionMessage::InitializeDecryptionSession(ref message) => {
let mut connected_nodes = data.connections.connected_nodes();
connected_nodes.insert(data.self_key_pair.public().clone());
let cluster = Arc::new(ClusterView::new(data.clone(), connected_nodes));
data.sessions.new_decryption_session(sender.clone(), session_id.clone(), sub_session_id.clone(), cluster)
.and_then(|s| s.on_initialize_session(sender.clone(), message))
},
DecryptionMessage::ConfirmDecryptionInitialization(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_confirm_initialization(sender.clone(), message)),
DecryptionMessage::RequestPartialDecryption(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_partial_decryption_requested(sender.clone(), message)),
DecryptionMessage::PartialDecryption(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session)
.ok_or(Error::InvalidSessionId)
.and_then(|s| s.on_partial_decryption(sender.clone(), message)),
DecryptionMessage::DecryptionSessionError(ref message) => {
if let Some(s) = data.sessions.decryption_session(&*message.session, &*message.sub_session) {
data.sessions.remove_decryption_session(&session_id, &sub_session_id);
s.on_session_error(sender.clone(), message);
}
Ok(())
},
};
match result {
Err(Error::TooEarlyForRequest) => {
data.sessions.enqueue_decryption_message(&session_id, &sub_session_id, sender, message, is_queued_message);
break;
},
Err(err) => {
if let Some(connection) = data.connections.get(&sender) {
data.spawn(connection.send_message(Message::Decryption(DecryptionMessage::DecryptionSessionError(message::DecryptionSessionError {
session: session_id.clone().into(),
sub_session: sub_session_id.clone().into(),
error: format!("{:?}", err),
}))));
}
if err != Error::InvalidSessionId {
data.sessions.remove_decryption_session(&session_id, &sub_session_id);
}
break;
},
_ => {
match data.sessions.dequeue_decryption_message(&session_id, &sub_session_id) {
Some((msg_sender, msg)) => {
is_queued_message = true;
sender = msg_sender;
message = msg;
},
None => break,
}
},
}
}
}
/// Process single cluster message from the connection.
fn process_cluster_message(data: Arc<ClusterData>, connection: Arc<Connection>, message: ClusterMessage) {
match message {
ClusterMessage::KeepAlive(_) => data.spawn(connection.send_message(Message::Cluster(ClusterMessage::KeepAliveResponse(message::KeepAliveResponse {})))),
ClusterMessage::KeepAliveResponse(_) => (),
_ => warn!(target: "secretstore_net", "{}: received unexpected message {} from node {} at {}", data.self_key_pair.public(), message, connection.node_id(), connection.node_address()),
}
}
}
impl ClusterConnections {
pub fn new(config: &ClusterConfiguration) -> Result<Self, Error> {
let mut connections = ClusterConnections {
self_node_id: config.self_key_pair.public().clone(),
nodes: BTreeMap::new(),
connections: RwLock::new(BTreeMap::new()),
};
for (node_id, &(ref node_addr, node_port)) in config.nodes.iter().filter(|&(node_id, _)| node_id != config.self_key_pair.public()) {
let socket_address = make_socket_address(&node_addr, node_port)?;
connections.nodes.insert(node_id.clone(), socket_address);
}
Ok(connections)
}
pub fn cluster_state(&self) -> ClusterState {
ClusterState {
connected: self.connections.read().keys().cloned().collect(),
}
}
pub fn get(&self, node: &NodeId) -> Option<Arc<Connection>> {
self.connections.read().get(node).cloned()
}
pub fn insert(&self, connection: Arc<Connection>) -> bool {
let mut connections = self.connections.write();
if connections.contains_key(connection.node_id()) {
// we have already connected to the same node
// the agreement is that node with lower id must establish connection to node with higher id
if (&self.self_node_id < connection.node_id() && connection.is_inbound())
|| (&self.self_node_id > connection.node_id() && !connection.is_inbound()) {
return false;
}
}
trace!(target: "secretstore_net", "{}: inserting connection to {} at {}", self.self_node_id, connection.node_id(), connection.node_address());
connections.insert(connection.node_id().clone(), connection);
true
}
pub fn remove(&self, node: &NodeId, is_inbound: bool) {
let mut connections = self.connections.write();
if let Entry::Occupied(entry) = connections.entry(node.clone()) {
if entry.get().is_inbound() != is_inbound {
return;
}
trace!(target: "secretstore_net", "{}: removing connection to {} at {}", self.self_node_id, entry.get().node_id(), entry.get().node_address());
entry.remove_entry();
}
}
pub fn connected_nodes(&self) -> BTreeSet<NodeId> {
self.connections.read().keys().cloned().collect()
}
pub fn active_connections(&self)-> Vec<Arc<Connection>> {
self.connections.read().values().cloned().collect()
}
pub fn disconnected_nodes(&self) -> BTreeMap<NodeId, SocketAddr> {
let connections = self.connections.read();
self.nodes.iter()
.filter(|&(node_id, _)| !connections.contains_key(node_id))
.map(|(node_id, node_address)| (node_id.clone(), node_address.clone()))
.collect()
}
}
impl ClusterSessions {
pub fn new(config: &ClusterConfiguration) -> Self {
ClusterSessions {
self_node_id: config.self_key_pair.public().clone(),
acl_storage: config.acl_storage.clone(),
key_storage: config.key_storage.clone(),
encryption_sessions: RwLock::new(BTreeMap::new()),
decryption_sessions: RwLock::new(BTreeMap::new()),
}
}
pub fn new_encryption_session(&self, _master: NodeId, session_id: SessionId, cluster: Arc<Cluster>) -> Result<Arc<EncryptionSessionImpl>, Error> {
let mut encryption_sessions = self.encryption_sessions.write();
if encryption_sessions.contains_key(&session_id) {
return Err(Error::DuplicateSessionId);
}
let session = Arc::new(EncryptionSessionImpl::new(EncryptionSessionParams {
id: session_id.clone(),
self_node_id: self.self_node_id.clone(),
key_storage: self.key_storage.clone(),
cluster: cluster,
}));
let encryption_session = QueuedEncryptionSession {
session: session.clone(),
queue: VecDeque::new()
};
encryption_sessions.insert(session_id, encryption_session);
Ok(session)
}
pub fn remove_encryption_session(&self, session_id: &SessionId) {
self.encryption_sessions.write().remove(session_id);
}
pub fn encryption_session(&self, session_id: &SessionId) -> Option<Arc<EncryptionSessionImpl>> {
self.encryption_sessions.read().get(session_id).map(|s| s.session.clone())
}
pub fn enqueue_encryption_message(&self, session_id: &SessionId, sender: NodeId, message: EncryptionMessage, is_queued_message: bool) {
self.encryption_sessions.write().get_mut(session_id)
.map(|session| if is_queued_message { session.queue.push_front((sender, message)) }
else { session.queue.push_back((sender, message)) });
}
pub fn dequeue_encryption_message(&self, session_id: &SessionId) -> Option<(NodeId, EncryptionMessage)> {
self.encryption_sessions.write().get_mut(session_id)
.and_then(|session| session.queue.pop_front())
}
pub fn new_decryption_session(&self, _master: NodeId, session_id: SessionId, sub_session_id: Secret, cluster: Arc<Cluster>) -> Result<Arc<DecryptionSessionImpl>, Error> {
let mut decryption_sessions = self.decryption_sessions.write();
let session_id = DecryptionSessionId::new(session_id, sub_session_id);
if decryption_sessions.contains_key(&session_id) {
return Err(Error::DuplicateSessionId);
}
let session = Arc::new(DecryptionSessionImpl::new(DecryptionSessionParams {
id: session_id.id.clone(),
access_key: session_id.access_key.clone(),
self_node_id: self.self_node_id.clone(),
encrypted_data: self.key_storage.get(&session_id.id).map_err(|e| Error::KeyStorage(e.into()))?,
acl_storage: self.acl_storage.clone(),
cluster: cluster,
})?);
let decryption_session = QueuedDecryptionSession {
session: session.clone(),
queue: VecDeque::new()
};
decryption_sessions.insert(session_id, decryption_session);
Ok(session)
}
pub fn remove_decryption_session(&self, session_id: &SessionId, sub_session_id: &Secret) {
let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone());
self.decryption_sessions.write().remove(&session_id);
}
pub fn decryption_session(&self, session_id: &SessionId, sub_session_id: &Secret) -> Option<Arc<DecryptionSessionImpl>> {
let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone());
self.decryption_sessions.read().get(&session_id).map(|s| s.session.clone())
}
pub fn enqueue_decryption_message(&self, session_id: &SessionId, sub_session_id: &Secret, sender: NodeId, message: DecryptionMessage, is_queued_message: bool) {
let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone());
self.decryption_sessions.write().get_mut(&session_id)
.map(|session| if is_queued_message { session.queue.push_front((sender, message)) }
else { session.queue.push_back((sender, message)) });
}
pub fn dequeue_decryption_message(&self, session_id: &SessionId, sub_session_id: &Secret) -> Option<(NodeId, DecryptionMessage)> {
let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone());
self.decryption_sessions.write().get_mut(&session_id)
.and_then(|session| session.queue.pop_front())
}
pub fn on_connection_timeout(&self, node_id: &NodeId) {
for encryption_session in self.encryption_sessions.read().values() {
encryption_session.session.on_session_timeout(node_id);
}
for decryption_session in self.decryption_sessions.read().values() {
decryption_session.session.on_session_timeout(node_id);
}
}
}
impl ClusterData {
pub fn new(handle: &Handle, config: ClusterConfiguration, connections: ClusterConnections, sessions: ClusterSessions) -> Arc<Self> {
Arc::new(ClusterData {
handle: handle.remote().clone(),
pool: CpuPool::new(config.threads),
self_key_pair: config.self_key_pair.clone(),
connections: connections,
sessions: sessions,
config: config,
})
}
/// Get connection to given node.
pub fn connection(&self, node: &NodeId) -> Option<Arc<Connection>> {
self.connections.get(node)
}
/// Spawns a future using thread pool and schedules execution of it with event loop handle.
pub fn spawn<F>(&self, f: F) where F: Future + Send + 'static, F::Item: Send + 'static, F::Error: Send + 'static {
let pool_work = self.pool.spawn(f);
self.handle.spawn(move |_handle| {
pool_work.then(|_| finished(()))
})
}
}
impl Connection {
pub fn new(is_inbound: bool, connection: NetConnection) -> Arc<Connection> {
Arc::new(Connection {
node_id: connection.node_id,
node_address: connection.address,
is_inbound: is_inbound,
stream: connection.stream,
key: connection.key,
last_message_time: Mutex::new(time::Instant::now()),
})
}
pub fn is_inbound(&self) -> bool {
self.is_inbound
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn last_message_time(&self) -> time::Instant {
*self.last_message_time.lock()
}
pub fn set_last_message_time(&self, last_message_time: time::Instant) {
*self.last_message_time.lock() = last_message_time;
}
pub fn node_address(&self) -> &SocketAddr {
&self.node_address
}
pub fn send_message(&self, message: Message) -> WriteMessage<SharedTcpStream> {
write_encrypted_message(self.stream.clone(), &self.key, message)
}
pub fn read_message(&self) -> ReadMessage<SharedTcpStream> {
read_encrypted_message(self.stream.clone(), self.key.clone())
}
}
impl ClusterView {
pub fn new(cluster: Arc<ClusterData>, nodes: BTreeSet<NodeId>) -> Self {
ClusterView {
core: Arc::new(Mutex::new(ClusterViewCore {
cluster: cluster,
nodes: nodes,
})),
}
}
}
impl Cluster for ClusterView {
fn broadcast(&self, message: Message) -> Result<(), Error> {
let core = self.core.lock();
for node in core.nodes.iter().filter(|n| *n != core.cluster.self_key_pair.public()) {
let connection = core.cluster.connection(node).ok_or(Error::NodeDisconnected)?;
core.cluster.spawn(connection.send_message(message.clone()))
}
Ok(())
}
fn send(&self, to: &NodeId, message: Message) -> Result<(), Error> {
let core = self.core.lock();
let connection = core.cluster.connection(to).ok_or(Error::NodeDisconnected)?;
core.cluster.spawn(connection.send_message(message));
Ok(())
}
fn blacklist(&self, _node: &NodeId) {
// TODO: unimplemented!()
}
}
impl ClusterClientImpl {
pub fn new(data: Arc<ClusterData>) -> Self {
ClusterClientImpl {
data: data,
}
}
}
impl ClusterClient for ClusterClientImpl {
fn cluster_state(&self) -> ClusterState {
self.data.connections.cluster_state()
}
fn new_encryption_session(&self, session_id: SessionId, threshold: usize) -> Result<Arc<EncryptionSession>, Error> {
let mut connected_nodes = self.data.connections.connected_nodes();
connected_nodes.insert(self.data.self_key_pair.public().clone());
let cluster = Arc::new(ClusterView::new(self.data.clone(), connected_nodes.clone()));
let session = self.data.sessions.new_encryption_session(self.data.self_key_pair.public().clone(), session_id, cluster)?;
session.initialize(threshold, connected_nodes)?;
Ok(session)
}
fn new_decryption_session(&self, session_id: SessionId, requestor_signature: Signature) -> Result<Arc<DecryptionSession>, Error> {
let mut connected_nodes = self.data.connections.connected_nodes();
connected_nodes.insert(self.data.self_key_pair.public().clone());
let access_key = Random.generate()?.secret().clone();
let cluster = Arc::new(ClusterView::new(self.data.clone(), connected_nodes.clone()));
let session = self.data.sessions.new_decryption_session(self.data.self_key_pair.public().clone(), session_id, access_key, cluster)?;
session.initialize(requestor_signature)?;
Ok(session)
}
}
fn make_socket_address(address: &str, port: u16) -> Result<SocketAddr, Error> {
let ip_address: IpAddr = address.parse().map_err(|_| Error::InvalidNodeAddress)?;
Ok(SocketAddr::new(ip_address, port))
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use std::sync::Arc;
use std::time;
use std::collections::VecDeque; use std::collections::VecDeque;
use parking_lot::Mutex; use parking_lot::Mutex;
use key_server_cluster::{NodeId, Error}; use tokio_core::reactor::Core;
use ethkey::{Random, Generator};
use key_server_cluster::{NodeId, Error, EncryptionConfiguration, DummyAclStorage, DummyKeyStorage};
use key_server_cluster::message::Message; use key_server_cluster::message::Message;
use key_server_cluster::cluster::Cluster; use key_server_cluster::cluster::{Cluster, ClusterCore, ClusterConfiguration};
#[derive(Debug)] #[derive(Debug)]
pub struct DummyCluster { pub struct DummyCluster {
@ -87,4 +946,61 @@ pub mod tests {
fn blacklist(&self, _node: &NodeId) { fn blacklist(&self, _node: &NodeId) {
} }
} }
pub fn loop_until<F>(core: &mut Core, timeout: time::Duration, predicate: F) where F: Fn() -> bool {
let start = time::Instant::now();
loop {
core.turn(Some(time::Duration::from_millis(1)));
if predicate() {
break;
}
if time::Instant::now() - start > timeout {
panic!("no result in {:?}", timeout);
}
}
}
pub fn all_connections_established(cluster: &Arc<ClusterCore>) -> bool {
cluster.config().nodes.keys()
.filter(|p| *p != cluster.config().self_key_pair.public())
.all(|p| cluster.connection(p).is_some())
}
pub fn make_clusters(core: &Core, ports_begin: u16, num_nodes: usize) -> Vec<Arc<ClusterCore>> {
let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect();
let cluster_params: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration {
threads: 1,
self_key_pair: key_pairs[i].clone(),
listen_address: ("127.0.0.1".to_owned(), ports_begin + i as u16),
nodes: key_pairs.iter().enumerate()
.map(|(j, kp)| (kp.public().clone(), ("127.0.0.1".into(), ports_begin + j as u16)))
.collect(),
allow_connecting_to_higher_nodes: false,
encryption_config: EncryptionConfiguration {
key_check_timeout_ms: 10,
},
key_storage: Arc::new(DummyKeyStorage::default()),
acl_storage: Arc::new(DummyAclStorage::default()),
}).collect();
let clusters: Vec<_> = cluster_params.into_iter().enumerate()
.map(|(_, params)| ClusterCore::new(core.handle(), params).unwrap())
.collect();
clusters
}
pub fn run_clusters(clusters: &[Arc<ClusterCore>]) {
for cluster in clusters {
cluster.run().unwrap();
}
}
#[test]
fn cluster_connects_to_other_nodes() {
let mut core = Core::new().unwrap();
let clusters = make_clusters(&core, 6010, 3);
run_clusters(&clusters);
loop_until(&mut core, time::Duration::from_millis(300), || clusters.iter().all(all_connections_established));
}
} }

View File

@ -14,15 +14,22 @@
// 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/>.
use std::cmp::{Ord, PartialOrd, Ordering};
use std::collections::{BTreeSet, BTreeMap}; use std::collections::{BTreeSet, BTreeMap};
use std::sync::Arc; use std::sync::Arc;
use parking_lot::Mutex; use parking_lot::{Mutex, Condvar};
use ethkey::{self, Secret, Public, Signature}; use ethkey::{self, Secret, Public, Signature};
use key_server_cluster::{Error, AclStorage, EncryptedData, NodeId, SessionId}; use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId};
use key_server_cluster::cluster::Cluster; use key_server_cluster::cluster::Cluster;
use key_server_cluster::math; use key_server_cluster::math;
use key_server_cluster::message::{Message, InitializeDecryptionSession, ConfirmDecryptionInitialization, use key_server_cluster::message::{Message, DecryptionMessage, InitializeDecryptionSession, ConfirmDecryptionInitialization,
RequestPartialDecryption, PartialDecryption}; RequestPartialDecryption, PartialDecryption, DecryptionSessionError};
/// Decryption session API.
pub trait Session: Send + Sync + 'static {
/// Wait until session is completed. Returns distributely restored secret key.
fn wait(&self) -> Result<Public, Error>;
}
/// Distributed decryption session. /// Distributed decryption session.
/// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper: /// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper:
@ -32,7 +39,7 @@ use key_server_cluster::message::{Message, InitializeDecryptionSession, ConfirmD
/// 2) ACL check: all nodes which have received the request are querying ACL-contract to check if requestor has access to the document /// 2) ACL check: all nodes which have received the request are querying ACL-contract to check if requestor has access to the document
/// 3) partial decryption: every node which has succussfully checked access for the requestor do a partial decryption /// 3) partial decryption: every node which has succussfully checked access for the requestor do a partial decryption
/// 4) decryption: master node receives all partial decryptions of the secret and restores the secret /// 4) decryption: master node receives all partial decryptions of the secret and restores the secret
pub struct Session { pub struct SessionImpl {
/// Encryption session id. /// Encryption session id.
id: SessionId, id: SessionId,
/// Decryption session access key. /// Decryption session access key.
@ -40,25 +47,36 @@ pub struct Session {
/// Public identifier of this node. /// Public identifier of this node.
self_node_id: NodeId, self_node_id: NodeId,
/// Encrypted data. /// Encrypted data.
encrypted_data: EncryptedData, encrypted_data: DocumentKeyShare,
/// ACL storate to check access to the resource. /// ACL storate to check access to the resource.
acl_storage: Arc<AclStorage>, acl_storage: Arc<AclStorage>,
/// Cluster which allows this node to send messages to other nodes in the cluster. /// Cluster which allows this node to send messages to other nodes in the cluster.
cluster: Arc<Cluster>, cluster: Arc<Cluster>,
/// SessionImpl completion condvar.
completed: Condvar,
/// Mutable session data. /// Mutable session data.
data: Mutex<SessionData>, data: Mutex<SessionData>,
} }
/// Session creation parameters /// Decryption session Id.
pub struct SessionParams { #[derive(Debug, Clone, PartialEq, Eq)]
/// Session identifier. pub struct DecryptionSessionId {
/// Encryption session id.
pub id: SessionId, pub id: SessionId,
/// Session access key. /// Decryption session access key.
pub access_key: Secret,
}
/// SessionImpl creation parameters
pub struct SessionParams {
/// SessionImpl identifier.
pub id: SessionId,
/// SessionImpl access key.
pub access_key: Secret, pub access_key: Secret,
/// Id of node, on which this session is running. /// Id of node, on which this session is running.
pub self_node_id: Public, pub self_node_id: Public,
/// Encrypted data (result of running encryption_session::Session). /// Encrypted data (result of running encryption_session::SessionImpl).
pub encrypted_data: EncryptedData, pub encrypted_data: DocumentKeyShare,
/// ACL storage. /// ACL storage.
pub acl_storage: Arc<AclStorage>, pub acl_storage: Arc<AclStorage>,
/// Cluster /// Cluster
@ -91,16 +109,11 @@ struct SessionData {
/// === Values, filled during final decryption === /// === Values, filled during final decryption ===
/// Decrypted secret /// Decrypted secret
decrypted_secret: Option<Public>, decrypted_secret: Option<Result<Public, Error>>,
}
#[derive(Debug)]
struct NodeData {
/// Node-generated shadow point.
shadow_point: Option<Public>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
/// Decryption session data.
pub enum SessionState { pub enum SessionState {
/// Every node starts in this state. /// Every node starts in this state.
WaitingForInitialization, WaitingForInitialization,
@ -116,18 +129,19 @@ pub enum SessionState {
Failed, Failed,
} }
impl Session { impl SessionImpl {
/// Create new decryption session. /// Create new decryption session.
pub fn new(params: SessionParams) -> Result<Self, Error> { pub fn new(params: SessionParams) -> Result<Self, Error> {
check_encrypted_data(&params.self_node_id, &params.encrypted_data)?; check_encrypted_data(&params.self_node_id, &params.encrypted_data)?;
Ok(Session { Ok(SessionImpl {
id: params.id, id: params.id,
access_key: params.access_key, access_key: params.access_key,
self_node_id: params.self_node_id, self_node_id: params.self_node_id,
encrypted_data: params.encrypted_data, encrypted_data: params.encrypted_data,
acl_storage: params.acl_storage, acl_storage: params.acl_storage,
cluster: params.cluster, cluster: params.cluster,
completed: Condvar::new(),
data: Mutex::new(SessionData { data: Mutex::new(SessionData {
state: SessionState::WaitingForInitialization, state: SessionState::WaitingForInitialization,
master: None, master: None,
@ -146,19 +160,22 @@ impl Session {
&self.self_node_id &self.self_node_id
} }
#[cfg(test)]
/// Get this session access key. /// Get this session access key.
pub fn access_key(&self) -> &Secret { pub fn access_key(&self) -> &Secret {
&self.access_key &self.access_key
} }
#[cfg(test)]
/// Get current session state. /// Get current session state.
pub fn state(&self) -> SessionState { pub fn state(&self) -> SessionState {
self.data.lock().state.clone() self.data.lock().state.clone()
} }
#[cfg(test)]
/// Get decrypted secret /// Get decrypted secret
pub fn decrypted_secret(&self) -> Option<Public> { pub fn decrypted_secret(&self) -> Option<Public> {
self.data.lock().decrypted_secret.clone() self.data.lock().decrypted_secret.clone().and_then(|r| r.ok())
} }
/// Initialize decryption session. /// Initialize decryption session.
@ -188,17 +205,22 @@ impl Session {
// not enough nodes => pass initialization message to all other nodes // not enough nodes => pass initialization message to all other nodes
SessionState::WaitingForInitializationConfirm => { SessionState::WaitingForInitializationConfirm => {
for node in self.encrypted_data.id_numbers.keys().filter(|n| *n != self.node()) { for node in self.encrypted_data.id_numbers.keys().filter(|n| *n != self.node()) {
self.cluster.send(node, Message::InitializeDecryptionSession(InitializeDecryptionSession { self.cluster.send(node, Message::Decryption(DecryptionMessage::InitializeDecryptionSession(InitializeDecryptionSession {
session: self.id.clone(), session: self.id.clone().into(),
sub_session: self.access_key.clone(), sub_session: self.access_key.clone().into(),
requestor_signature: requestor_signature.clone(), requestor_signature: requestor_signature.clone().into(),
}))?; })))?;
} }
}, },
// we can decrypt data on our own // we can decrypt data on our own
SessionState::WaitingForPartialDecryption => unimplemented!(), SessionState::WaitingForPartialDecryption => {
data.confirmed_nodes.insert(self.node().clone());
SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data)?;
SessionImpl::do_decryption(self.access_key.clone(), &self.encrypted_data, &mut *data)?;
self.completed.notify_all();
},
// we can not decrypt data // we can not decrypt data
SessionState::Failed => (), SessionState::Failed => self.completed.notify_all(),
// cannot reach other states // cannot reach other states
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
} }
@ -207,9 +229,9 @@ impl Session {
} }
/// When session initialization message is received. /// When session initialization message is received.
pub fn on_initialize_session(&self, sender: NodeId, message: InitializeDecryptionSession) -> Result<(), Error> { pub fn on_initialize_session(&self, sender: NodeId, message: &InitializeDecryptionSession) -> Result<(), Error> {
debug_assert!(self.id == message.session); debug_assert!(self.id == *message.session);
debug_assert!(self.access_key == message.sub_session); debug_assert!(self.access_key == *message.sub_session);
debug_assert!(&sender != self.node()); debug_assert!(&sender != self.node());
let mut data = self.data.lock(); let mut data = self.data.lock();
@ -230,17 +252,17 @@ impl Session {
// respond to master node // respond to master node
data.master = Some(sender.clone()); data.master = Some(sender.clone());
self.cluster.send(&sender, Message::ConfirmDecryptionInitialization(ConfirmDecryptionInitialization { self.cluster.send(&sender, Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(ConfirmDecryptionInitialization {
session: self.id.clone(), session: self.id.clone().into(),
sub_session: self.access_key.clone(), sub_session: self.access_key.clone().into(),
is_confirmed: is_requestor_allowed_to_read, is_confirmed: is_requestor_allowed_to_read,
})) })))
} }
/// When session initialization confirmation message is reeived. /// When session initialization confirmation message is reeived.
pub fn on_confirm_initialization(&self, sender: NodeId, message: ConfirmDecryptionInitialization) -> Result<(), Error> { pub fn on_confirm_initialization(&self, sender: NodeId, message: &ConfirmDecryptionInitialization) -> Result<(), Error> {
debug_assert!(self.id == message.session); debug_assert!(self.id == *message.session);
debug_assert!(self.access_key == message.sub_session); debug_assert!(self.access_key == *message.sub_session);
debug_assert!(&sender != self.node()); debug_assert!(&sender != self.node());
let mut data = self.data.lock(); let mut data = self.data.lock();
@ -260,37 +282,22 @@ impl Session {
// we do not yet have enough nodes for decryption // we do not yet have enough nodes for decryption
SessionState::WaitingForInitializationConfirm => Ok(()), SessionState::WaitingForInitializationConfirm => Ok(()),
// we have enough nodes for decryption // we have enough nodes for decryption
SessionState::WaitingForPartialDecryption => { SessionState::WaitingForPartialDecryption =>
let confirmed_nodes: BTreeSet<_> = data.confirmed_nodes.clone(); SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data),
for node in data.confirmed_nodes.iter().filter(|n| n != &self.node()) { // we can not have enough nodes for decryption
self.cluster.send(node, Message::RequestPartialDecryption(RequestPartialDecryption { SessionState::Failed => {
session: self.id.clone(), self.completed.notify_all();
sub_session: self.access_key.clone(),
nodes: confirmed_nodes.clone(),
}))?;
}
assert!(data.confirmed_nodes.remove(self.node()));
let shadow_point = {
let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryption follows initialization; qed");
do_partial_decryption(self.node(), &requestor, &data.confirmed_nodes, &self.access_key, &self.encrypted_data)?
};
data.shadow_points.insert(self.node().clone(), shadow_point);
Ok(()) Ok(())
}, },
// we can not have enough nodes for decryption
SessionState::Failed => Ok(()),
// cannot reach other states // cannot reach other states
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
} }
} }
/// When partial decryption is requested. /// When partial decryption is requested.
pub fn on_partial_decryption_requested(&self, sender: NodeId, message: RequestPartialDecryption) -> Result<(), Error> { pub fn on_partial_decryption_requested(&self, sender: NodeId, message: &RequestPartialDecryption) -> Result<(), Error> {
debug_assert!(self.id == message.session); debug_assert!(self.id == *message.session);
debug_assert!(self.access_key == message.sub_session); debug_assert!(self.access_key == *message.sub_session);
debug_assert!(&sender != self.node()); debug_assert!(&sender != self.node());
// check message // check message
@ -311,13 +318,13 @@ impl Session {
// calculate shadow point // calculate shadow point
let shadow_point = { let shadow_point = {
let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryptionRequest follows initialization; qed"); let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryptionRequest follows initialization; qed");
do_partial_decryption(self.node(), &requestor, &message.nodes, &self.access_key, &self.encrypted_data)? do_partial_decryption(self.node(), &requestor, &message.nodes.iter().cloned().map(Into::into).collect(), &self.access_key, &self.encrypted_data)?
}; };
self.cluster.send(&sender, Message::PartialDecryption(PartialDecryption { self.cluster.send(&sender, Message::Decryption(DecryptionMessage::PartialDecryption(PartialDecryption {
session: self.id.clone(), session: self.id.clone().into(),
sub_session: self.access_key.clone(), sub_session: self.access_key.clone().into(),
shadow_point: shadow_point, shadow_point: shadow_point.into(),
}))?; })))?;
// update sate // update sate
data.state = SessionState::Finished; data.state = SessionState::Finished;
@ -326,9 +333,9 @@ impl Session {
} }
/// When partial decryption is received. /// When partial decryption is received.
pub fn on_partial_decryption(&self, sender: NodeId, message: PartialDecryption) -> Result<(), Error> { pub fn on_partial_decryption(&self, sender: NodeId, message: &PartialDecryption) -> Result<(), Error> {
debug_assert!(self.id == message.session); debug_assert!(self.id == *message.session);
debug_assert!(self.access_key == message.sub_session); debug_assert!(self.access_key == *message.sub_session);
debug_assert!(&sender != self.node()); debug_assert!(&sender != self.node());
let mut data = self.data.lock(); let mut data = self.data.lock();
@ -341,24 +348,113 @@ impl Session {
if !data.confirmed_nodes.remove(&sender) { if !data.confirmed_nodes.remove(&sender) {
return Err(Error::InvalidStateForRequest); return Err(Error::InvalidStateForRequest);
} }
data.shadow_points.insert(sender, message.shadow_point); data.shadow_points.insert(sender, message.shadow_point.clone().into());
// check if we have enough shadow points to decrypt the secret // check if we have enough shadow points to decrypt the secret
if data.shadow_points.len() != self.encrypted_data.threshold + 1 { if data.shadow_points.len() != self.encrypted_data.threshold + 1 {
return Ok(()); return Ok(());
} }
SessionImpl::do_decryption(self.access_key.clone(), &self.encrypted_data, &mut *data)?;
self.completed.notify_all();
Ok(())
}
/// When error has occured on another node.
pub fn on_session_error(&self, sender: NodeId, message: &DecryptionSessionError) {
warn!("{}: decryption session error: {:?} from {}", self.node(), message, sender);
let mut data = self.data.lock();
data.state = SessionState::Failed;
data.decrypted_secret = Some(Err(Error::Io(message.error.clone())));
self.completed.notify_all();
}
/// When session timeout has occured.
pub fn on_session_timeout(&self, _node: &NodeId) {
warn!("{}: decryption session timeout", self.node());
let mut data = self.data.lock();
// TODO: check that node is a part of decryption process
data.state = SessionState::Failed;
data.decrypted_secret = Some(Err(Error::Io("session expired".into())));
self.completed.notify_all();
}
fn start_waiting_for_partial_decryption(self_node_id: NodeId, session_id: SessionId, access_key: Secret, cluster: &Arc<Cluster>, encrypted_data: &DocumentKeyShare, data: &mut SessionData) -> Result<(), Error> {
let confirmed_nodes: BTreeSet<_> = data.confirmed_nodes.clone();
for node in data.confirmed_nodes.iter().filter(|n| n != &&self_node_id) {
cluster.send(node, Message::Decryption(DecryptionMessage::RequestPartialDecryption(RequestPartialDecryption {
session: session_id.clone().into(),
sub_session: access_key.clone().into(),
nodes: confirmed_nodes.iter().cloned().map(Into::into).collect(),
})))?;
}
assert!(data.confirmed_nodes.remove(&self_node_id));
let shadow_point = {
let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryption follows initialization; qed");
do_partial_decryption(&self_node_id, &requestor, &data.confirmed_nodes, &access_key, &encrypted_data)?
};
data.shadow_points.insert(self_node_id.clone(), shadow_point);
Ok(())
}
fn do_decryption(access_key: Secret, encrypted_data: &DocumentKeyShare, data: &mut SessionData) -> Result<(), Error> {
// decrypt the secret using shadow points // decrypt the secret using shadow points
let joint_shadow_point = math::compute_joint_shadow_point(data.shadow_points.values())?; let joint_shadow_point = math::compute_joint_shadow_point(data.shadow_points.values())?;
let decrypted_secret = math::decrypt_with_joint_shadow(&self.access_key, &self.encrypted_data.encrypted_point, &joint_shadow_point)?; let decrypted_secret = math::decrypt_with_joint_shadow(encrypted_data.threshold, &access_key, &encrypted_data.encrypted_point, &joint_shadow_point)?;
data.decrypted_secret = Some(decrypted_secret); data.decrypted_secret = Some(Ok(decrypted_secret));
// switch to completed state
data.state = SessionState::Finished; data.state = SessionState::Finished;
Ok(()) Ok(())
} }
} }
fn check_encrypted_data(self_node_id: &Public, encrypted_data: &EncryptedData) -> Result<(), Error> { impl Session for SessionImpl {
fn wait(&self) -> Result<Public, Error> {
let mut data = self.data.lock();
if !data.decrypted_secret.is_some() {
self.completed.wait(&mut data);
}
data.decrypted_secret.as_ref()
.expect("checked above or waited for completed; completed is only signaled when decrypted_secret.is_some(); qed")
.clone()
}
}
impl DecryptionSessionId {
/// Create new decryption session Id.
pub fn new(session_id: SessionId, sub_session_id: Secret) -> Self {
DecryptionSessionId {
id: session_id,
access_key: sub_session_id,
}
}
}
impl PartialOrd for DecryptionSessionId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DecryptionSessionId {
fn cmp(&self, other: &Self) -> Ordering {
match self.id.cmp(&other.id) {
Ordering::Equal => self.access_key.cmp(&other.access_key),
r @ _ => r,
}
}
}
fn check_encrypted_data(self_node_id: &Public, encrypted_data: &DocumentKeyShare) -> Result<(), Error> {
use key_server_cluster::encryption_session::{check_cluster_nodes, check_threshold}; use key_server_cluster::encryption_session::{check_cluster_nodes, check_threshold};
let nodes = encrypted_data.id_numbers.keys().cloned().collect(); let nodes = encrypted_data.id_numbers.keys().cloned().collect();
@ -368,7 +464,7 @@ fn check_encrypted_data(self_node_id: &Public, encrypted_data: &EncryptedData) -
Ok(()) Ok(())
} }
fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut SessionData, node: &NodeId, check_result: bool) -> Result<(), Error> { fn process_initialization_response(encrypted_data: &DocumentKeyShare, data: &mut SessionData, node: &NodeId, check_result: bool) -> Result<(), Error> {
if !data.requested_nodes.remove(node) { if !data.requested_nodes.remove(node) {
return Err(Error::InvalidMessage); return Err(Error::InvalidMessage);
} }
@ -387,6 +483,7 @@ fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut Se
// check if we still can receive enough confirmations to do a decryption? // check if we still can receive enough confirmations to do a decryption?
if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 { if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 {
data.decrypted_secret = Some(Err(Error::AccessDenied));
data.state = SessionState::Failed; data.state = SessionState::Failed;
} }
}, },
@ -395,7 +492,7 @@ fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut Se
Ok(()) Ok(())
} }
fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants: &BTreeSet<NodeId>, access_key: &Secret, encrypted_data: &EncryptedData) -> Result<Public, Error> { fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants: &BTreeSet<NodeId>, access_key: &Secret, encrypted_data: &DocumentKeyShare) -> Result<Public, Error> {
let node_id_number = &encrypted_data.id_numbers[node]; let node_id_number = &encrypted_data.id_numbers[node];
let node_secret_share = &encrypted_data.secret_share; let node_secret_share = &encrypted_data.secret_share;
let other_id_numbers = participants.iter() let other_id_numbers = participants.iter()
@ -409,43 +506,42 @@ fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use std::str::FromStr;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use super::super::super::acl_storage::DummyAclStorage; use super::super::super::acl_storage::tests::DummyAclStorage;
use ethkey::{self, Random, Generator, Public, Secret}; use ethkey::{self, Random, Generator, Public, Secret};
use key_server_cluster::{NodeId, EncryptedData, SessionId, Error}; use key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error};
use key_server_cluster::cluster::tests::DummyCluster; use key_server_cluster::cluster::tests::DummyCluster;
use key_server_cluster::decryption_session::{Session, SessionParams, SessionState}; use key_server_cluster::decryption_session::{SessionImpl, SessionParams, SessionState};
use key_server_cluster::message::{self, Message}; use key_server_cluster::message::{self, Message, DecryptionMessage};
const SECRET_PLAIN: &'static str = "d2b57ae7619e070af0af6bc8c703c0cd27814c54d5d6a999cacac0da34ede279ca0d9216e85991029e54e2f0c92ee0bd30237725fa765cbdbfc4529489864c5f"; const SECRET_PLAIN: &'static str = "d2b57ae7619e070af0af6bc8c703c0cd27814c54d5d6a999cacac0da34ede279ca0d9216e85991029e54e2f0c92ee0bd30237725fa765cbdbfc4529489864c5f";
fn prepare_decryption_sessions() -> (Vec<Arc<DummyCluster>>, Vec<Arc<DummyAclStorage>>, Vec<Session>) { fn prepare_decryption_sessions() -> (Vec<Arc<DummyCluster>>, Vec<Arc<DummyAclStorage>>, Vec<SessionImpl>) {
// prepare encrypted data + cluster configuration for scheme 4-of-5 // prepare encrypted data + cluster configuration for scheme 4-of-5
let session_id = SessionId::default(); let session_id = SessionId::default();
let access_key = Random.generate().unwrap().secret().clone(); let access_key = Random.generate().unwrap().secret().clone();
let secret_shares = vec![ let secret_shares: Vec<Secret> = vec![
Secret::from_str("834cb736f02d9c968dfaf0c37658a1d86ff140554fc8b59c9fdad5a8cf810eec").unwrap(), "834cb736f02d9c968dfaf0c37658a1d86ff140554fc8b59c9fdad5a8cf810eec".parse().unwrap(),
Secret::from_str("5a3c1d90fafafa66bb808bcc464354a98b05e6b2c95b5f609d4511cdd1b17a0b").unwrap(), "5a3c1d90fafafa66bb808bcc464354a98b05e6b2c95b5f609d4511cdd1b17a0b".parse().unwrap(),
Secret::from_str("71bf61e7848e08e3a8486c308ce521bdacfebcf9116a0151447eb301f3a2d0e9").unwrap(), "71bf61e7848e08e3a8486c308ce521bdacfebcf9116a0151447eb301f3a2d0e9".parse().unwrap(),
Secret::from_str("80c0e5e2bea66fa9b2e07f7ce09630a9563e8242446d5ee63221feb09c4338f4").unwrap(), "80c0e5e2bea66fa9b2e07f7ce09630a9563e8242446d5ee63221feb09c4338f4".parse().unwrap(),
Secret::from_str("c06546b5669877ba579ca437a5602e89425c53808c708d44ccd6afcaa4610fad").unwrap(), "c06546b5669877ba579ca437a5602e89425c53808c708d44ccd6afcaa4610fad".parse().unwrap(),
]; ];
let id_numbers: Vec<(NodeId, Secret)> = vec![ let id_numbers: Vec<(NodeId, Secret)> = vec![
("b486d3840218837b035c66196ecb15e6b067ca20101e11bd5e626288ab6806ecc70b8307012626bd512bad1559112d11d21025cef48cc7a1d2f3976da08f36c8".into(), ("b486d3840218837b035c66196ecb15e6b067ca20101e11bd5e626288ab6806ecc70b8307012626bd512bad1559112d11d21025cef48cc7a1d2f3976da08f36c8".into(),
Secret::from_str("281b6bf43cb86d0dc7b98e1b7def4a80f3ce16d28d2308f934f116767306f06c").unwrap()), "281b6bf43cb86d0dc7b98e1b7def4a80f3ce16d28d2308f934f116767306f06c".parse().unwrap()),
("1395568277679f7f583ab7c0992da35f26cde57149ee70e524e49bdae62db3e18eb96122501e7cbb798b784395d7bb5a499edead0706638ad056d886e56cf8fb".into(), ("1395568277679f7f583ab7c0992da35f26cde57149ee70e524e49bdae62db3e18eb96122501e7cbb798b784395d7bb5a499edead0706638ad056d886e56cf8fb".into(),
Secret::from_str("00125d85a05e5e63e214cb60fe63f132eec8a103aa29266b7e6e6c5b7597230b").unwrap()), "00125d85a05e5e63e214cb60fe63f132eec8a103aa29266b7e6e6c5b7597230b".parse().unwrap()),
("99e82b163b062d55a64085bacfd407bb55f194ba5fb7a1af9c34b84435455520f1372e0e650a4f91aed0058cb823f62146ccb5599c8d13372c300dea866b69fc".into(), ("99e82b163b062d55a64085bacfd407bb55f194ba5fb7a1af9c34b84435455520f1372e0e650a4f91aed0058cb823f62146ccb5599c8d13372c300dea866b69fc".into(),
Secret::from_str("f43ac0fba42a5b6ed95707d2244659e89ba877b1c9b82c0d0a9dcf834e80fc62").unwrap()), "f43ac0fba42a5b6ed95707d2244659e89ba877b1c9b82c0d0a9dcf834e80fc62".parse().unwrap()),
("7e05df9dd077ec21ed4bc45c9fe9e0a43d65fa4be540630de615ced5e95cf5c3003035eb713317237d7667feeeb64335525158f5f7411f67aca9645169ea554c".into(), ("7e05df9dd077ec21ed4bc45c9fe9e0a43d65fa4be540630de615ced5e95cf5c3003035eb713317237d7667feeeb64335525158f5f7411f67aca9645169ea554c".into(),
Secret::from_str("5a324938dfb2516800487d25ab7289ba8ec38811f77c3df602e4e65e3c9acd9f").unwrap()), "5a324938dfb2516800487d25ab7289ba8ec38811f77c3df602e4e65e3c9acd9f".parse().unwrap()),
("321977760d1d8e15b047a309e4c7fe6f355c10bb5a06c68472b676926427f69f229024fa2692c10da167d14cdc77eb95d0fce68af0a0f704f0d3db36baa83bb2".into(), ("321977760d1d8e15b047a309e4c7fe6f355c10bb5a06c68472b676926427f69f229024fa2692c10da167d14cdc77eb95d0fce68af0a0f704f0d3db36baa83bb2".into(),
Secret::from_str("12cf422d50002d04e52bd4906fd7f5f235f051ca36abfe37e061f8da248008d8").unwrap()), "12cf422d50002d04e52bd4906fd7f5f235f051ca36abfe37e061f8da248008d8".parse().unwrap()),
]; ];
let common_point: Public = "6962be696e1bcbba8e64cc7fddf140f854835354b5804f3bb95ae5a2799130371b589a131bd39699ac7174ccb35fc4342dab05331202209582fc8f3a40916ab0".into(); let common_point: Public = "6962be696e1bcbba8e64cc7fddf140f854835354b5804f3bb95ae5a2799130371b589a131bd39699ac7174ccb35fc4342dab05331202209582fc8f3a40916ab0".into();
let encrypted_point: Public = "b07031982bde9890e12eff154765f03c56c3ab646ad47431db5dd2d742a9297679c4c65b998557f8008469afd0c43d40b6c5f6c6a1c7354875da4115237ed87a".into(); let encrypted_point: Public = "b07031982bde9890e12eff154765f03c56c3ab646ad47431db5dd2d742a9297679c4c65b998557f8008469afd0c43d40b6c5f6c6a1c7354875da4115237ed87a".into();
let encrypted_datas: Vec<_> = (0..5).map(|i| EncryptedData { let encrypted_datas: Vec<_> = (0..5).map(|i| DocumentKeyShare {
threshold: 3, threshold: 3,
id_numbers: id_numbers.clone().into_iter().collect(), id_numbers: id_numbers.clone().into_iter().collect(),
secret_share: secret_shares[i].clone(), secret_share: secret_shares[i].clone(),
@ -454,7 +550,7 @@ mod tests {
}).collect(); }).collect();
let acl_storages: Vec<_> = (0..5).map(|_| Arc::new(DummyAclStorage::default())).collect(); let acl_storages: Vec<_> = (0..5).map(|_| Arc::new(DummyAclStorage::default())).collect();
let clusters: Vec<_> = (0..5).map(|i| Arc::new(DummyCluster::new(id_numbers.iter().nth(i).clone().unwrap().0))).collect(); let clusters: Vec<_> = (0..5).map(|i| Arc::new(DummyCluster::new(id_numbers.iter().nth(i).clone().unwrap().0))).collect();
let sessions: Vec<_> = (0..5).map(|i| Session::new(SessionParams { let sessions: Vec<_> = (0..5).map(|i| SessionImpl::new(SessionParams {
id: session_id.clone(), id: session_id.clone(),
access_key: access_key.clone(), access_key: access_key.clone(),
self_node_id: id_numbers.iter().nth(i).clone().unwrap().0, self_node_id: id_numbers.iter().nth(i).clone().unwrap().0,
@ -466,11 +562,11 @@ mod tests {
(clusters, acl_storages, sessions) (clusters, acl_storages, sessions)
} }
fn do_messages_exchange(clusters: &[Arc<DummyCluster>], sessions: &[Session]) { fn do_messages_exchange(clusters: &[Arc<DummyCluster>], sessions: &[SessionImpl]) {
do_messages_exchange_until(clusters, sessions, |_, _, _| false); do_messages_exchange_until(clusters, sessions, |_, _, _| false);
} }
fn do_messages_exchange_until<F>(clusters: &[Arc<DummyCluster>], sessions: &[Session], mut cond: F) where F: FnMut(&NodeId, &NodeId, &Message) -> bool { fn do_messages_exchange_until<F>(clusters: &[Arc<DummyCluster>], sessions: &[SessionImpl], mut cond: F) where F: FnMut(&NodeId, &NodeId, &Message) -> bool {
while let Some((from, to, message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() { while let Some((from, to, message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() {
let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()]; let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()];
if cond(&from, &to, &message) { if cond(&from, &to, &message) {
@ -478,10 +574,10 @@ mod tests {
} }
match message { match message {
Message::InitializeDecryptionSession(message) => session.on_initialize_session(from, message).unwrap(), Message::Decryption(DecryptionMessage::InitializeDecryptionSession(message)) => session.on_initialize_session(from, &message).unwrap(),
Message::ConfirmDecryptionInitialization(message) => session.on_confirm_initialization(from, message).unwrap(), Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(message)) => session.on_confirm_initialization(from, &message).unwrap(),
Message::RequestPartialDecryption(message) => session.on_partial_decryption_requested(from, message).unwrap(), Message::Decryption(DecryptionMessage::RequestPartialDecryption(message)) => session.on_partial_decryption_requested(from, &message).unwrap(),
Message::PartialDecryption(message) => session.on_partial_decryption(from, message).unwrap(), Message::Decryption(DecryptionMessage::PartialDecryption(message)) => session.on_partial_decryption(from, &message).unwrap(),
_ => panic!("unexpected"), _ => panic!("unexpected"),
} }
} }
@ -492,11 +588,11 @@ mod tests {
let mut nodes = BTreeMap::new(); let mut nodes = BTreeMap::new();
let self_node_id = Random.generate().unwrap().public().clone(); let self_node_id = Random.generate().unwrap().public().clone();
nodes.insert(self_node_id, Random.generate().unwrap().secret().clone()); nodes.insert(self_node_id, Random.generate().unwrap().secret().clone());
match Session::new(SessionParams { match SessionImpl::new(SessionParams {
id: SessionId::default(), id: SessionId::default(),
access_key: Random.generate().unwrap().secret().clone(), access_key: Random.generate().unwrap().secret().clone(),
self_node_id: self_node_id.clone(), self_node_id: self_node_id.clone(),
encrypted_data: EncryptedData { encrypted_data: DocumentKeyShare {
threshold: 0, threshold: 0,
id_numbers: nodes, id_numbers: nodes,
secret_share: Random.generate().unwrap().secret().clone(), secret_share: Random.generate().unwrap().secret().clone(),
@ -517,11 +613,11 @@ mod tests {
let self_node_id = Random.generate().unwrap().public().clone(); let self_node_id = Random.generate().unwrap().public().clone();
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
match Session::new(SessionParams { match SessionImpl::new(SessionParams {
id: SessionId::default(), id: SessionId::default(),
access_key: Random.generate().unwrap().secret().clone(), access_key: Random.generate().unwrap().secret().clone(),
self_node_id: self_node_id.clone(), self_node_id: self_node_id.clone(),
encrypted_data: EncryptedData { encrypted_data: DocumentKeyShare {
threshold: 0, threshold: 0,
id_numbers: nodes, id_numbers: nodes,
secret_share: Random.generate().unwrap().secret().clone(), secret_share: Random.generate().unwrap().secret().clone(),
@ -542,11 +638,11 @@ mod tests {
let self_node_id = Random.generate().unwrap().public().clone(); let self_node_id = Random.generate().unwrap().public().clone();
nodes.insert(self_node_id.clone(), Random.generate().unwrap().secret().clone()); nodes.insert(self_node_id.clone(), Random.generate().unwrap().secret().clone());
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
match Session::new(SessionParams { match SessionImpl::new(SessionParams {
id: SessionId::default(), id: SessionId::default(),
access_key: Random.generate().unwrap().secret().clone(), access_key: Random.generate().unwrap().secret().clone(),
self_node_id: self_node_id.clone(), self_node_id: self_node_id.clone(),
encrypted_data: EncryptedData { encrypted_data: DocumentKeyShare {
threshold: 2, threshold: 2,
id_numbers: nodes, id_numbers: nodes,
secret_share: Random.generate().unwrap().secret().clone(), secret_share: Random.generate().unwrap().secret().clone(),
@ -572,70 +668,70 @@ mod tests {
fn fails_to_accept_initialization_when_already_initialized() { fn fails_to_accept_initialization_when_already_initialized() {
let (_, _, sessions) = prepare_decryption_sessions(); let (_, _, sessions) = prepare_decryption_sessions();
assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap(), ()); assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap(), ());
assert_eq!(sessions[0].on_initialize_session(sessions[1].node().clone(), message::InitializeDecryptionSession { assert_eq!(sessions[0].on_initialize_session(sessions[1].node().clone(), &message::InitializeDecryptionSession {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(),
}).unwrap_err(), Error::InvalidStateForRequest); }).unwrap_err(), Error::InvalidStateForRequest);
} }
#[test] #[test]
fn fails_to_partial_decrypt_if_not_waiting() { fn fails_to_partial_decrypt_if_not_waiting() {
let (_, _, sessions) = prepare_decryption_sessions(); let (_, _, sessions) = prepare_decryption_sessions();
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(),
}).unwrap(), ()); }).unwrap(), ());
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(),
}).unwrap(), ()); }).unwrap(), ());
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(),
}).unwrap_err(), Error::InvalidStateForRequest); }).unwrap_err(), Error::InvalidStateForRequest);
} }
#[test] #[test]
fn fails_to_partial_decrypt_if_requested_by_slave() { fn fails_to_partial_decrypt_if_requested_by_slave() {
let (_, _, sessions) = prepare_decryption_sessions(); let (_, _, sessions) = prepare_decryption_sessions();
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(),
}).unwrap(), ()); }).unwrap(), ());
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[2].node().clone(), message::RequestPartialDecryption { assert_eq!(sessions[1].on_partial_decryption_requested(sessions[2].node().clone(), &message::RequestPartialDecryption {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(),
}).unwrap_err(), Error::InvalidMessage); }).unwrap_err(), Error::InvalidMessage);
} }
#[test] #[test]
fn fails_to_partial_decrypt_if_wrong_number_of_nodes_participating() { fn fails_to_partial_decrypt_if_wrong_number_of_nodes_participating() {
let (_, _, sessions) = prepare_decryption_sessions(); let (_, _, sessions) = prepare_decryption_sessions();
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(),
}).unwrap(), ()); }).unwrap(), ());
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
nodes: sessions.iter().map(|s| s.node().clone()).take(2).collect(), nodes: sessions.iter().map(|s| s.node().clone().into()).take(2).collect(),
}).unwrap_err(), Error::InvalidMessage); }).unwrap_err(), Error::InvalidMessage);
} }
#[test] #[test]
fn fails_to_accept_partial_decrypt_if_not_waiting() { fn fails_to_accept_partial_decrypt_if_not_waiting() {
let (_, _, sessions) = prepare_decryption_sessions(); let (_, _, sessions) = prepare_decryption_sessions();
assert_eq!(sessions[0].on_partial_decryption(sessions[1].node().clone(), message::PartialDecryption { assert_eq!(sessions[0].on_partial_decryption(sessions[1].node().clone(), &message::PartialDecryption {
session: SessionId::default(), session: SessionId::default().into(),
sub_session: sessions[0].access_key().clone(), sub_session: sessions[0].access_key().clone().into(),
shadow_point: Random.generate().unwrap().public().clone(), shadow_point: Random.generate().unwrap().public().clone().into(),
}).unwrap_err(), Error::InvalidStateForRequest); }).unwrap_err(), Error::InvalidStateForRequest);
} }
@ -647,7 +743,7 @@ mod tests {
let mut pd_from = None; let mut pd_from = None;
let mut pd_msg = None; let mut pd_msg = None;
do_messages_exchange_until(&clusters, &sessions, |from, _, msg| match msg { do_messages_exchange_until(&clusters, &sessions, |from, _, msg| match msg {
&Message::PartialDecryption(ref msg) => { &Message::Decryption(DecryptionMessage::PartialDecryption(ref msg)) => {
pd_from = Some(from.clone()); pd_from = Some(from.clone());
pd_msg = Some(msg.clone()); pd_msg = Some(msg.clone());
true true
@ -655,8 +751,8 @@ mod tests {
_ => false, _ => false,
}); });
assert_eq!(sessions[0].on_partial_decryption(pd_from.clone().unwrap(), pd_msg.clone().unwrap()).unwrap(), ()); assert_eq!(sessions[0].on_partial_decryption(pd_from.clone().unwrap(), &pd_msg.clone().unwrap()).unwrap(), ());
assert_eq!(sessions[0].on_partial_decryption(pd_from.unwrap(), pd_msg.unwrap()).unwrap_err(), Error::InvalidStateForRequest); assert_eq!(sessions[0].on_partial_decryption(pd_from.unwrap(), &pd_msg.unwrap()).unwrap_err(), Error::InvalidStateForRequest);
} }
#[test] #[test]
@ -704,4 +800,9 @@ mod tests {
// 3) 0 sessions have decrypted key value // 3) 0 sessions have decrypted key value
assert!(sessions.iter().all(|s| s.decrypted_secret().is_none())); assert!(sessions.iter().all(|s| s.decrypted_secret().is_none()));
} }
#[test]
fn decryption_session_works_over_network() {
// TODO
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
// 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/>.
use std::io;
use std::time::Duration;
use futures::{Future, Select, BoxFuture, Poll, Async};
use tokio_core::reactor::{Handle, Timeout};
type DeadlineBox<F> where F: Future = BoxFuture<DeadlineStatus<F::Item>, F::Error>;
/// Complete a passed future or fail if it is not completed within timeout.
pub fn deadline<F, T>(duration: Duration, handle: &Handle, future: F) -> Result<Deadline<F>, io::Error>
where F: Future<Item = T, Error = io::Error> + Send + 'static, T: 'static {
let timeout = try!(Timeout::new(duration, handle)).map(|_| DeadlineStatus::Timeout).boxed();
let future = future.map(DeadlineStatus::Meet).boxed();
let deadline = Deadline {
future: timeout.select(future),
};
Ok(deadline)
}
#[derive(Debug, PartialEq)]
/// Deadline future completion status.
pub enum DeadlineStatus<T> {
/// Completed a future.
Meet(T),
/// Faled with timeout.
Timeout,
}
/// Future, which waits for passed future completion within given period, or fails with timeout.
pub struct Deadline<F> where F: Future {
future: Select<DeadlineBox<F>, DeadlineBox<F>>,
}
impl<F, T> Future for Deadline<F> where F: Future<Item = T, Error = io::Error> {
type Item = DeadlineStatus<T>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.future.poll() {
Ok(Async::Ready((result, _other))) => Ok(Async::Ready(result)),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err((err, _other)) => Err(err),
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use std::time::Duration;
use futures::{Future, empty, done};
use tokio_core::reactor::Core;
use super::{deadline, DeadlineStatus};
//#[test] TODO: not working
fn _deadline_timeout_works() {
let mut core = Core::new().unwrap();
let deadline = deadline(Duration::from_millis(1), &core.handle(), empty::<(), io::Error>()).unwrap();
core.turn(Some(Duration::from_millis(3)));
assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Timeout);
}
#[test]
fn deadline_result_works() {
let mut core = Core::new().unwrap();
let deadline = deadline(Duration::from_millis(1000), &core.handle(), done(Ok(()))).unwrap();
core.turn(Some(Duration::from_millis(3)));
assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Meet(()));
}
}

View File

@ -0,0 +1,320 @@
// 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/>.
use std::io;
use std::collections::BTreeSet;
use futures::{Future, Poll, Async};
use ethkey::{Random, Generator, KeyPair, Secret, sign, verify_public};
use util::H256;
use key_server_cluster::{NodeId, Error};
use key_server_cluster::message::{Message, ClusterMessage, NodePublicKey, NodePrivateKeySignature};
use key_server_cluster::io::{write_message, write_encrypted_message, WriteMessage, ReadMessage,
read_message, compute_shared_key};
/// Start handshake procedure with another node from the cluster.
pub fn handshake<A>(a: A, self_key_pair: KeyPair, trusted_nodes: BTreeSet<NodeId>) -> Handshake<A> where A: io::Write + io::Read {
let self_confirmation_plain = Random.generate().map(|kp| *kp.secret().clone()).map_err(Into::into);
handshake_with_plain_confirmation(a, self_confirmation_plain, self_key_pair, trusted_nodes)
}
/// Start handshake procedure with another node from the cluster and given plain confirmation.
pub fn handshake_with_plain_confirmation<A>(a: A, self_confirmation_plain: Result<H256, Error>, self_key_pair: KeyPair, trusted_nodes: BTreeSet<NodeId>) -> Handshake<A> where A: io::Write + io::Read {
let (error, state) = match self_confirmation_plain.clone()
.and_then(|c| Handshake::<A>::make_public_key_message(self_key_pair.public().clone(), c)) {
Ok(message) => (None, HandshakeState::SendPublicKey(write_message(a, message))),
Err(err) => (Some((a, Err(err))), HandshakeState::Finished),
};
Handshake {
is_active: true,
error: error,
state: state,
self_key_pair: self_key_pair,
self_confirmation_plain: self_confirmation_plain.unwrap_or(Default::default()),
trusted_nodes: trusted_nodes,
other_node_id: None,
other_confirmation_plain: None,
shared_key: None,
}
}
/// Wait for handshake procedure to be started by another node from the cluster.
pub fn accept_handshake<A>(a: A, self_key_pair: KeyPair, trusted_nodes: BTreeSet<NodeId>) -> Handshake<A> where A: io::Write + io::Read {
let self_confirmation_plain = Random.generate().map(|kp| *kp.secret().clone()).map_err(Into::into);
let (error, state) = match self_confirmation_plain.clone() {
Ok(_) => (None, HandshakeState::ReceivePublicKey(read_message(a))),
Err(err) => (Some((a, Err(err))), HandshakeState::Finished),
};
Handshake {
is_active: false,
error: error,
state: state,
self_key_pair: self_key_pair,
self_confirmation_plain: self_confirmation_plain.unwrap_or(Default::default()),
trusted_nodes: trusted_nodes,
other_node_id: None,
other_confirmation_plain: None,
shared_key: None,
}
}
#[derive(Debug, PartialEq)]
/// Result of handshake procedure.
pub struct HandshakeResult {
/// Node id.
pub node_id: NodeId,
/// Shared key.
pub shared_key: Secret,
}
/// Future handshake procedure.
pub struct Handshake<A> {
is_active: bool,
error: Option<(A, Result<HandshakeResult, Error>)>,
state: HandshakeState<A>,
self_key_pair: KeyPair,
self_confirmation_plain: H256,
trusted_nodes: BTreeSet<NodeId>,
other_node_id: Option<NodeId>,
other_confirmation_plain: Option<H256>,
shared_key: Option<Secret>,
}
/// Active handshake state.
enum HandshakeState<A> {
SendPublicKey(WriteMessage<A>),
ReceivePublicKey(ReadMessage<A>),
SendPrivateKeySignature(WriteMessage<A>),
ReceivePrivateKeySignature(ReadMessage<A>),
Finished,
}
impl<A> Handshake<A> where A: io::Read + io::Write {
#[cfg(test)]
pub fn set_self_confirmation_plain(&mut self, self_confirmation_plain: H256) {
self.self_confirmation_plain = self_confirmation_plain;
}
pub fn make_public_key_message(self_node_id: NodeId, confirmation_plain: H256) -> Result<Message, Error> {
Ok(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey {
node_id: self_node_id.into(),
confirmation_plain: confirmation_plain.into(),
})))
}
fn make_private_key_signature_message(secret: &Secret, confirmation_plain: &H256) -> Result<Message, Error> {
Ok(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature {
confirmation_signed: sign(secret, confirmation_plain)?.into(),
})))
}
}
impl<A> Future for Handshake<A> where A: io::Read + io::Write {
type Item = (A, Result<HandshakeResult, Error>);
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(error_result) = self.error.take() {
return Ok(error_result.into());
}
let (next, result) = match self.state {
HandshakeState::SendPublicKey(ref mut future) => {
let (stream, _) = try_ready!(future.poll());
if self.is_active {
(HandshakeState::ReceivePublicKey(
read_message(stream)
), Async::NotReady)
} else {
self.shared_key = match compute_shared_key(self.self_key_pair.secret(),
self.other_node_id.as_ref().expect("we are in passive mode; in passive mode SendPublicKey follows ReceivePublicKey; other_node_id is filled in ReceivePublicKey; qed")
) {
Ok(shared_key) => Some(shared_key),
Err(err) => return Ok((stream, Err(err)).into()),
};
let message = match Handshake::<A>::make_private_key_signature_message(
self.self_key_pair.secret(),
self.other_confirmation_plain.as_ref().expect("we are in passive mode; in passive mode SendPublicKey follows ReceivePublicKey; other_confirmation_plain is filled in ReceivePublicKey; qed")
) {
Ok(message) => message,
Err(err) => return Ok((stream, Err(err)).into()),
};
(HandshakeState::SendPrivateKeySignature(write_encrypted_message(stream,
self.shared_key.as_ref().expect("filled couple of lines above; qed"),
message)), Async::NotReady)
}
},
HandshakeState::ReceivePublicKey(ref mut future) => {
let (stream, message) = try_ready!(future.poll());
let message = match message {
Ok(message) => match message {
Message::Cluster(ClusterMessage::NodePublicKey(message)) => message,
_ => return Ok((stream, Err(Error::InvalidMessage)).into()),
},
Err(err) => return Ok((stream, Err(err.into())).into()),
};
if !self.trusted_nodes.contains(&*message.node_id) {
return Ok((stream, Err(Error::InvalidNodeId)).into());
}
self.other_node_id = Some(message.node_id.into());
self.other_confirmation_plain = Some(message.confirmation_plain.into());
if self.is_active {
self.shared_key = match compute_shared_key(self.self_key_pair.secret(),
self.other_node_id.as_ref().expect("filled couple of lines above; qed")
) {
Ok(shared_key) => Some(shared_key),
Err(err) => return Ok((stream, Err(err)).into()),
};
let message = match Handshake::<A>::make_private_key_signature_message(
self.self_key_pair.secret(),
self.other_confirmation_plain.as_ref().expect("filled couple of lines above; qed")
) {
Ok(message) => message,
Err(err) => return Ok((stream, Err(err)).into()),
};
(HandshakeState::SendPrivateKeySignature(write_encrypted_message(stream,
self.shared_key.as_ref().expect("filled couple of lines above; qed"),
message)), Async::NotReady)
} else {
let message = match Handshake::<A>::make_public_key_message(self.self_key_pair.public().clone(), self.self_confirmation_plain.clone()) {
Ok(message) => message,
Err(err) => return Ok((stream, Err(err)).into()),
};
(HandshakeState::SendPublicKey(write_message(stream, message)), Async::NotReady)
}
},
HandshakeState::SendPrivateKeySignature(ref mut future) => {
let (stream, _) = try_ready!(future.poll());
(HandshakeState::ReceivePrivateKeySignature(
read_message(stream)
), Async::NotReady)
},
HandshakeState::ReceivePrivateKeySignature(ref mut future) => {
let (stream, message) = try_ready!(future.poll());
let message = match message {
Ok(message) => match message {
Message::Cluster(ClusterMessage::NodePrivateKeySignature(message)) => message,
_ => return Ok((stream, Err(Error::InvalidMessage)).into()),
},
Err(err) => return Ok((stream, Err(err.into())).into()),
};
let other_node_public = self.other_node_id.as_ref().expect("other_node_id is filled in ReceivePublicKey; ReceivePrivateKeySignature follows ReceivePublicKey; qed");
if !verify_public(other_node_public, &*message.confirmation_signed, &self.self_confirmation_plain).unwrap_or(false) {
return Ok((stream, Err(Error::InvalidMessage)).into());
}
(HandshakeState::Finished, Async::Ready((stream, Ok(HandshakeResult {
node_id: self.other_node_id.expect("other_node_id is filled in ReceivePublicKey; ReceivePrivateKeySignature follows ReceivePublicKey; qed"),
shared_key: self.shared_key.clone().expect("shared_key is filled in Send/ReceivePublicKey; ReceivePrivateKeySignature follows Send/ReceivePublicKey; qed"),
}))))
},
HandshakeState::Finished => panic!("poll Handshake after it's done"),
};
self.state = next;
match result {
// by polling again, we register new future
Async::NotReady => self.poll(),
result => Ok(result)
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use futures::Future;
use ethcrypto::ecdh::agree;
use ethkey::{Random, Generator, sign};
use util::H256;
use key_server_cluster::io::message::tests::TestIo;
use key_server_cluster::message::{Message, ClusterMessage, NodePublicKey, NodePrivateKeySignature};
use super::{handshake_with_plain_confirmation, accept_handshake, HandshakeResult};
fn prepare_test_io() -> (H256, TestIo) {
let self_key_pair = Random.generate().unwrap();
let peer_key_pair = Random.generate().unwrap();
let mut io = TestIo::new(self_key_pair.clone(), peer_key_pair.public().clone());
let self_confirmation_plain = *Random.generate().unwrap().secret().clone();
let peer_confirmation_plain = *Random.generate().unwrap().secret().clone();
let self_confirmation_signed = sign(peer_key_pair.secret(), &self_confirmation_plain).unwrap();
let peer_confirmation_signed = sign(self_key_pair.secret(), &peer_confirmation_plain).unwrap();
io.add_input_message(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey {
node_id: peer_key_pair.public().clone().into(),
confirmation_plain: peer_confirmation_plain.into(),
})));
io.add_input_message(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature {
confirmation_signed: self_confirmation_signed.into(),
})));
io.add_output_message(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey {
node_id: self_key_pair.public().clone().into(),
confirmation_plain: self_confirmation_plain.clone().into(),
})));
io.add_output_message(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature {
confirmation_signed: peer_confirmation_signed.into(),
})));
(self_confirmation_plain, io)
}
#[test]
fn active_handshake_works() {
let (self_confirmation_plain, io) = prepare_test_io();
let self_key_pair = io.self_key_pair().clone();
let trusted_nodes: BTreeSet<_> = vec![io.peer_public().clone()].into_iter().collect();
let shared_key = agree(self_key_pair.secret(), trusted_nodes.iter().nth(0).unwrap()).unwrap();
let handshake = handshake_with_plain_confirmation(io, Ok(self_confirmation_plain), self_key_pair, trusted_nodes);
let handshake_result = handshake.wait().unwrap();
assert_eq!(handshake_result.1, Ok(HandshakeResult {
node_id: handshake_result.0.peer_public().clone(),
shared_key: shared_key,
}));
handshake_result.0.assert_output();
}
#[test]
fn passive_handshake_works() {
let (self_confirmation_plain, io) = prepare_test_io();
let self_key_pair = io.self_key_pair().clone();
let trusted_nodes: BTreeSet<_> = vec![io.peer_public().clone()].into_iter().collect();
let shared_key = agree(self_key_pair.secret(), io.peer_public()).unwrap();
let mut handshake = accept_handshake(io, self_key_pair, trusted_nodes);
handshake.set_self_confirmation_plain(self_confirmation_plain);
let handshake_result = handshake.wait().unwrap();
assert_eq!(handshake_result.1, Ok(HandshakeResult {
node_id: handshake_result.0.peer_public().clone(),
shared_key: shared_key,
}));
handshake_result.0.assert_output();
}
}

Some files were not shown because too many files have changed in this diff Show More