diff --git a/Cargo.lock b/Cargo.lock index 36adce0b5..f818e4846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,13 +26,14 @@ dependencies = [ "ethcore-util 1.5.0", "ethsync 1.5.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.5.0", + "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", "parity-updater 1.5.0", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", @@ -42,13 +43,22 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "advapi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "aho-corasick" version = "0.5.1" @@ -99,9 +109,15 @@ dependencies = [ ] [[package]] -name = "base64" -version = "0.2.1" +name = "bigint" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "bit-set" @@ -126,6 +142,11 @@ name = "bitflags" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "blastfig" version = "0.3.3" @@ -200,9 +221,35 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.2.9" +name = "core-foundation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crypt32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "ctrlc" @@ -281,7 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "docopt 0.6.80 (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.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -305,7 +352,7 @@ dependencies = [ "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.5.0", @@ -341,10 +388,10 @@ dependencies = [ name = "ethcore-bigint" version = "0.1.2" dependencies = [ + "bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -364,6 +411,7 @@ dependencies = [ "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", "fetch 0.1.0", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", @@ -373,10 +421,11 @@ dependencies = [ "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.5.0", + "parity-reactor 0.1.0", "parity-ui 1.5.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.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -396,7 +445,7 @@ dependencies = [ name = "ethcore-io" version = "1.5.0" dependencies = [ - "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.1 (git+https://github.com/ethcore/mio)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -531,16 +580,19 @@ dependencies = [ "ethstore 0.1.0", "ethsync 1.5.0", "fetch 0.1.0", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-reactor 0.1.0", "parity-updater 1.5.0", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -637,7 +689,7 @@ version = "0.1.0" dependencies = [ "ethcore-util 1.5.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -670,7 +722,7 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (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.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -721,10 +773,11 @@ dependencies = [ name = "fetch" version = "0.1.0" dependencies = [ - "https-fetch 0.1.0", - "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -744,6 +797,16 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-cpupool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gcc" version = "0.3.35" @@ -752,6 +815,15 @@ dependencies = [ "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gdi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" @@ -783,19 +855,9 @@ name = "httparse" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "https-fetch" -version = "0.1.0" -dependencies = [ - "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", - "rustls 0.1.2 (git+https://github.com/ctz/rustls)", -] - [[package]] name = "hyper" -version = "0.9.10" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -803,7 +865,7 @@ dependencies = [ "language-tags 0.2.2 (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)", - "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -847,7 +909,7 @@ name = "igd" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -892,7 +954,7 @@ source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec33 dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -929,7 +991,7 @@ version = "0.1.0" source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a" dependencies = [ "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1103,6 +1165,22 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miow" version = "0.1.3" @@ -1142,6 +1220,18 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "native-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "openssl 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "net2" version = "0.2.23" @@ -1276,6 +1366,14 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num_cpus" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "number_prefix" version = "0.2.5" @@ -1298,6 +1396,28 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "openssl" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "owning_ref" version = "0.2.2" @@ -1324,11 +1444,23 @@ dependencies = [ "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-util 1.5.0", "fetch 0.1.0", + "futures 0.1.6 (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-reactor 0.1.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)", ] +[[package]] +name = "parity-reactor" +version = "0.1.0" +dependencies = [ + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parity-rpc-client" version = "1.4.0" @@ -1342,7 +1474,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1368,7 +1500,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#af35fe9fa45377016ef90c55aae976fb44b35b55" +source = "git+https://github.com/ethcore/js-precompiled.git#1b82b5f0d694fb2d3da71d7b0357dc50c2229d9a" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1386,6 +1518,7 @@ dependencies = [ "ipc-common-types 1.5.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.5.0", + "parity-reactor 0.1.0", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1444,6 +1577,11 @@ dependencies = [ "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "podio" version = "0.1.5" @@ -1574,12 +1712,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "ring" -version = "0.4.3" +name = "reqwest" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1682,16 +1825,51 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.1.2" -source = "git+https://github.com/ctz/rustls#3d2db624997004b7b18ba4463d6081f37598b2f5" +name = "schannel" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "webpki 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scoped-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "secur32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1726,7 +1904,7 @@ dependencies = [ [[package]] name = "serde" -version = "0.8.4" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1759,7 +1937,16 @@ dependencies = [ "dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_urlencoded" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1978,6 +2165,18 @@ name = "tiny-keccak" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "tokio-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.1.28" @@ -2038,11 +2237,6 @@ name = "unicode-xid" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "untrusted" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "url" version = "1.2.0" @@ -2052,6 +2246,15 @@ dependencies = [ "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "user32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "using_queue" version = "0.1.0" @@ -2084,17 +2287,6 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "webpki" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winapi" version = "0.2.8" @@ -2162,17 +2354,19 @@ dependencies = [ ] [metadata] +"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03" "checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962" "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" "checksum aster 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4df293303e8a52e1df7984ac1415e195f5fcbf51e4bb7bda54557861a3954a08" -"checksum base64 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2015e3793554aa5b6007e3a72959e84c1070039e74f13dde08fa64afe1ddd892" +"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae" "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" @@ -2183,7 +2377,10 @@ dependencies = [ "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" -"checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" +"checksum core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "20a6d0448d3a99d977ae4a2aa5a98d886a923e863e81ad9ff814645b6feb3bbd" +"checksum core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "05eed248dc504a5391c63794fe4fb64f46f071280afaa1b73308f3c0ce4574c5" +"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" +"checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" "checksum ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)" = "" "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" @@ -2196,14 +2393,16 @@ dependencies = [ "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" "checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897" +"checksum futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bb982bb25cd8fa5da6a8eb3a460354c984ff1113da82bcb4f0b0862b5795db82" "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" +"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "" -"checksum hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "eb27e8a3e8f17ac43ffa41bbda9cf5ad3f9f13ef66fa4873409d4902310275f7" +"checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" @@ -2232,10 +2431,12 @@ dependencies = [ "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" "checksum mio 0.6.0-dev (git+https://github.com/ethcore/mio?branch=timer-fix)" = "" "checksum mio 0.6.1 (git+https://github.com/ethcore/mio)" = "" +"checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8" "checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" +"checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09" "checksum net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "6a816012ca11cb47009693c1e0c6130e26d39e4d97ee2a13c50e868ec83e3204" "checksum nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f05c2fc965fc1cd6b73fa57fa7b89f288178737f2f3ce9e63e4a6a141189000e" "checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" @@ -2250,9 +2451,12 @@ dependencies = [ "checksum num-rational 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "48cdcc9ff4ae2a8296805ac15af88b3d88ce62128ded0cb74ffb63a587502a84" "checksum num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "51eab148f171aefad295f8cece636fc488b9b392ef544da31ea4b8ef6b9e9c39" "checksum num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "51fedae97a05f7353612fe017ab705a37e6db8f4d67c5c6fe739a9e70d6eed09" +"checksum num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "55aabf4e2d6271a2e4e4c0f2ea1f5b07cc589cc1a9e9213013b54a76678ca4f3" "checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77" "checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1" "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" +"checksum openssl 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "12be61c7eaa23228316ff02c39807e4c1b1af84ba81420f19fd58dade304b25c" +"checksum openssl-sys 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d2845e841700e7b04282ceaa115407ea84e0db918ae689ad9ceb6f06fa6046bd" "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "" @@ -2262,6 +2466,7 @@ dependencies = [ "checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d" "checksum phf_generator 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "db005608fd99800c8c74106a7c894cf582055b689aa14a79462cefdcb7dc1cc3" "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 podio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e5422a1ee1bc57cc47ae717b0137314258138f38fd5f3cea083f43a9725383a0" "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" @@ -2278,7 +2483,7 @@ dependencies = [ "checksum rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "655df67c314c30fa3055a365eae276eb88aa4f3413a352a1ab32c1320eda41ea" "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" -"checksum ring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0d2f6547bf9640f1d3cc4e771f82374ec8fd237c17eeb3ff5cd5ccbe22377a09" +"checksum reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83186fee0d4dbeb95e610b77b05b05cf5b31703dd375222acb74c3dff4be957c" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2287,15 +2492,20 @@ dependencies = [ "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" -"checksum rustls 0.1.2 (git+https://github.com/ctz/rustls)" = "" +"checksum schannel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "167852e03fcd0029c3ddebb5afb0715b2996f6e262b2c2aceaa7cd84edd4b158" +"checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" +"checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" +"checksum security-framework 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52186fcf3b391c9f0ccdce9a2ac708f7cc81b3f89e149b34bd9279fb1b23f9fa" +"checksum security-framework-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c84067e6297c1f09514a8666d8bbc1268817ec4a6c0f30f12f6201e1f34bd2a1" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f" "checksum semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2ff60ecdb19c255841c066cbfa5f8c2a4ada1eb3ae47c77ab6667128da71f5" "checksum semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e88e43a5a74dd2a11707f9c21dfd4a423c66bd871df813227bb0a3e78f3a1ae9" -"checksum serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b1dfda9ebb31d29fa8b94d7eb3031a86a8dcec065f0fe268a30f98867bf45775" +"checksum serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "58a19c0871c298847e6b68318484685cd51fa5478c0c905095647540031356e5" "checksum serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e422ae53d7933f59c6ff57e7b5870b5c9094b1f473f78ec33d89f8a692c3ec02" "checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21" "checksum serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e10f8a9d94b06cf5d3bef66475f04c8ff90950f1be7004c357ff9472ccbaebc" +"checksum serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53d4ebaa8d1d4f90d1b63dfca81ccd98ac20e1e479dbae393cbaf60f6fecd8d8" "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d" "checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd" @@ -2323,6 +2533,7 @@ dependencies = [ "checksum thread_local 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0694f51610ef7cfac7a1b81de7f1602ee5356e76541bcd62c40e71933338cab1" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" "checksum tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7aef43048292ca0bae4ab32180e85f6202cf2816c2a210c396a84b99dab9270" +"checksum tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "52416b3e937abac22a543a7f1c66bd37feb60137ff1ab42390fa02df85347e58" "checksum toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "fcd27a04ca509aff336ba5eb2abc58d456f52c4ff64d9724d88acb85ead560b6" "checksum toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a442dfc13508e603c3f763274361db7f79d7469a0e95c411cde53662ab30fc72" "checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" @@ -2332,13 +2543,12 @@ dependencies = [ "checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" -"checksum untrusted 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5d9bc0e6e73a10975d1fbff8ac3541e221181b0d8998351600fb5523de634c0d" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" +"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum webpki 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "813503a5985585e0812d430cd1328ee322f47f66629c8ed4ecab939cf9e92f91" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)" = "" diff --git a/Cargo.toml b/Cargo.toml index 1eed33821..18ada1185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,8 @@ rpc-cli = { path = "rpc_cli" } parity-rpc-client = { path = "rpc_client" } ethcore-light = { path = "ethcore/light" } parity-hash-fetch = { path = "hash-fetch" } -parity-updater = { path = "updater" } +parity-updater = { path = "updater" } +parity-reactor = { path = "util/reactor" } [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/README.md b/README.md index 65c374413..45f39da06 100644 --- a/README.md +++ b/README.md @@ -58,18 +58,18 @@ Parity is fully compatible with Stable Rust. We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this: -- Linux and OSX: +- Linux: ```bash $ curl https://sh.rustup.rs -sSf | sh ``` - Parity also requires `gcc`, `g++` and `make` packages to be installed. + Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl` and `pkg-config` packages to be installed. - OSX: ```bash $ curl https://sh.rustup.rs -sSf | sh ``` - `clang` and `make` are required. These come with Xcode command line tools or can be installed with homebrew. + `clang` is required. It comes with Xcode command line tools or can be installed with homebrew. - Windows Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 75e8e346c..5b044fb89 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -9,9 +9,10 @@ build = "build.rs" [lib] [dependencies] -rand = "0.3.14" +rand = "0.3" log = "0.3" env_logger = "0.3" +futures = "0.1" jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } @@ -33,6 +34,7 @@ ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } parity-hash-fetch = { path = "../hash-fetch" } +parity-reactor = { path = "../util/reactor" } clippy = { version = "0.0.103", optional = true} diff --git a/dapps/res/gavcoin.zip b/dapps/res/gavcoin.zip new file mode 100644 index 000000000..3ced8c5c1 Binary files /dev/null and b/dapps/res/gavcoin.zip differ diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 440396898..fcd6d9921 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -23,7 +23,7 @@ use hyper::header::AccessControlAllowOrigin; use api::types::{App, ApiError}; use api::response; -use apps::fetcher::ContentFetcher; +use apps::fetcher::Fetcher; use handlers::extract_url; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; @@ -33,11 +33,11 @@ use jsonrpc_http_server::cors; pub struct RestApi { cors_domains: Option>, endpoints: Arc, - fetcher: Arc, + fetcher: Arc, } impl RestApi { - pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { + pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), endpoints: endpoints, diff --git a/dapps/src/apps/cache.rs b/dapps/src/apps/cache.rs index 3f531fe41..679791d22 100644 --- a/dapps/src/apps/cache.rs +++ b/dapps/src/apps/cache.rs @@ -17,14 +17,13 @@ //! Fetchable Dapps support. use std::fs; -use std::sync::{Arc}; use linked_hash_map::LinkedHashMap; use page::LocalPageEndpoint; use handlers::FetchControl; pub enum ContentStatus { - Fetching(Arc), + Fetching(FetchControl), Ready(LocalPageEndpoint), } diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs deleted file mode 100644 index 006858e73..000000000 --- a/dapps/src/apps/fetcher.rs +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2015, 2016 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 . - -//! Fetchable Dapps support. -//! Manages downloaded (cached) Dapps and downloads them when necessary. -//! Uses `URLHint` to resolve addresses into Dapps bundle file location. - -use zip; -use std::{fs, env, fmt}; -use std::io::{self, Read, Write}; -use std::path::PathBuf; -use std::sync::Arc; -use rustc_serialize::hex::FromHex; -use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult}; - -use hyper; -use hyper::status::StatusCode; - -use random_filename; -use SyncStatus; -use util::{Mutex, H256}; -use util::sha3::sha3; -use page::{LocalPageEndpoint, PageCache}; -use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator}; -use endpoint::{Endpoint, EndpointPath, Handler}; -use apps::cache::{ContentCache, ContentStatus}; -use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; - -/// Limit of cached dapps/content -const MAX_CACHED_DAPPS: usize = 20; - -pub struct ContentFetcher { - dapps_path: PathBuf, - resolver: R, - cache: Arc>, - sync: Arc, - embeddable_on: Option<(String, u16)>, -} - -impl Drop for ContentFetcher { - fn drop(&mut self) { - // Clear cache path - let _ = fs::remove_dir_all(&self.dapps_path); - } -} - -impl ContentFetcher { - - pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>) -> Self { - let mut dapps_path = env::temp_dir(); - dapps_path.push(random_filename()); - - ContentFetcher { - dapps_path: dapps_path, - resolver: resolver, - sync: sync_status, - cache: Arc::new(Mutex::new(ContentCache::default())), - embeddable_on: embeddable_on, - } - } - - fn still_syncing(address: Option<(String, u16)>) -> Box { - Box::new(ContentHandler::error( - StatusCode::ServiceUnavailable, - "Sync In Progress", - "Your node is still syncing. We cannot resolve any content before it's fully synced.", - Some("Refresh"), - address, - )) - } - - #[cfg(test)] - fn set_status(&self, content_id: &str, status: ContentStatus) { - self.cache.lock().insert(content_id.to_owned(), status); - } - - pub fn contains(&self, content_id: &str) -> bool { - { - let mut cache = self.cache.lock(); - // Check if we already have the app - if cache.get(content_id).is_some() { - return true; - } - } - // fallback to resolver - if let Ok(content_id) = content_id.from_hex() { - // else try to resolve the app_id - let has_content = self.resolver.resolve(content_id).is_some(); - // if there is content or we are syncing return true - has_content || self.sync.is_major_importing() - } else { - false - } - } - - pub fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box { - let mut cache = self.cache.lock(); - let content_id = path.app_id.clone(); - - let (new_status, handler) = { - let status = cache.get(&content_id); - match status { - // Just serve the content - Some(&mut ContentStatus::Ready(ref endpoint)) => { - (None, endpoint.to_async_handler(path, control)) - }, - // Content is already being fetched - Some(&mut ContentStatus::Fetching(ref fetch_control)) => { - trace!(target: "dapps", "Content fetching in progress. Waiting..."); - (None, fetch_control.to_async_handler(path, control)) - }, - // We need to start fetching the content - None => { - trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id); - let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true."); - let content = self.resolver.resolve(content_hex); - - let cache = self.cache.clone(); - let id = content_id.clone(); - let on_done = move |result: Option| { - let mut cache = cache.lock(); - match result { - Some(endpoint) => { - cache.insert(id.clone(), ContentStatus::Ready(endpoint)); - }, - // In case of error - None => { - cache.remove(&id); - }, - } - }; - - match content { - // Don't serve dapps if we are still syncing (but serve content) - Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_on.clone())) - }, - Some(URLHintResult::Dapp(dapp)) => { - let (handler, fetch_control) = ContentFetcherHandler::new( - dapp.url(), - path, - control, - DappInstaller { - id: content_id.clone(), - dapps_path: self.dapps_path.clone(), - on_done: Box::new(on_done), - embeddable_on: self.embeddable_on.clone(), - }, - self.embeddable_on.clone(), - ); - - (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) - }, - Some(URLHintResult::Content(content)) => { - let (handler, fetch_control) = ContentFetcherHandler::new( - content.url, - path, - control, - ContentInstaller { - id: content_id.clone(), - mime: content.mime, - content_path: self.dapps_path.clone(), - on_done: Box::new(on_done), - }, - self.embeddable_on.clone(), - ); - - (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) - }, - None if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_on.clone())) - }, - None => { - // This may happen when sync status changes in between - // `contains` and `to_handler` - (None, Box::new(ContentHandler::error( - StatusCode::NotFound, - "Resource Not Found", - "Requested resource was not found.", - None, - self.embeddable_on.clone(), - )) as Box) - }, - } - }, - } - }; - - if let Some(status) = new_status { - cache.clear_garbage(MAX_CACHED_DAPPS); - cache.insert(content_id, status); - } - - handler - } -} - -#[derive(Debug)] -pub enum ValidationError { - Io(io::Error), - Zip(zip::result::ZipError), - InvalidContentId, - ManifestNotFound, - ManifestSerialization(String), - HashMismatch { expected: H256, got: H256, }, -} - -impl fmt::Display for ValidationError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io), - ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip), - ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."), - ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."), - ValidationError::ManifestSerialization(ref err) => { - write!(f, "There was an error during Dapp Manifest serialization: {:?}", err) - }, - ValidationError::HashMismatch { ref expected, ref got } => { - write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got) - }, - } - } -} - -impl From for ValidationError { - fn from(err: io::Error) -> Self { - ValidationError::Io(err) - } -} - -impl From for ValidationError { - fn from(err: zip::result::ZipError) -> Self { - ValidationError::Zip(err) - } -} - -struct ContentInstaller { - id: String, - mime: String, - content_path: PathBuf, - on_done: Box) + Send>, -} - -impl ContentValidator for ContentInstaller { - type Error = ValidationError; - - fn validate_and_install(&self, path: PathBuf) -> Result { - let validate = || { - // Create dir - try!(fs::create_dir_all(&self.content_path)); - - // Validate hash - let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); - let hash = try!(sha3(&mut file_reader)); - let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); - if id != hash { - return Err(ValidationError::HashMismatch { - expected: id, - got: hash, - }); - } - - // And prepare path for a file - let filename = path.file_name().expect("We always fetch a file."); - let mut content_path = self.content_path.clone(); - content_path.push(&filename); - - if content_path.exists() { - try!(fs::remove_dir_all(&content_path)) - } - - try!(fs::copy(&path, &content_path)); - Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)) - }; - - // Make sure to always call on_done (even in case of errors)! - let result = validate(); - (self.on_done)(result.as_ref().ok().cloned()); - result - } -} - - -struct DappInstaller { - id: String, - dapps_path: PathBuf, - on_done: Box) + Send>, - embeddable_on: Option<(String, u16)>, -} - -impl DappInstaller { - fn find_manifest(zip: &mut zip::ZipArchive) -> Result<(Manifest, PathBuf), ValidationError> { - for i in 0..zip.len() { - let mut file = try!(zip.by_index(i)); - - if !file.name().ends_with(MANIFEST_FILENAME) { - continue; - } - - // try to read manifest - let mut manifest = String::new(); - let manifest = file - .read_to_string(&mut manifest).ok() - .and_then(|_| deserialize_manifest(manifest).ok()); - - if let Some(manifest) = manifest { - let mut manifest_location = PathBuf::from(file.name()); - manifest_location.pop(); // get rid of filename - return Ok((manifest, manifest_location)); - } - } - - Err(ValidationError::ManifestNotFound) - } - - fn dapp_target_path(&self, manifest: &Manifest) -> PathBuf { - let mut target = self.dapps_path.clone(); - target.push(&manifest.id); - target - } -} - -impl ContentValidator for DappInstaller { - type Error = ValidationError; - - fn validate_and_install(&self, path: PathBuf) -> Result { - trace!(target: "dapps", "Opening dapp bundle at {:?}", path); - let validate = || { - let mut file_reader = io::BufReader::new(try!(fs::File::open(path))); - let hash = try!(sha3(&mut file_reader)); - let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); - if id != hash { - return Err(ValidationError::HashMismatch { - expected: id, - got: hash, - }); - } - let file = file_reader.into_inner(); - // Unpack archive - let mut zip = try!(zip::ZipArchive::new(file)); - // First find manifest file - let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip)); - // Overwrite id to match hash - manifest.id = self.id.clone(); - - let target = self.dapp_target_path(&manifest); - - // Remove old directory - if target.exists() { - warn!(target: "dapps", "Overwriting existing dapp: {}", manifest.id); - try!(fs::remove_dir_all(target.clone())); - } - - // Unpack zip - for i in 0..zip.len() { - let mut file = try!(zip.by_index(i)); - // TODO [todr] Check if it's consistent on windows. - let is_dir = file.name().chars().rev().next() == Some('/'); - - let file_path = PathBuf::from(file.name()); - let location_in_manifest_base = file_path.strip_prefix(&manifest_dir); - // Create files that are inside manifest directory - if let Ok(location_in_manifest_base) = location_in_manifest_base { - let p = target.join(location_in_manifest_base); - // Check if it's a directory - if is_dir { - try!(fs::create_dir_all(p)); - } else { - let mut target = try!(fs::File::create(p)); - try!(io::copy(&mut file, &mut target)); - } - } - } - - // Write manifest - let manifest_str = try!(serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)); - let manifest_path = target.join(MANIFEST_FILENAME); - let mut manifest_file = try!(fs::File::create(manifest_path)); - try!(manifest_file.write_all(manifest_str.as_bytes())); - // Create endpoint - let endpoint = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); - Ok(endpoint) - }; - - let result = validate(); - (self.on_done)(result.as_ref().ok().cloned()); - result - } -} - -#[cfg(test)] -mod tests { - use std::env; - use std::sync::Arc; - use util::Bytes; - use hash_fetch::urlhint::{URLHint, URLHintResult}; - - use apps::cache::ContentStatus; - use endpoint::EndpointInfo; - use page::LocalPageEndpoint; - use super::ContentFetcher; - - struct FakeResolver; - impl URLHint for FakeResolver { - fn resolve(&self, _id: Bytes) -> Option { - None - } - } - - #[test] - fn should_true_if_contains_the_app() { - // given - let path = env::temp_dir(); - let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None); - let handler = LocalPageEndpoint::new(path, EndpointInfo { - name: "fake".into(), - description: "".into(), - version: "".into(), - author: "".into(), - icon_url: "".into(), - }, Default::default(), None); - - // when - fetcher.set_status("test", ContentStatus::Ready(handler)); - fetcher.set_status("test2", ContentStatus::Fetching(Default::default())); - - // then - assert_eq!(fetcher.contains("test"), true); - assert_eq!(fetcher.contains("test2"), true); - assert_eq!(fetcher.contains("test3"), false); - } -} diff --git a/dapps/src/apps/fetcher/installers.rs b/dapps/src/apps/fetcher/installers.rs new file mode 100644 index 000000000..28f81127a --- /dev/null +++ b/dapps/src/apps/fetcher/installers.rs @@ -0,0 +1,255 @@ +// Copyright 2015, 2016 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 . + +use zip; +use std::{fs, fmt}; +use std::io::{self, Read, Write}; +use std::path::PathBuf; +use fetch::{self, Mime}; +use util::H256; + +use util::sha3::sha3; +use page::{LocalPageEndpoint, PageCache}; +use handlers::{ContentValidator, ValidatorResponse}; +use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; + +type OnDone = Box) + Send>; + +fn write_response_and_check_hash( + id: &str, + mut content_path: PathBuf, + filename: &str, + response: fetch::Response +) -> Result<(fs::File, PathBuf), ValidationError> { + // try to parse id + let id = id.parse().map_err(|_| ValidationError::InvalidContentId)?; + + // check if content exists + if content_path.exists() { + warn!(target: "dapps", "Overwriting existing content at 0x{:?}", id); + fs::remove_dir_all(&content_path)? + } + + // create directory + fs::create_dir_all(&content_path)?; + + // append filename + content_path.push(filename); + + // Now write the response + let mut file = io::BufWriter::new(fs::File::create(&content_path)?); + let mut reader = io::BufReader::new(response); + io::copy(&mut reader, &mut file)?; + file.flush()?; + + // Validate hash + // TODO [ToDr] calculate sha3 in-flight while reading the response + let mut file = io::BufReader::new(fs::File::open(&content_path)?); + let hash = sha3(&mut file)?; + if id == hash { + Ok((file.into_inner(), content_path)) + } else { + Err(ValidationError::HashMismatch { + expected: id, + got: hash, + }) + } +} + +pub struct Content { + id: String, + mime: Mime, + content_path: PathBuf, + on_done: OnDone, +} + +impl Content { + pub fn new(id: String, mime: Mime, content_path: PathBuf, on_done: OnDone) -> Self { + Content { + id: id, + mime: mime, + content_path: content_path, + on_done: on_done, + } + } +} + +impl ContentValidator for Content { + type Error = ValidationError; + + fn validate_and_install(&self, response: fetch::Response) -> Result { + let validate = |content_path: PathBuf| { + // Create dir + let (_, content_path) = write_response_and_check_hash(self.id.as_str(), content_path.clone(), self.id.as_str(), response)?; + + Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)) + }; + + // Prepare path for a file + let content_path = self.content_path.join(&self.id); + // Make sure to always call on_done (even in case of errors)! + let result = validate(content_path.clone()); + // remove the file if there was an error + if result.is_err() { + // Ignore errors since the file might not exist + let _ = fs::remove_dir_all(&content_path); + } + (self.on_done)(result.as_ref().ok().cloned()); + result.map(ValidatorResponse::Local) + } +} + +pub struct Dapp { + id: String, + dapps_path: PathBuf, + on_done: OnDone, + embeddable_on: Option<(String, u16)>, +} + +impl Dapp { + pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self { + Dapp { + id: id, + dapps_path: dapps_path, + on_done: on_done, + embeddable_on: embeddable_on, + } + } + + fn find_manifest(zip: &mut zip::ZipArchive) -> Result<(Manifest, PathBuf), ValidationError> { + for i in 0..zip.len() { + let mut file = zip.by_index(i)?; + + if !file.name().ends_with(MANIFEST_FILENAME) { + continue; + } + + // try to read manifest + let mut manifest = String::new(); + let manifest = file + .read_to_string(&mut manifest).ok() + .and_then(|_| deserialize_manifest(manifest).ok()); + + if let Some(manifest) = manifest { + let mut manifest_location = PathBuf::from(file.name()); + manifest_location.pop(); // get rid of filename + return Ok((manifest, manifest_location)); + } + } + + Err(ValidationError::ManifestNotFound) + } +} + +impl ContentValidator for Dapp { + type Error = ValidationError; + + fn validate_and_install(&self, response: fetch::Response) -> Result { + let validate = |dapp_path: PathBuf| { + let (file, zip_path) = write_response_and_check_hash(self.id.as_str(), dapp_path.clone(), &format!("{}.zip", self.id), response)?; + trace!(target: "dapps", "Opening dapp bundle at {:?}", zip_path); + // Unpack archive + let mut zip = zip::ZipArchive::new(file)?; + // First find manifest file + let (mut manifest, manifest_dir) = Self::find_manifest(&mut zip)?; + // Overwrite id to match hash + manifest.id = self.id.clone(); + + // Unpack zip + for i in 0..zip.len() { + let mut file = zip.by_index(i)?; + let is_dir = file.name().chars().rev().next() == Some('/'); + + let file_path = PathBuf::from(file.name()); + let location_in_manifest_base = file_path.strip_prefix(&manifest_dir); + // Create files that are inside manifest directory + if let Ok(location_in_manifest_base) = location_in_manifest_base { + let p = dapp_path.join(location_in_manifest_base); + // Check if it's a directory + if is_dir { + fs::create_dir_all(p)?; + } else { + let mut target = fs::File::create(p)?; + io::copy(&mut file, &mut target)?; + } + } + } + + // Remove zip + fs::remove_file(&zip_path)?; + + // Write manifest + let manifest_str = serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)?; + let manifest_path = dapp_path.join(MANIFEST_FILENAME); + let mut manifest_file = fs::File::create(manifest_path)?; + manifest_file.write_all(manifest_str.as_bytes())?; + // Create endpoint + let endpoint = LocalPageEndpoint::new(dapp_path, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); + Ok(endpoint) + }; + + // Prepare directory for dapp + let target = self.dapps_path.join(&self.id); + // Validate the dapp + let result = validate(target.clone()); + // remove the file if there was an error + if result.is_err() { + // Ignore errors since the file might not exist + let _ = fs::remove_dir_all(&target); + } + (self.on_done)(result.as_ref().ok().cloned()); + result.map(ValidatorResponse::Local) + } +} + +#[derive(Debug)] +pub enum ValidationError { + Io(io::Error), + Zip(zip::result::ZipError), + InvalidContentId, + ManifestNotFound, + ManifestSerialization(String), + HashMismatch { expected: H256, got: H256, }, +} + +impl fmt::Display for ValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io), + ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip), + ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."), + ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."), + ValidationError::ManifestSerialization(ref err) => { + write!(f, "There was an error during Dapp Manifest serialization: {:?}", err) + }, + ValidationError::HashMismatch { ref expected, ref got } => { + write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got) + }, + } + } +} + +impl From for ValidationError { + fn from(err: io::Error) -> Self { + ValidationError::Io(err) + } +} + +impl From for ValidationError { + fn from(err: zip::result::ZipError) -> Self { + ValidationError::Zip(err) + } +} diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs new file mode 100644 index 000000000..17539393c --- /dev/null +++ b/dapps/src/apps/fetcher/mod.rs @@ -0,0 +1,265 @@ +// Copyright 2015, 2016 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 . + +//! Fetchable Dapps support. +//! Manages downloaded (cached) Dapps and downloads them when necessary. +//! Uses `URLHint` to resolve addresses into Dapps bundle file location. + +mod installers; + +use std::{fs, env}; +use std::path::PathBuf; +use std::sync::Arc; +use rustc_serialize::hex::FromHex; +use fetch::{Client as FetchClient, Fetch}; +use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult}; +use parity_reactor::Remote; + +use hyper; +use hyper::status::StatusCode; + +use {SyncStatus, random_filename}; +use util::Mutex; +use page::LocalPageEndpoint; +use handlers::{ContentHandler, ContentFetcherHandler}; +use endpoint::{Endpoint, EndpointPath, Handler}; +use apps::cache::{ContentCache, ContentStatus}; + +/// Limit of cached dapps/content +const MAX_CACHED_DAPPS: usize = 20; + +pub trait Fetcher: Send + Sync + 'static { + fn contains(&self, content_id: &str) -> bool; + + fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box; +} + +pub struct ContentFetcher { + dapps_path: PathBuf, + resolver: R, + cache: Arc>, + sync: Arc, + embeddable_on: Option<(String, u16)>, + remote: Remote, + fetch: F, +} + +impl Drop for ContentFetcher { + fn drop(&mut self) { + // Clear cache path + let _ = fs::remove_dir_all(&self.dapps_path); + } +} + +impl ContentFetcher { + + pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self { + let mut dapps_path = env::temp_dir(); + dapps_path.push(random_filename()); + + ContentFetcher { + dapps_path: dapps_path, + resolver: resolver, + sync: sync_status, + cache: Arc::new(Mutex::new(ContentCache::default())), + embeddable_on: embeddable_on, + remote: remote, + fetch: fetch, + } + } + + fn still_syncing(address: Option<(String, u16)>) -> Box { + Box::new(ContentHandler::error( + StatusCode::ServiceUnavailable, + "Sync In Progress", + "Your node is still syncing. We cannot resolve any content before it's fully synced.", + Some("Refresh"), + address, + )) + } + + #[cfg(test)] + fn set_status(&self, content_id: &str, status: ContentStatus) { + self.cache.lock().insert(content_id.to_owned(), status); + } +} + +impl Fetcher for ContentFetcher { + fn contains(&self, content_id: &str) -> bool { + { + let mut cache = self.cache.lock(); + // Check if we already have the app + if cache.get(content_id).is_some() { + return true; + } + } + // fallback to resolver + if let Ok(content_id) = content_id.from_hex() { + // else try to resolve the app_id + let has_content = self.resolver.resolve(content_id).is_some(); + // if there is content or we are syncing return true + has_content || self.sync.is_major_importing() + } else { + false + } + } + + fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box { + let mut cache = self.cache.lock(); + let content_id = path.app_id.clone(); + + let (new_status, handler) = { + let status = cache.get(&content_id); + match status { + // Just serve the content + Some(&mut ContentStatus::Ready(ref endpoint)) => { + (None, endpoint.to_async_handler(path, control)) + }, + // Content is already being fetched + Some(&mut ContentStatus::Fetching(ref fetch_control)) if !fetch_control.is_deadline_reached() => { + trace!(target: "dapps", "Content fetching in progress. Waiting..."); + (None, fetch_control.to_async_handler(path, control)) + }, + // We need to start fetching the content + _ => { + trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id); + let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true."); + let content = self.resolver.resolve(content_hex); + + let cache = self.cache.clone(); + let id = content_id.clone(); + let on_done = move |result: Option| { + let mut cache = cache.lock(); + match result { + Some(endpoint) => cache.insert(id.clone(), ContentStatus::Ready(endpoint)), + // In case of error + None => cache.remove(&id), + }; + }; + + match content { + // Don't serve dapps if we are still syncing (but serve content) + Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { + (None, Self::still_syncing(self.embeddable_on.clone())) + }, + Some(URLHintResult::Dapp(dapp)) => { + let handler = ContentFetcherHandler::new( + dapp.url(), + path, + control, + installers::Dapp::new( + content_id.clone(), + self.dapps_path.clone(), + Box::new(on_done), + self.embeddable_on.clone(), + ), + self.embeddable_on.clone(), + self.remote.clone(), + self.fetch.clone(), + ); + + (Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box) + }, + Some(URLHintResult::Content(content)) => { + let handler = ContentFetcherHandler::new( + content.url, + path, + control, + installers::Content::new( + content_id.clone(), + content.mime, + self.dapps_path.clone(), + Box::new(on_done), + ), + self.embeddable_on.clone(), + self.remote.clone(), + self.fetch.clone(), + ); + + (Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box) + }, + None if self.sync.is_major_importing() => { + (None, Self::still_syncing(self.embeddable_on.clone())) + }, + None => { + // This may happen when sync status changes in between + // `contains` and `to_handler` + (None, Box::new(ContentHandler::error( + StatusCode::NotFound, + "Resource Not Found", + "Requested resource was not found.", + None, + self.embeddable_on.clone(), + )) as Box) + }, + } + }, + } + }; + + if let Some(status) = new_status { + cache.clear_garbage(MAX_CACHED_DAPPS); + cache.insert(content_id, status); + } + + handler + } +} + +#[cfg(test)] +mod tests { + use std::env; + use std::sync::Arc; + use util::Bytes; + use fetch::{Fetch, Client}; + use hash_fetch::urlhint::{URLHint, URLHintResult}; + use parity_reactor::Remote; + + use apps::cache::ContentStatus; + use endpoint::EndpointInfo; + use page::LocalPageEndpoint; + use super::{ContentFetcher, Fetcher}; + + struct FakeResolver; + impl URLHint for FakeResolver { + fn resolve(&self, _id: Bytes) -> Option { + None + } + } + + #[test] + fn should_true_if_contains_the_app() { + // given + let path = env::temp_dir(); + let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap()); + let handler = LocalPageEndpoint::new(path, EndpointInfo { + name: "fake".into(), + description: "".into(), + version: "".into(), + author: "".into(), + icon_url: "".into(), + }, Default::default(), None); + + // when + fetcher.set_status("test", ContentStatus::Ready(handler)); + fetcher.set_status("test2", ContentStatus::Fetching(Default::default())); + + // then + assert_eq!(fetcher.contains("test"), true); + assert_eq!(fetcher.contains("test2"), true); + assert_eq!(fetcher.contains("test3"), false); + } +} diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index b7188b0b4..12bad2e3d 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -37,8 +37,8 @@ fn local_dapps(dapps_path: String) -> Vec { let files = files.expect("Check is done earlier"); files.map(|dir| { - let entry = try!(dir); - let file_type = try!(entry.file_type()); + let entry = dir?; + let file_type = entry.file_type()?; // skip files if file_type.is_file() { @@ -79,7 +79,7 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { .and_then(|mut f| { // Reat file let mut s = String::new(); - try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))); + f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?; // Try to deserialize manifest deserialize_manifest(s) }) diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 3f3b1aaba..51f8f5572 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -14,10 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Arc; use endpoint::{Endpoints, Endpoint}; use page::PageEndpoint; use proxypac::ProxyPac; +use web::Web; +use fetch::Fetch; use parity_dapps::WebApp; +use parity_reactor::Remote; +use {WebProxyTokens}; mod cache; mod fs; @@ -27,22 +32,30 @@ pub mod manifest; extern crate parity_ui; pub const HOME_PAGE: &'static str = "home"; -pub const DAPPS_DOMAIN : &'static str = ".parity"; -pub const RPC_PATH : &'static str = "rpc"; -pub const API_PATH : &'static str = "api"; -pub const UTILS_PATH : &'static str = "parity-utils"; +pub const DAPPS_DOMAIN: &'static str = ".parity"; +pub const RPC_PATH: &'static str = "rpc"; +pub const API_PATH: &'static str = "api"; +pub const UTILS_PATH: &'static str = "parity-utils"; +pub const WEB_PATH: &'static str = "web"; pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { +pub fn all_endpoints( + dapps_path: String, + signer_address: Option<(String, u16)>, + web_proxy_tokens: Arc, + remote: Remote, + fetch: F, +) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins let mut pages = fs::local_endpoints(dapps_path, signer_address.clone()); // NOTE [ToDr] Dapps will be currently embeded on 8180 insert::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); - pages.insert("proxy".into(), ProxyPac::boxed(signer_address)); + pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone())); + pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone())); pages } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index d62b425d9..7634f1941 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -16,13 +16,14 @@ //! Hyper Server Handler that fetches a file during a request (proxy). -use std::{fs, fmt}; -use std::path::PathBuf; +use std::fmt; use std::sync::{mpsc, Arc}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Instant, Duration}; +use fetch::{self, Fetch}; +use futures::Future; +use parity_reactor::Remote; use util::Mutex; -use fetch::{Client, Fetch, FetchResult}; use hyper::{server, Decoder, Encoder, Next, Method, Control}; use hyper::net::HttpStream; @@ -30,33 +31,41 @@ use hyper::uri::RequestUri; use hyper::status::StatusCode; use endpoint::EndpointPath; -use handlers::ContentHandler; +use handlers::{ContentHandler, StreamingHandler}; use page::{LocalPageEndpoint, PageHandlerWaiting}; const FETCH_TIMEOUT: u64 = 30; +pub enum ValidatorResponse { + Local(LocalPageEndpoint), + Streaming(StreamingHandler), +} + +pub trait ContentValidator: Send + 'static { + type Error: fmt::Debug + fmt::Display; + + fn validate_and_install(&self, fetch::Response) -> Result; +} + enum FetchState { Waiting, NotStarted(String), Error(ContentHandler), - InProgress(mpsc::Receiver), + InProgress(mpsc::Receiver), + Streaming(StreamingHandler), Done(LocalPageEndpoint, Box), } enum WaitResult { Error(ContentHandler), Done(LocalPageEndpoint), + NonAwaitable, } -pub trait ContentValidator { - type Error: fmt::Debug + fmt::Display; - - fn validate_and_install(&self, path: PathBuf) -> Result; -} - +#[derive(Clone)] pub struct FetchControl { abort: Arc, - listeners: Mutex)>>, + listeners: Arc)>>>, deadline: Instant, } @@ -64,7 +73,7 @@ impl Default for FetchControl { fn default() -> Self { FetchControl { abort: Arc::new(AtomicBool::new(false)), - listeners: Mutex::new(Vec::new()), + listeners: Arc::new(Mutex::new(Vec::new())), deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT), } } @@ -87,10 +96,15 @@ impl FetchControl { match *status { FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())), FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())), + FetchState::Streaming(_) => self.notify(|| WaitResult::NonAwaitable), FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {}, } } + pub fn is_deadline_reached(&self) -> bool { + self.deadline < Instant::now() + } + pub fn abort(&self) { self.abort.store(true, Ordering::SeqCst); } @@ -130,7 +144,7 @@ impl server::Handler for WaitingHandler { page_handler.set_uri(&self.uri); FetchState::Done(endpoint, page_handler) }, - None => { + _ => { warn!("A result for waiting request was not received."); FetchState::Waiting }, @@ -138,6 +152,7 @@ impl server::Handler for WaitingHandler { match self.state { FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder), + FetchState::Streaming(ref mut handler) => handler.on_request_readable(decoder), FetchState::Error(ref mut handler) => handler.on_request_readable(decoder), _ => Next::write(), } @@ -146,6 +161,7 @@ impl server::Handler for WaitingHandler { fn on_response(&mut self, res: &mut server::Response) -> Next { match self.state { FetchState::Done(_, ref mut handler) => handler.on_response(res), + FetchState::Streaming(ref mut handler) => handler.on_response(res), FetchState::Error(ref mut handler) => handler.on_response(res), _ => Next::end(), } @@ -154,91 +170,170 @@ impl server::Handler for WaitingHandler { fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { match self.state { FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder), + FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder), FetchState::Error(ref mut handler) => handler.on_response_writable(encoder), _ => Next::end(), } } } -pub struct ContentFetcherHandler { - fetch_control: Arc, - control: Option, - status: FetchState, - client: Option, - installer: H, - path: EndpointPath, - uri: RequestUri, +#[derive(Clone)] +struct Errors { embeddable_on: Option<(String, u16)>, } -impl ContentFetcherHandler { +impl Errors { + fn download_error(&self, e: E) -> ContentHandler { + ContentHandler::error( + StatusCode::BadGateway, + "Download Error", + "There was an error when fetching the content.", + Some(&format!("{:?}", e)), + self.embeddable_on.clone(), + ) + } + + fn invalid_content(&self, e: E) -> ContentHandler { + ContentHandler::error( + StatusCode::BadGateway, + "Invalid Dapp", + "Downloaded bundle does not contain a valid content.", + Some(&format!("{:?}", e)), + self.embeddable_on.clone(), + ) + } + + fn timeout_error(&self) -> ContentHandler { + ContentHandler::error( + StatusCode::GatewayTimeout, + "Download Timeout", + &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), + None, + self.embeddable_on.clone(), + ) + } + + fn method_not_allowed(&self) -> ContentHandler { + ContentHandler::error( + StatusCode::MethodNotAllowed, + "Method Not Allowed", + "Only GET requests are allowed.", + None, + self.embeddable_on.clone(), + ) + } +} + +pub struct ContentFetcherHandler { + fetch_control: FetchControl, + control: Control, + remote: Remote, + status: FetchState, + fetch: F, + installer: Option, + path: EndpointPath, + errors: Errors, +} + +impl ContentFetcherHandler { pub fn new( url: String, path: EndpointPath, control: Control, - handler: H, + installer: H, embeddable_on: Option<(String, u16)>, - ) -> (Self, Arc) { - let fetch_control = Arc::new(FetchControl::default()); - let client = Client::default(); - let handler = ContentFetcherHandler { - fetch_control: fetch_control.clone(), - control: Some(control), - client: Some(client), + remote: Remote, + fetch: F, + ) -> Self { + ContentFetcherHandler { + fetch_control: FetchControl::default(), + control: control, + remote: remote, + fetch: fetch, status: FetchState::NotStarted(url), - installer: handler, + installer: Some(installer), path: path, - uri: RequestUri::default(), - embeddable_on: embeddable_on, - }; - - (handler, fetch_control) + errors: Errors { + embeddable_on: embeddable_on, + }, + } } - fn close_client(client: &mut Option) { - client.take() - .expect("After client is closed we are going into write, hence we can never close it again") - .close(); + pub fn fetch_control(&self) -> FetchControl { + self.fetch_control.clone() } - fn fetch_content(client: &mut Client, url: &str, abort: Arc, control: Control) -> Result, String> { - client.request(url, abort, Box::new(move || { - trace!(target: "dapps", "Fetching finished."); + fn fetch_content(&self, uri: RequestUri, url: &str, installer: H) -> mpsc::Receiver { + let (tx, rx) = mpsc::channel(); + let abort = self.fetch_control.abort.clone(); + + let path = self.path.clone(); + let tx2 = tx.clone(); + let control = self.control.clone(); + let errors = self.errors.clone(); + + let future = self.fetch.fetch_with_abort(url, abort.into()).then(move |result| { + trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result); + let new_state = match result { + Ok(response) => match installer.validate_and_install(response) { + Ok(ValidatorResponse::Local(endpoint)) => { + trace!(target: "dapps", "Validation OK. Returning response."); + let mut handler = endpoint.to_page_handler(path); + handler.set_uri(&uri); + FetchState::Done(endpoint, handler) + }, + Ok(ValidatorResponse::Streaming(handler)) => { + trace!(target: "dapps", "Validation OK. Streaming response."); + FetchState::Streaming(handler) + }, + Err(e) => { + trace!(target: "dapps", "Error while validating content: {:?}", e); + FetchState::Error(errors.invalid_content(e)) + }, + }, + Err(e) => { + warn!(target: "dapps", "Unable to fetch content: {:?}", e); + FetchState::Error(errors.download_error(e)) + }, + }; + // Content may be resolved when the connection is already dropped. + let _ = tx2.send(new_state); // Ignoring control errors let _ = control.ready(Next::read()); - })).map_err(|e| format!("{:?}", e)) + Ok(()) as Result<(), ()> + }); + + // make sure to run within fetch thread pool. + let future = self.fetch.process(future); + // spawn to event loop + let control = self.control.clone(); + let errors = self.errors.clone(); + self.remote.spawn_with_timeout(|| future, Duration::from_secs(FETCH_TIMEOUT), move || { + // Notify about the timeout + let _ = tx.send(FetchState::Error(errors.timeout_error())); + // Ignoring control errors + let _ = control.ready(Next::read()); + }); + + rx } } -impl server::Handler for ContentFetcherHandler { +impl server::Handler for ContentFetcherHandler { fn on_request(&mut self, request: server::Request) -> Next { let status = if let FetchState::NotStarted(ref url) = self.status { + let uri = request.uri().clone(); + let installer = self.installer.take().expect("Installer always set initialy; installer used only in on_request; on_request invoked only once; qed"); + Some(match *request.method() { // Start fetching content Method::Get => { trace!(target: "dapps", "Fetching content from: {:?}", url); - let control = self.control.take().expect("on_request is called only once, thus control is always Some"); - let client = self.client.as_mut().expect("on_request is called before client is closed."); - let fetch = Self::fetch_content(client, url, self.fetch_control.abort.clone(), control); - match fetch { - Ok(receiver) => FetchState::InProgress(receiver), - Err(e) => FetchState::Error(ContentHandler::error( - StatusCode::BadGateway, - "Unable To Start Content Download", - "Could not initialize download of the content. It might be a problem with the remote server.", - Some(&format!("{}", e)), - self.embeddable_on.clone(), - )), - } + let receiver = self.fetch_content(uri, url, installer); + FetchState::InProgress(receiver) }, // or return error - _ => FetchState::Error(ContentHandler::error( - StatusCode::MethodNotAllowed, - "Method Not Allowed", - "Only GET requests are allowed.", - None, - self.embeddable_on.clone(), - )), + _ => FetchState::Error(self.errors.method_not_allowed()), }) } else { None }; @@ -246,7 +341,6 @@ impl server::Handler for ContentFetcherHandler< self.fetch_control.set_status(&status); self.status = status; } - self.uri = request.uri().clone(); Next::read() } @@ -254,59 +348,16 @@ impl server::Handler for ContentFetcherHandler< fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { let (status, next) = match self.status { // Request may time out - FetchState::InProgress(_) if self.fetch_control.deadline < Instant::now() => { + FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => { trace!(target: "dapps", "Fetching dapp failed because of timeout."); - let timeout = ContentHandler::error( - StatusCode::GatewayTimeout, - "Download Timeout", - &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), - None, - self.embeddable_on.clone(), - ); - Self::close_client(&mut self.client); - (Some(FetchState::Error(timeout)), Next::write()) + (Some(FetchState::Error(self.errors.timeout_error())), Next::write()) }, FetchState::InProgress(ref receiver) => { // Check if there is an answer let rec = receiver.try_recv(); match rec { - // Unpack and validate - Ok(Ok(path)) => { - trace!(target: "dapps", "Fetching content finished. Starting validation ({:?})", path); - Self::close_client(&mut self.client); - // Unpack and verify - let state = match self.installer.validate_and_install(path.clone()) { - Err(e) => { - trace!(target: "dapps", "Error while validating content: {:?}", e); - FetchState::Error(ContentHandler::error( - StatusCode::BadGateway, - "Invalid Dapp", - "Downloaded bundle does not contain a valid content.", - Some(&format!("{:?}", e)), - self.embeddable_on.clone(), - )) - }, - Ok(endpoint) => { - let mut handler = endpoint.to_page_handler(self.path.clone()); - handler.set_uri(&self.uri); - FetchState::Done(endpoint, handler) - }, - }; - // Remove temporary zip file - let _ = fs::remove_file(path); - (Some(state), Next::write()) - }, - Ok(Err(e)) => { - warn!(target: "dapps", "Unable to fetch content: {:?}", e); - let error = ContentHandler::error( - StatusCode::BadGateway, - "Download Error", - "There was an error when fetching the content.", - Some(&format!("{:?}", e)), - self.embeddable_on.clone(), - ); - (Some(FetchState::Error(error)), Next::write()) - }, + // just return the new state + Ok(state) => (Some(state), Next::write()), // wait some more _ => (None, Next::wait()) } @@ -326,6 +377,7 @@ impl server::Handler for ContentFetcherHandler< fn on_response(&mut self, res: &mut server::Response) -> Next { match self.status { FetchState::Done(_, ref mut handler) => handler.on_response(res), + FetchState::Streaming(ref mut handler) => handler.on_response(res), FetchState::Error(ref mut handler) => handler.on_response(res), _ => Next::end(), } @@ -334,6 +386,7 @@ impl server::Handler for ContentFetcherHandler< fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { match self.status { FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder), + FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder), FetchState::Error(ref mut handler) => handler.on_response_writable(encoder), _ => Next::end(), } diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index bb292ad4d..a7295bf49 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -17,16 +17,18 @@ //! Hyper handlers implementations. mod auth; -mod echo; mod content; -mod redirect; +mod echo; mod fetch; +mod redirect; +mod streaming; pub use self::auth::AuthRequiredHandler; -pub use self::echo::EchoHandler; pub use self::content::ContentHandler; +pub use self::echo::EchoHandler; +pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse}; pub use self::redirect::Redirection; -pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; +pub use self::streaming::StreamingHandler; use url::Url; use hyper::{server, header, net, uri}; @@ -49,20 +51,30 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option } } + /// Extracts URL part from the Request. pub fn extract_url(req: &server::Request) -> Option { - match *req.uri() { + convert_uri_to_url(req.uri(), req.headers().get::()) +} + +/// Extracts URL given URI and Host header. +pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) -> Option { + match *uri { uri::RequestUri::AbsoluteUri(ref url) => { match Url::from_generic_url(url.clone()) { Ok(url) => Some(url), _ => None, } }, - uri::RequestUri::AbsolutePath { ref path, .. } => { + uri::RequestUri::AbsolutePath { ref path, ref query } => { + let query = match *query { + Some(ref query) => format!("?{}", query), + None => "".into(), + }; // Attempt to prepend the Host header (mandatory in HTTP/1.1) - let url_string = match req.headers().get::() { + let url_string = match host { Some(ref host) => { - format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) + format!("http://{}:{}{}{}", host.hostname, host.port.unwrap_or(80), path, query) }, None => return None, }; diff --git a/dapps/src/handlers/streaming.rs b/dapps/src/handlers/streaming.rs new file mode 100644 index 000000000..5d6b49631 --- /dev/null +++ b/dapps/src/handlers/streaming.rs @@ -0,0 +1,101 @@ +// Copyright 2015, 2016 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 . + +//! Content Stream Response + +use std::io::{self, Read}; + +use hyper::{header, server, Decoder, Encoder, Next}; +use hyper::net::HttpStream; +use hyper::mime::Mime; +use hyper::status::StatusCode; + +use handlers::add_security_headers; + +const BUFFER_SIZE: usize = 1024; + +pub struct StreamingHandler { + buffer: [u8; BUFFER_SIZE], + buffer_leftover: usize, + status: StatusCode, + content: io::BufReader, + mimetype: Mime, + safe_to_embed_on: Option<(String, u16)>, +} + +impl StreamingHandler { + pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { + StreamingHandler { + buffer: [0; BUFFER_SIZE], + buffer_leftover: 0, + status: status, + content: io::BufReader::new(content), + mimetype: mimetype, + safe_to_embed_on: embeddable_on, + } + } + + pub fn set_initial_content(&mut self, content: &str) { + assert_eq!(self.buffer_leftover, 0); + let bytes = content.as_bytes(); + self.buffer_leftover = bytes.len(); + self.buffer[0..self.buffer_leftover].copy_from_slice(bytes); + } +} + +impl server::Handler for StreamingHandler { + fn on_request(&mut self, _request: server::Request) -> Next { + Next::write() + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + Next::write() + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + res.set_status(self.status); + res.headers_mut().set(header::ContentType(self.mimetype.clone())); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); + Next::write() + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + fn handle_error(e: io::Error) -> Next { + match e.kind() { + ::std::io::ErrorKind::WouldBlock => Next::write(), + _ => Next::end(), + } + } + + let write_pos = self.buffer_leftover; + match self.content.read(&mut self.buffer[write_pos..]) { + Err(e) => handle_error(e), + Ok(read) => match encoder.write(&self.buffer[..write_pos + read]) { + Err(e) => handle_error(e), + Ok(0) => Next::end(), + Ok(wrote) => { + self.buffer_leftover = write_pos + read - wrote; + if self.buffer_leftover > 0 { + for i in self.buffer_leftover..write_pos + read { + self.buffer.swap(i, i - self.buffer_leftover); + } + } + Next::write() + }, + }, + } + } +} diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 72a246596..effa29fcc 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -61,6 +61,9 @@ extern crate parity_hash_fetch as hash_fetch; extern crate linked_hash_map; extern crate fetch; extern crate parity_dapps_glue as parity_dapps; +extern crate futures; +extern crate parity_reactor; + #[macro_use] extern crate log; #[macro_use] @@ -81,6 +84,7 @@ mod rpc; mod api; mod proxypac; mod url; +mod web; #[cfg(test)] mod tests; @@ -89,9 +93,11 @@ use std::net::SocketAddr; use std::collections::HashMap; use hash_fetch::urlhint::ContractClient; +use fetch::{Fetch, Client as FetchClient}; use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use ethcore_rpc::Extendable; +use parity_reactor::Remote; use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; @@ -105,16 +111,29 @@ impl SyncStatus for F where F: Fn() -> bool + Send + Sync { fn is_major_importing(&self) -> bool { self() } } +/// Validates Web Proxy tokens +pub trait WebProxyTokens: Send + Sync { + /// Should return true if token is a valid web proxy access token. + fn is_web_proxy_token_valid(&self, token: &String) -> bool; +} + +impl WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync { + fn is_web_proxy_token_valid(&self, token: &String) -> bool { self(token.to_owned()) } +} + /// Webapps HTTP+RPC server build. -pub struct ServerBuilder { +pub struct ServerBuilder { dapps_path: String, handler: Arc, registrar: Arc, sync_status: Arc, + web_proxy_tokens: Arc, signer_address: Option<(String, u16)>, + remote: Remote, + fetch: Option, } -impl Extendable for ServerBuilder { +impl Extendable for ServerBuilder { fn add_delegate(&self, delegate: IoDelegate) { self.handler.add_delegate(delegate); } @@ -122,29 +141,56 @@ impl Extendable for ServerBuilder { impl ServerBuilder { /// Construct new dapps server - pub fn new(dapps_path: String, registrar: Arc) -> Self { + pub fn new(dapps_path: String, registrar: Arc, remote: Remote) -> Self { ServerBuilder { dapps_path: dapps_path, handler: Arc::new(IoHandler::new()), registrar: registrar, sync_status: Arc::new(|| false), + web_proxy_tokens: Arc::new(|_| false), signer_address: None, + remote: remote, + fetch: None, + } + } +} + +impl ServerBuilder { + /// Set a fetch client to use. + pub fn fetch(self, fetch: X) -> ServerBuilder { + ServerBuilder { + dapps_path: self.dapps_path, + handler: self.handler, + registrar: self.registrar, + sync_status: self.sync_status, + web_proxy_tokens: self.web_proxy_tokens, + signer_address: self.signer_address, + remote: self.remote, + fetch: Some(fetch), } } /// Change default sync status. - pub fn with_sync_status(&mut self, status: Arc) { + pub fn sync_status(mut self, status: Arc) -> Self { self.sync_status = status; + self + } + + /// Change default web proxy tokens validator. + pub fn web_proxy_tokens(mut self, tokens: Arc) -> Self { + self.web_proxy_tokens = tokens; + self } /// Change default signer port. - pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) { + pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self { self.signer_address = signer_address; + 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, hosts: Option>) -> Result { + pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option>) -> Result { Server::start_http( addr, hosts, @@ -154,12 +200,15 @@ impl ServerBuilder { self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), + self.web_proxy_tokens.clone(), + self.remote.clone(), + self.fetch_client()?, ) } /// Asynchronously start server with `HTTP Basic Authentication`, /// return result with `Server` handle on success or an error. - pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option>, username: &str, password: &str) -> Result { + pub fn start_basic_auth_http(self, addr: &SocketAddr, hosts: Option>, username: &str, password: &str) -> Result { Server::start_http( addr, hosts, @@ -169,8 +218,18 @@ impl ServerBuilder { self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), + self.web_proxy_tokens.clone(), + self.remote.clone(), + self.fetch_client()?, ) } + + fn fetch_client(&self) -> Result { + match self.fetch.clone() { + Some(fetch) => Ok(fetch), + None => T::new().map_err(|_| ServerError::FetchInitialization), + } + } } /// Webapps HTTP server. @@ -206,7 +265,7 @@ impl Server { } } - fn start_http( + fn start_http( addr: &SocketAddr, hosts: Option>, authorization: A, @@ -215,11 +274,20 @@ impl Server { signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, + web_proxy_tokens: Arc, + remote: Remote, + fetch: F, ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(hash_fetch::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new( + hash_fetch::urlhint::URLHintContract::new(registrar), + sync_status, + signer_address.clone(), + remote.clone(), + fetch.clone(), + )); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone(), web_proxy_tokens, remote.clone(), fetch.clone())); let cors_domains = Self::cors_domains(signer_address.clone()); let special = Arc::new({ @@ -234,7 +302,7 @@ impl Server { }); let hosts = Self::allowed_hosts(hosts, format!("{}", addr)); - try!(hyper::Server::http(addr)) + hyper::Server::http(addr)? .handle(move |ctrl| router::Router::new( ctrl, signer_address.clone(), @@ -287,6 +355,8 @@ pub enum ServerError { IoError(std::io::Error), /// Other `hyper` error Other(hyper::error::Error), + /// Fetch service initialization error + FetchInitialization, } impl From for ServerError { @@ -299,7 +369,7 @@ impl From for ServerError { } /// Random filename -pub fn random_filename() -> String { +fn random_filename() -> String { use ::rand::Rng; let mut rng = ::rand::OsRng::new().unwrap(); rng.gen_ascii_chars().take(12).collect() diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index e8ab9ce14..08f942cea 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -20,11 +20,12 @@ use std::fs; use std::path::{Path, PathBuf}; use page::handler::{self, PageCache, PageHandlerWaiting}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; +use mime::Mime; #[derive(Debug, Clone)] pub struct LocalPageEndpoint { path: PathBuf, - mime: Option, + mime: Option, info: Option, cache: PageCache, embeddable_on: Option<(String, u16)>, @@ -41,7 +42,7 @@ impl LocalPageEndpoint { } } - pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self { + pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self { LocalPageEndpoint { path: path, mime: Some(mime), @@ -55,9 +56,9 @@ impl LocalPageEndpoint { self.path.clone() } - fn page_handler_with_mime(&self, path: EndpointPath, mime: &str) -> handler::PageHandler { + fn page_handler_with_mime(&self, path: EndpointPath, mime: &Mime) -> handler::PageHandler { handler::PageHandler { - app: LocalSingleFile { path: self.path.clone(), mime: mime.into() }, + app: LocalSingleFile { path: self.path.clone(), mime: format!("{}", mime) }, prefix: None, path: path, file: handler::ServedFile::new(None), diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index b444b1526..685b957d5 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -21,15 +21,16 @@ pub mod auth; mod host_validation; use address; +use std::cmp; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; -use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode}; +use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode}; use hyper::net::HttpStream; use apps::{self, DAPPS_DOMAIN}; -use apps::fetcher::ContentFetcher; +use apps::fetcher::Fetcher; use endpoint::{Endpoint, Endpoints, EndpointPath}; -use handlers::{Redirection, extract_url, ContentHandler}; +use handlers::{self, Redirection, ContentHandler}; use self::auth::{Authorization, Authorized}; /// Special endpoints are accessible on every domain (every dapp) @@ -45,7 +46,7 @@ pub struct Router { control: Option, signer_address: Option<(String, u16)>, endpoints: Arc, - fetch: Arc, + fetch: Arc, special: Arc>>, authorization: Arc, allowed_hosts: Option>, @@ -57,9 +58,11 @@ impl server::Handler for Router { fn on_request(&mut self, req: server::Request) -> Next { // Choose proper handler depending on path / domain - let url = extract_url(&req); + let url = handlers::extract_url(&req); let endpoint = extract_endpoint(&url); + let referer = extract_referer_endpoint(&req); let is_utils = endpoint.1 == SpecialEndpoint::Utils; + let is_get_request = *req.method() == hyper::Method::Get; trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); @@ -83,25 +86,42 @@ impl server::Handler for Router { 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); - self.handler = match endpoint { + self.handler = match (endpoint.0, endpoint.1, referer) { + // Handle invalid web requests that we can recover from + (ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url))) + if is_get_request + && referer.app_id == apps::WEB_PATH + && self.endpoints.contains_key(apps::WEB_PATH) + && !is_web_endpoint(path) + => + { + trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url); + // TODO [ToDr] Some nice util for this! + let using_domain = if referer.using_dapps_domains { 0 } else { 1 }; + let len = cmp::min(referer_url.path.len(), using_domain + 3); // token + protocol + hostname + let base = referer_url.path[..len].join("/"); + let requested = url.map(|u| u.path.join("/")).unwrap_or_default(); + Redirection::boxed(&format!("/{}/{}", base, requested)) + }, // 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."); self.special.get(endpoint) .expect("special known to contain key; qed") .to_async_handler(path.clone().unwrap_or_default(), control) }, // 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."); self.endpoints.get(&path.app_id) - .expect("special known to contain key; qed") + .expect("endpoints known to contain key; qed") .to_async_handler(path.clone(), control) }, // 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."); self.fetch.to_async_handler(path.clone(), control) }, @@ -110,7 +130,7 @@ impl server::Handler for Router { // It should be safe to remove it in (near) future. // // 404 for non-existent content - (Some(ref path), _) if *req.method() == hyper::Method::Get && path.app_id != "home" => { + (Some(ref path), _, _) if is_get_request && path.app_id != "home" => { trace!(target: "dapps", "Resolving to 404."); Box::new(ContentHandler::error( StatusCode::NotFound, @@ -121,7 +141,7 @@ impl server::Handler for Router { )) }, // Redirect any other GET request to signer. - _ if *req.method() == hyper::Method::Get => { + _ if is_get_request => { if let Some(signer_address) = self.signer_address.clone() { trace!(target: "dapps", "Redirecting to signer interface."); Redirection::boxed(&format!("http://{}", address(signer_address))) @@ -169,7 +189,7 @@ impl Router { pub fn new( control: Control, signer_address: Option<(String, u16)>, - content_fetcher: Arc, + content_fetcher: Arc, endpoints: Arc, special: Arc>>, authorization: Arc, @@ -192,6 +212,23 @@ impl Router { } } +fn is_web_endpoint(path: &Option) -> bool { + match *path { + Some(ref path) if path.app_id == apps::WEB_PATH => true, + _ => false, + } +} + +fn extract_referer_endpoint(req: &server::Request) -> Option<(EndpointPath, Url)> { + let referer = req.headers().get::(); + + let url = referer.and_then(|referer| Url::parse(&referer.0).ok()); + url.and_then(|url| { + let option = Some(url); + extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed"))) + }) +} + fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint) { fn special_endpoint(url: &Url) -> SpecialEndpoint { if url.path.len() <= 1 { diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index f7d988ce8..1abae3b49 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -51,16 +51,16 @@ impl Endpoint for RpcEndpoint { } } -const MIDDLEWARE_METHOD: &'static str = "eth_accounts"; - struct RpcMiddleware { handler: Arc, + methods: Vec, } impl RpcMiddleware { fn new(handler: Arc) -> Self { RpcMiddleware { handler: handler, + methods: vec!["eth_accounts".into(), "parity_accountsInfo".into()], } } @@ -68,9 +68,9 @@ impl RpcMiddleware { fn augment_request(&self, request: &mut Request, meta: Option) { use jsonrpc_core::{Call, Params, to_value}; - fn augment_call(call: &mut Call, meta: Option<&Meta>) { + fn augment_call(call: &mut Call, meta: Option<&Meta>, methods: &Vec) { match (call, meta) { - (&mut Call::MethodCall(ref mut method_call), Some(meta)) if &method_call.method == MIDDLEWARE_METHOD => { + (&mut Call::MethodCall(ref mut method_call), Some(meta)) if methods.contains(&method_call.method) => { let session = to_value(&meta.app_id); let params = match method_call.params { @@ -86,10 +86,10 @@ impl RpcMiddleware { } match *request { - Request::Single(ref mut call) => augment_call(call, meta.as_ref()), + Request::Single(ref mut call) => augment_call(call, meta.as_ref(), &self.methods), Request::Batch(ref mut vec) => { for mut call in vec { - augment_call(call, meta.as_ref()) + augment_call(call, meta.as_ref(), &self.methods) } }, } diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index 8540a407f..f98fd473d 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -14,7 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers_for_embed}; +use devtools::http_client; +use rustc_serialize::hex::FromHex; +use tests::helpers::{ + serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch, + serve_with_registrar_and_fetch, serve_with_registrar_and_fetch_and_threads, + request, assert_security_headers_for_embed, +}; #[test] fn should_resolve_dapp() { @@ -32,7 +38,7 @@ fn should_resolve_dapp() { ); // then - assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); + response.assert_status("HTTP/1.1 404 Not Found"); assert_eq!(registrar.calls.lock().len(), 2); assert_security_headers_for_embed(&response.headers); } @@ -41,14 +47,6 @@ fn should_resolve_dapp() { fn should_return_503_when_syncing_but_should_make_the_calls() { // given let (server, registrar) = serve_with_registrar_and_sync(); - { - let mut responses = registrar.responses.lock(); - let res1 = responses.get(0).unwrap().clone(); - let res2 = responses.get(1).unwrap().clone(); - // Registrar will be called twice - fill up the responses. - responses.push(res1); - responses.push(res2); - } // when let response = request(server, @@ -61,7 +59,378 @@ fn should_return_503_when_syncing_but_should_make_the_calls() { ); // then - assert_eq!(response.status, "HTTP/1.1 503 Service Unavailable".to_owned()); + response.assert_status("HTTP/1.1 503 Service Unavailable"); assert_eq!(registrar.calls.lock().len(), 4); assert_security_headers_for_embed(&response.headers); } + +const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000"; +const GAVCOIN_ICON: &'static str = "00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e000000000000000000000000000000000000000000000000000000000000007768747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f657468636f72652f646170702d6173736574732f623838653938336162616131613661363334356238643934343863313562313137646462353430652f746f6b656e732f676176636f696e2d36347836342e706e67000000000000000000"; + +#[test] +fn should_return_502_on_hash_mismatch() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_DAPP.from_hex().unwrap(); + registrar.set_result( + "94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 502 Bad Gateway"); + assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body); + assert_security_headers_for_embed(&response.headers); +} + +#[test] +fn should_return_error_for_invalid_dapp_zip() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_DAPP.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 502 Bad Gateway"); + assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body); + assert_security_headers_for_embed(&response.headers); +} + +#[test] +fn should_return_fetched_dapp_content() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_DAPP.from_hex().unwrap(); + registrar.set_result( + "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a".parse().unwrap(), + Ok(gavcoin.clone()) + ); + fetch.set_response(include_bytes!("../../res/gavcoin.zip")); + + // when + let response1 = http_client::request(server.addr(), + "\ + GET /index.html HTTP/1.1\r\n\ + Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + let response2 = http_client::request(server.addr(), + "\ + GET /manifest.json HTTP/1.1\r\n\ + Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db"); + fetch.assert_no_more_requests(); + + response1.assert_status("HTTP/1.1 200 OK"); + assert_security_headers_for_embed(&response1.headers); + assert_eq!( + response1.body, + r#"18 +

Hello Gavcoin!

+ +"# + ); + + response2.assert_status("HTTP/1.1 200 OK"); + assert_security_headers_for_embed(&response2.headers); + assert_eq!( + response2.body, + r#"BE +{ + "id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a", + "name": "Gavcoin", + "description": "Gavcoin", + "version": "1.0.0", + "author": "", + "iconUrl": "icon.png" +} +0 + +"# + ); +} + +#[test] +fn should_return_fetched_content() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_ICON.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 200 OK"); + response.assert_security_headers_present(None); +} + +#[test] +fn should_cache_content() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_ICON.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + let request_str = "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + "; + + let response = http_client::request(server.addr(), request_str); + fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png"); + fetch.assert_no_more_requests(); + response.assert_status("HTTP/1.1 200 OK"); + + // when + let response = http_client::request(server.addr(), request_str); + + // then + fetch.assert_no_more_requests(); + response.assert_status("HTTP/1.1 200 OK"); +} + +#[test] +fn should_not_request_content_twice() { + use std::thread; + + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch_and_threads(true); + let gavcoin = GAVCOIN_ICON.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + let request_str = "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + "; + let fire_request = || { + let addr = server.addr().to_owned(); + let req = request_str.to_owned(); + thread::spawn(move || { + http_client::request(&addr, &req) + }) + }; + let control = fetch.manual(); + + // when + + // Fire two requests at the same time + let r1 = fire_request(); + let r2 = fire_request(); + + // wait for single request in fetch, the second one should go into waiting state. + control.wait_for_requests(1); + control.respond(); + + let response1 = r1.join().unwrap(); + let response2 = r2.join().unwrap(); + + // then + fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png"); + fetch.assert_no_more_requests(); + response1.assert_status("HTTP/1.1 200 OK"); + response2.assert_status("HTTP/1.1 200 OK"); +} + +#[test] +fn should_stream_web_content() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/token/https/parity.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 200 OK"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_requested("https://parity.io/"); + fetch.assert_no_more_requests(); +} + +#[test] +fn should_return_error_on_invalid_token() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/invalidtoken/https/parity.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 400 Bad Request"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_no_more_requests(); +} + +#[test] +fn should_return_error_on_invalid_protocol() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/token/ftp/parity.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 400 Bad Request"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_no_more_requests(); +} + +#[test] +fn should_redirect_if_trailing_slash_is_missing() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/token/https/parity.io HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 302 Found"); + response.assert_header("Location", "/web/token/https/parity.io/"); + + fetch.assert_no_more_requests(); +} + +#[test] +fn should_disallow_non_get_requests() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + POST /token/https/parity.io/ HTTP/1.1\r\n\ + Host: web.parity\r\n\ + Content-Type: application/json\r\n\ + Connection: close\r\n\ + \r\n\ + 123\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 405 Method Not Allowed"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_no_more_requests(); +} + +#[test] +fn should_fix_absolute_requests_based_on_referer() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /styles.css HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + Referer: http://localhost:8080/web/token/https/parity.io/\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 302 Found"); + response.assert_header("Location", "/web/token/https/parity.io/styles.css"); + + fetch.assert_no_more_requests(); +} diff --git a/dapps/src/tests/helpers/fetch.rs b/dapps/src/tests/helpers/fetch.rs new file mode 100644 index 000000000..44e77a360 --- /dev/null +++ b/dapps/src/tests/helpers/fetch.rs @@ -0,0 +1,122 @@ +// Copyright 2015, 2016 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 . + +use std::{io, thread, time}; +use std::sync::{atomic, mpsc, Arc}; +use util::Mutex; + +use futures::{self, Future}; +use fetch::{self, Fetch}; + +pub struct FetchControl { + sender: mpsc::Sender<()>, + fetch: FakeFetch, +} + +impl FetchControl { + pub fn respond(self) { + self.sender.send(()) + .expect("Fetch cannot be finished without sending a response at least once."); + } + + pub fn wait_for_requests(&self, len: usize) { + const MAX_TIMEOUT_MS: u64 = 5000; + const ATTEMPTS: u64 = 10; + let mut attempts_left = ATTEMPTS; + loop { + let current = self.fetch.requested.lock().len(); + + if current == len { + break; + } else if attempts_left == 0 { + panic!( + "Timeout reached when waiting for pending requests. Expected: {}, current: {}", + len, current + ); + } else { + attempts_left -= 1; + // Should we handle spurious timeouts better? + thread::park_timeout(time::Duration::from_millis(MAX_TIMEOUT_MS / ATTEMPTS)); + } + } + } +} + +#[derive(Clone, Default)] +pub struct FakeFetch { + manual: Arc>>>, + response: Arc>>, + asserted: Arc, + requested: Arc>>, +} + +impl FakeFetch { + pub fn set_response(&self, data: &'static [u8]) { + *self.response.lock() = Some(data); + } + + pub fn manual(&self) -> FetchControl { + assert!(self.manual.lock().is_none(), "Only one manual control may be active."); + let (tx, rx) = mpsc::channel(); + *self.manual.lock() = Some(rx); + + FetchControl { + sender: tx, + fetch: self.clone(), + } + } + + pub fn assert_requested(&self, url: &str) { + let requests = self.requested.lock(); + let idx = self.asserted.fetch_add(1, atomic::Ordering::SeqCst); + + assert_eq!(requests.get(idx), Some(&url.to_owned()), "Expected fetch from specific URL."); + } + + pub fn assert_no_more_requests(&self) { + let requests = self.requested.lock(); + let len = self.asserted.load(atomic::Ordering::SeqCst); + assert_eq!(requests.len(), len, "Didn't expect any more requests, got: {:?}", &requests[len..]); + } +} + +impl Fetch for FakeFetch { + type Result = futures::BoxFuture; + + fn new() -> Result where Self: Sized { + Ok(FakeFetch::default()) + } + + fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result { + self.requested.lock().push(url.into()); + let manual = self.manual.clone(); + let response = self.response.clone(); + + let (tx, rx) = futures::oneshot(); + thread::spawn(move || { + if let Some(rx) = manual.lock().take() { + // wait for manual resume + let _ = rx.recv(); + } + + let data = response.lock().take().unwrap_or(b"Some content"); + let cursor = io::Cursor::new(data); + tx.complete(fetch::Response::from_reader(cursor)); + }); + + rx.map_err(|_| fetch::Error::Aborted).boxed() + } +} diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers/mod.rs similarity index 53% rename from dapps/src/tests/helpers.rs rename to dapps/src/tests/helpers/mod.rs index 87edd00e0..d3f97b35b 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -17,49 +17,22 @@ use std::env; use std::str; use std::sync::Arc; -use rustc_serialize::hex::FromHex; use env_logger::LogBuilder; use ServerBuilder; use Server; -use hash_fetch::urlhint::ContractClient; -use util::{Bytes, Address, Mutex, ToPretty}; +use fetch::Fetch; use devtools::http_client; +use parity_reactor::Remote; + +mod registrar; +mod fetch; + +use self::registrar::FakeRegistrar; +use self::fetch::FakeFetch; -const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; -const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; const SIGNER_PORT: u16 = 18180; -pub struct FakeRegistrar { - pub calls: Arc>>, - pub responses: Mutex>>, -} - -impl FakeRegistrar { - fn new() -> Self { - FakeRegistrar { - calls: Arc::new(Mutex::new(Vec::new())), - responses: Mutex::new( - vec![ - Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), - Ok(Vec::new()) - ] - ), - } - } -} - -impl ContractClient for FakeRegistrar { - fn registrar(&self) -> Result { - Ok(REGISTRAR.parse().unwrap()) - } - - fn call(&self, address: Address, data: Bytes) -> Result { - self.calls.lock().push((address.to_hex(), data.to_hex())); - self.responses.lock().remove(0) - } -} - fn init_logger() { // Initialize logger if let Ok(log) = env::var("RUST_LOG") { @@ -69,16 +42,21 @@ fn init_logger() { } } -pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc) { +pub fn init_server(hosts: Option>, process: F, remote: Remote) -> (Server, Arc) where + F: FnOnce(ServerBuilder) -> ServerBuilder, + B: Fetch, +{ init_logger(); 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 mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); - builder.with_sync_status(Arc::new(move || is_syncing)); - builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); + let server = process(ServerBuilder::new( + dapps_path.to_str().unwrap().into(), registrar.clone(), remote, + )) + .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) + .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(); ( - builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), + server, registrar, ) } @@ -88,25 +66,53 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { 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 mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); - builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); - builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() + ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync()) + .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) + .start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } pub fn serve_hosts(hosts: Option>) -> Server { - init_server(hosts, false).0 + init_server(hosts, |builder| builder, Remote::new_sync()).0 } pub fn serve_with_registrar() -> (Server, Arc) { - init_server(None, false) + init_server(None, |builder| builder, Remote::new_sync()) } pub fn serve_with_registrar_and_sync() -> (Server, Arc) { - init_server(None, true) + init_server(None, |builder| { + builder.sync_status(Arc::new(|| true)) + }, Remote::new_sync()) +} + +pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc) { + serve_with_registrar_and_fetch_and_threads(false) +} + +pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc) { + let fetch = FakeFetch::default(); + let f = fetch.clone(); + let (server, reg) = init_server(None, move |builder| { + builder.fetch(f.clone()) + }, if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() }); + + (server, fetch, reg) +} + +pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) { + let fetch = FakeFetch::default(); + let f = fetch.clone(); + let (server, _) = init_server(None, move |builder| { + builder + .fetch(f.clone()) + .web_proxy_tokens(Arc::new(move |token| &token == web_token)) + }, Remote::new_sync()); + + (server, fetch) } pub fn serve() -> Server { - init_server(None, false).0 + init_server(None, |builder| builder, Remote::new_sync()).0 } pub fn request(server: Server, request: &str) -> http_client::Response { diff --git a/dapps/src/tests/helpers/registrar.rs b/dapps/src/tests/helpers/registrar.rs new file mode 100644 index 000000000..342f3440c --- /dev/null +++ b/dapps/src/tests/helpers/registrar.rs @@ -0,0 +1,72 @@ +// Copyright 2015, 2016 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 . + +use std::str; +use std::sync::Arc; +use std::collections::HashMap; +use rustc_serialize::hex::FromHex; + +use hash_fetch::urlhint::ContractClient; +use util::{Bytes, Address, Mutex, H256, ToPretty}; + +const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; +const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; +const URLHINT_RESOLVE: &'static str = "267b6922"; +const DEFAULT_HASH: &'static str = "1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d"; + +pub struct FakeRegistrar { + pub calls: Arc>>, + pub responses: Mutex>>, +} + +impl FakeRegistrar { + pub fn new() -> Self { + FakeRegistrar { + calls: Arc::new(Mutex::new(Vec::new())), + responses: Mutex::new({ + let mut map = HashMap::new(); + map.insert( + (REGISTRAR.into(), "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".into()), + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + ); + map.insert( + (URLHINT.into(), format!("{}{}", URLHINT_RESOLVE, DEFAULT_HASH)), + Ok(vec![]) + ); + map + }), + } + } + + pub fn set_result(&self, hash: H256, result: Result) { + self.responses.lock().insert( + (URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)), + result + ); + } +} + +impl ContractClient for FakeRegistrar { + fn registrar(&self) -> Result { + Ok(REGISTRAR.parse().unwrap()) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + let call = (address.to_hex(), data.to_hex()); + self.calls.lock().push(call.clone()); + self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call)) + } +} diff --git a/dapps/src/url.rs b/dapps/src/url.rs index b8b8393cc..be5867d8c 100644 --- a/dapps/src/url.rs +++ b/dapps/src/url.rs @@ -37,6 +37,9 @@ pub struct Url { /// Empty entries of `""` correspond to trailing slashes. pub path: Vec, + /// The URL query. + pub query: Option, + /// The URL username field, from the userinfo section of the URL. /// /// `None` if the `@` character was not part of the input OR @@ -82,15 +85,17 @@ impl Url { _ => None, }; - let port = try!(raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))); - let host = try!(raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())).to_owned(); - let path = try!(raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())) + let port = raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))?; + let host = raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())?.to_owned(); + let path = raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())? .map(|part| part.to_owned()).collect(); + let query = raw_url.query().map(|x| x.to_owned()); Ok(Url { port: port, host: host, path: path, + query: query, raw: raw_url, username: username, password: password, diff --git a/dapps/src/web.rs b/dapps/src/web.rs new file mode 100644 index 000000000..ac10b4d7f --- /dev/null +++ b/dapps/src/web.rs @@ -0,0 +1,222 @@ +// Copyright 2015, 2016 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 . + +//! Serving web-based content (proxying) + +use std::sync::Arc; +use fetch::{self, Fetch}; +use parity_reactor::Remote; + +use hyper::{self, server, net, Next, Encoder, Decoder}; +use hyper::status::StatusCode; + +use apps; +use endpoint::{Endpoint, Handler, EndpointPath}; +use handlers::{ + ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse, + StreamingHandler, Redirection, extract_url, +}; +use url::Url; +use WebProxyTokens; + +pub type Embeddable = Option<(String, u16)>; + +pub struct Web { + embeddable_on: Embeddable, + web_proxy_tokens: Arc, + remote: Remote, + fetch: F, +} + +impl Web { + pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc, remote: Remote, fetch: F) -> Box { + Box::new(Web { + embeddable_on: embeddable_on, + web_proxy_tokens: web_proxy_tokens, + remote: remote, + fetch: fetch, + }) + } +} + +impl Endpoint for Web { + fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box { + Box::new(WebHandler { + control: control, + state: State::Initial, + path: path, + remote: self.remote.clone(), + fetch: self.fetch.clone(), + web_proxy_tokens: self.web_proxy_tokens.clone(), + embeddable_on: self.embeddable_on.clone(), + }) + } +} + +struct WebInstaller { + embeddable_on: Embeddable, +} + +impl ContentValidator for WebInstaller { + type Error = String; + + fn validate_and_install(&self, response: fetch::Response) -> Result { + let status = StatusCode::from_u16(response.status().to_u16()); + let is_html = response.is_html(); + let mime = response.content_type().unwrap_or(mime!(Text/Html)); + let mut handler = StreamingHandler::new( + response, + status, + mime, + self.embeddable_on.clone(), + ); + if is_html { + handler.set_initial_content(&format!(r#""#, apps::UTILS_PATH)); + } + Ok(ValidatorResponse::Streaming(handler)) + } +} + +enum State { + Initial, + Error(ContentHandler), + Redirecting(Redirection), + Fetching(ContentFetcherHandler), +} + +struct WebHandler { + control: hyper::Control, + state: State, + path: EndpointPath, + remote: Remote, + fetch: F, + web_proxy_tokens: Arc, + embeddable_on: Embeddable, +} + +impl WebHandler { + fn extract_target_url(&self, url: Option) -> Result> { + let (path, query) = match url { + Some(url) => (url.path, url.query), + None => { + return Err(State::Error(ContentHandler::error( + StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone() + ))); + } + }; + + // Support domain based routing. + let idx = match path.get(0).map(|m| m.as_ref()) { + Some(apps::WEB_PATH) => 1, + _ => 0, + }; + + // Check if token supplied in URL is correct. + match path.get(idx) { + Some(ref token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {}, + _ => { + return Err(State::Error(ContentHandler::error( + StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone() + ))); + } + } + + // Validate protocol + let protocol = match path.get(idx + 1).map(|a| a.as_str()) { + Some("http") => "http", + Some("https") => "https", + _ => { + return Err(State::Error(ContentHandler::error( + StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone() + ))); + } + }; + + // Redirect if address to main page does not end with / + if let None = path.get(idx + 3) { + return Err(State::Redirecting( + Redirection::new(&format!("/{}/", path.join("/"))) + )); + } + + let query = match query { + Some(query) => format!("?{}", query), + None => "".into(), + }; + + Ok(format!("{}://{}{}", protocol, path[idx + 2..].join("/"), query)) + } +} + +impl server::Handler for WebHandler { + fn on_request(&mut self, request: server::Request) -> Next { + let url = extract_url(&request); + + // First extract the URL (reject invalid URLs) + let target_url = match self.extract_target_url(url) { + Ok(url) => url, + Err(error) => { + self.state = error; + return Next::write(); + } + }; + + let mut handler = ContentFetcherHandler::new( + target_url, + self.path.clone(), + self.control.clone(), + WebInstaller { + embeddable_on: self.embeddable_on.clone(), + }, + self.embeddable_on.clone(), + self.remote.clone(), + self.fetch.clone(), + ); + let res = handler.on_request(request); + self.state = State::Fetching(handler); + + res + } + + fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { + match self.state { + State::Initial => Next::end(), + State::Error(ref mut handler) => handler.on_request_readable(decoder), + State::Redirecting(ref mut handler) => handler.on_request_readable(decoder), + State::Fetching(ref mut handler) => handler.on_request_readable(decoder), + } + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + match self.state { + State::Initial => Next::end(), + State::Error(ref mut handler) => handler.on_response(res), + State::Redirecting(ref mut handler) => handler.on_response(res), + State::Fetching(ref mut handler) => handler.on_response(res), + } + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + match self.state { + State::Initial => Next::end(), + State::Error(ref mut handler) => handler.on_response_writable(encoder), + State::Redirecting(ref mut handler) => handler.on_response_writable(encoder), + State::Fetching(ref mut handler) => handler.on_response_writable(encoder), + } + } +} + + diff --git a/db/src/database.rs b/db/src/database.rs index 36f349e32..0381cf4cf 100644 --- a/db/src/database.rs +++ b/db/src/database.rs @@ -73,10 +73,10 @@ impl WriteCache { match *cache_entry { WriteCacheEntry::Write(ref val) => { - try!(batch.put(&key, val)); + batch.put(&key, val)?; }, WriteCacheEntry::Remove => { - try!(batch.delete(&key)); + batch.delete(&key)?; }, } key.clone() @@ -87,14 +87,14 @@ impl WriteCache { removed_so_far = removed_so_far + 1; } if removed_so_far > 0 { - try!(db.write(batch)); + db.write(batch)?; } Ok(()) } /// flushes until cache is empty fn flush_all(&mut self, db: &DB) -> Result<(), Error> { - while !self.is_empty() { try!(self.flush(db, FLUSH_BATCH_SIZE)); } + while !self.is_empty() { self.flush(db, FLUSH_BATCH_SIZE)?; } Ok(()) } @@ -104,7 +104,7 @@ impl WriteCache { fn try_shrink(&mut self, db: &DB) -> Result<(), Error> { if self.entries.len() > self.preferred_len { - try!(self.flush(db, FLUSH_BATCH_SIZE)); + self.flush(db, FLUSH_BATCH_SIZE)?; } Ok(()) } @@ -135,7 +135,7 @@ impl Database { if db_lock.is_none() { return Ok(()); } let db = db_lock.as_ref().unwrap(); - try!(cache_lock.try_shrink(&db)); + cache_lock.try_shrink(&db)?; Ok(()) } @@ -145,7 +145,7 @@ impl Database { if db_lock.is_none() { return Ok(()); } let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step"); - try!(cache_lock.flush_all(&db)); + cache_lock.flush_all(&db)?; Ok(()) } @@ -174,7 +174,7 @@ impl DatabaseService for Database { opts.set_block_based_table_factory(&block_opts); opts.set_prefix_extractor_fixed_size(size); } - *db = Some(try!(DB::open(&opts, &path))); + *db = Some(DB::open(&opts, &path)?); Ok(()) } @@ -185,7 +185,7 @@ impl DatabaseService for Database { } fn close(&self) -> Result<(), Error> { - try!(self.flush_all()); + self.flush_all()?; let mut db = self.db.write(); if db.is_none() { return Err(Error::IsClosed); } @@ -231,9 +231,9 @@ impl DatabaseService for Database { } } let db_lock = self.db.read(); - let db = try!(db_lock.as_ref().ok_or(Error::IsClosed)); + let db = db_lock.as_ref().ok_or(Error::IsClosed)?; - match try!(db.get(key)) { + match db.get(key)? { Some(db_vec) => { Ok(Some(db_vec.to_vec())) }, @@ -243,7 +243,7 @@ impl DatabaseService for Database { fn get_by_prefix(&self, prefix: &[u8]) -> Result>, Error> { let db_lock = self.db.read(); - let db = try!(db_lock.as_ref().ok_or(Error::IsClosed)); + let db = db_lock.as_ref().ok_or(Error::IsClosed)?; let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward)); match iter.next() { @@ -255,14 +255,14 @@ impl DatabaseService for Database { fn is_empty(&self) -> Result { let db_lock = self.db.read(); - let db = try!(db_lock.as_ref().ok_or(Error::IsClosed)); + let db = db_lock.as_ref().ok_or(Error::IsClosed)?; Ok(db.iterator(IteratorMode::Start).next().is_none()) } fn iter(&self) -> Result { let db_lock = self.db.read(); - let db = try!(db_lock.as_ref().ok_or(Error::IsClosed)); + let db = db_lock.as_ref().ok_or(Error::IsClosed)?; let mut iterators = self.iterators.write(); let next_iterator = iterators.keys().last().unwrap_or(&0) + 1; diff --git a/db/src/lib.rs.in b/db/src/lib.rs.in index 31dbfd71c..26d6f66cc 100644 --- a/db/src/lib.rs.in +++ b/db/src/lib.rs.in @@ -52,27 +52,27 @@ impl std::convert::From for ServiceError { pub fn blocks_service_url(db_path: &str) -> Result { let mut path = PathBuf::from(db_path); - try!(::std::fs::create_dir_all(db_path)); + ::std::fs::create_dir_all(db_path)?; path.push("blocks.ipc"); Ok(format!("ipc://{}", path.to_str().unwrap())) } pub fn extras_service_url(db_path: &str) -> Result { let mut path = PathBuf::from(db_path); - try!(::std::fs::create_dir_all(db_path)); + ::std::fs::create_dir_all(db_path)?; path.push("extras.ipc"); Ok(format!("ipc://{}", path.to_str().unwrap())) } pub fn blocks_client(db_path: &str) -> Result { - let url = try!(blocks_service_url(db_path)); - let client = try!(nanoipc::generic_client::>(&url)); + let url = blocks_service_url(db_path)?; + let client = nanoipc::generic_client::>(&url)?; Ok(client) } pub fn extras_client(db_path: &str) -> Result { - let url = try!(extras_service_url(db_path)); - let client = try!(nanoipc::generic_client::>(&url)); + let url = extras_service_url(db_path)?; + let client = nanoipc::generic_client::>(&url)?; Ok(client) } diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index bb3b896af..cd26b500d 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -27,6 +27,21 @@ pub struct Response { pub body: String, } +impl Response { + pub fn assert_header(&self, header: &str, value: &str) { + let header = format!("{}: {}", header, value); + assert!(self.headers.iter().find(|h| *h == &header).is_some(), "Couldn't find header {} in {:?}", header, &self.headers) + } + + pub fn assert_status(&self, status: &str) { + assert_eq!(self.status, status.to_owned(), "Got unexpected code. Body: {:?}", self.body); + } + + pub fn assert_security_headers_present(&self, port: Option) { + assert_security_headers_present(&self.headers, port) + } +} + pub fn read_block(lines: &mut Lines, all: bool) -> String { let mut block = String::new(); loop { @@ -65,7 +80,7 @@ fn connect(address: &SocketAddr) -> TcpStream { pub fn request(address: &SocketAddr, request: &str) -> Response { let mut req = connect(address); - req.set_read_timeout(Some(Duration::from_secs(1))).unwrap(); + req.set_read_timeout(Some(Duration::from_secs(2))).unwrap(); req.write_all(request.as_bytes()).unwrap(); let mut response = String::new(); diff --git a/ethash/src/compute.rs b/ethash/src/compute.rs index 269c3d863..efd0584d5 100644 --- a/ethash/src/compute.rs +++ b/ethash/src/compute.rs @@ -116,17 +116,17 @@ impl Light { pub fn from_file(block_number: u64) -> io::Result { let seed_compute = SeedHashCompute::new(); let path = Light::file_path(seed_compute.get_seedhash(block_number)); - let mut file = try!(File::open(path)); + let mut file = File::open(path)?; let cache_size = get_cache_size(block_number); - if try!(file.metadata()).len() != cache_size as u64 { + if file.metadata()?.len() != cache_size as u64 { return Err(io::Error::new(io::ErrorKind::Other, "Cache file size mismatch")); } let num_nodes = cache_size / NODE_BYTES; let mut nodes: Vec = Vec::new(); nodes.resize(num_nodes, unsafe { mem::uninitialized() }); let buf = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut u8, cache_size) }; - try!(file.read_exact(buf)); + file.read_exact(buf)?; Ok(Light { cache: nodes, block_number: block_number, @@ -144,16 +144,16 @@ impl Light { if deprecated.exists() { debug!(target: "ethash", "removing: {:?}", &deprecated); - try!(fs::remove_file(deprecated)); + fs::remove_file(deprecated)?; } } - try!(fs::create_dir_all(path.parent().unwrap())); - let mut file = try!(File::create(&path)); + fs::create_dir_all(path.parent().unwrap())?; + let mut file = File::create(&path)?; let cache_size = self.cache.len() * NODE_BYTES; let buf = unsafe { slice::from_raw_parts(self.cache.as_ptr() as *const u8, cache_size) }; - try!(file.write(buf)); + file.write(buf)?; Ok(path) } } diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 9a594f3dd..14d032821 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -26,6 +26,7 @@ use ethcore::block_status::BlockStatus; use ethcore::verification::queue::{HeaderQueue, QueueInfo}; use ethcore::transaction::{SignedTransaction, PendingTransaction}; use ethcore::blockchain_info::BlockChainInfo; +use ethcore::encoded; use io::IoChannel; use util::hash::{H256, H256FastMap}; @@ -90,11 +91,11 @@ impl Provider for Client { None } - fn block_header(&self, _id: BlockId) -> Option { + fn block_header(&self, _id: BlockId) -> Option { None } - fn block_body(&self, _id: BlockId) -> Option { + fn block_body(&self, _id: BlockId) -> Option { None } @@ -110,7 +111,7 @@ impl Provider for Client { Vec::new() } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(Bytes, Vec)> { + fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { None } diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index 61866f686..89ba2e6e8 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -135,10 +135,10 @@ impl RlpDecodable for CostTable { let mut header_proofs = None; for row in rlp.iter() { - let msg_id: u8 = try!(row.val_at(0)); + let msg_id: u8 = row.val_at(0)?; let cost = { - let base = try!(row.val_at(1)); - let per = try!(row.val_at(2)); + let base = row.val_at(1)?; + let per = row.val_at(2)?; Cost(base, per) }; @@ -155,12 +155,12 @@ impl RlpDecodable for CostTable { } Ok(CostTable { - headers: try!(headers.ok_or(DecoderError::Custom("No headers cost specified"))), - bodies: try!(bodies.ok_or(DecoderError::Custom("No bodies cost specified"))), - receipts: try!(receipts.ok_or(DecoderError::Custom("No receipts cost specified"))), - state_proofs: try!(state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))), - contract_codes: try!(contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))), - header_proofs: try!(header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))), + headers: headers.ok_or(DecoderError::Custom("No headers cost specified"))?, + bodies: bodies.ok_or(DecoderError::Custom("No bodies cost specified"))?, + receipts: receipts.ok_or(DecoderError::Custom("No receipts cost specified"))?, + state_proofs: state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))?, + contract_codes: contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))?, + header_proofs: header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))?, }) } } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 17898c24c..18df0f899 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -143,7 +143,7 @@ impl Peer { flow_params.recharge(&mut self.local_buffer); let max_cost = flow_params.compute_cost(kind, max); - try!(self.local_buffer.deduct_cost(max_cost)); + self.local_buffer.deduct_cost(max_cost)?; Ok(max_cost) } @@ -275,14 +275,14 @@ impl LightProtocol { /// with an event. pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result { let peers = self.peers.read(); - let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); + let peer = peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)?; let mut peer = peer.lock(); match peer.remote_flow.as_mut() { Some(&mut (ref mut buf, ref flow)) => { flow.recharge(buf); let max = flow.compute_cost(request.kind(), request.amount()); - try!(buf.deduct_cost(max)); + buf.deduct_cost(max)?; } None => return Err(Error::NotServer), } @@ -386,8 +386,8 @@ impl LightProtocol { // - check whether request was made // - check whether request kinds match fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result { - let req_id: usize = try!(raw.val_at(0)); - let cur_buffer: U256 = try!(raw.val_at(1)); + let req_id: usize = raw.val_at(0)?; + let cur_buffer: U256 = raw.val_at(1)?; trace!(target: "les", "pre-verifying response from peer {}, kind={:?}", peer, kind); @@ -582,7 +582,7 @@ impl LightProtocol { } }; - let (status, capabilities, flow_params) = try!(status::parse_handshake(data)); + let (status, capabilities, flow_params) = status::parse_handshake(data)?; trace!(target: "les", "Connected peer with chain head {:?}", (status.head_hash, status.head_num)); @@ -623,7 +623,7 @@ impl LightProtocol { return Ok(()) } - let announcement = try!(status::parse_announcement(data)); + let announcement = status::parse_announcement(data)?; // scope to ensure locks are dropped before moving into handler-space. { @@ -676,25 +676,25 @@ impl LightProtocol { let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); - let data = try!(data.at(1)); + let req_id: u64 = data.val_at(0)?; + let data = data.at(1)?; let start_block = { - if try!(data.at(0)).size() == 32 { - HashOrNumber::Hash(try!(data.val_at(0))) + if data.at(0)?.size() == 32 { + HashOrNumber::Hash(data.val_at(0)?) } else { - HashOrNumber::Number(try!(data.val_at(0))) + HashOrNumber::Number(data.val_at(0)?) } }; let req = request::Headers { start: start_block, - max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(1))), - skip: try!(data.val_at(2)), - reverse: try!(data.val_at(3)), + max: ::std::cmp::min(MAX_HEADERS, data.val_at(1)?), + skip: data.val_at(2)?, + reverse: data.val_at(3)?, }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max)); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max)?; let response = self.provider.block_headers(req); let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); @@ -706,7 +706,7 @@ impl LightProtocol { stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); for header in response { - stream.append_raw(&header, 1); + stream.append_raw(&header.into_inner(), 1); } stream.out() @@ -717,8 +717,8 @@ impl LightProtocol { // Receive a response for block headers. fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw)); - let raw_headers: Vec<_> = try!(raw.at(2)).iter().map(|x| x.as_raw().to_owned()).collect(); + let req_id = self.pre_verify_response(peer, request::Kind::Headers, &raw)?; + let raw_headers: Vec<_> = raw.at(2)?.iter().map(|x| x.as_raw().to_owned()).collect(); for handler in &self.handlers { handler.on_block_headers(&Ctx { @@ -745,16 +745,19 @@ impl LightProtocol { }; let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); + let req_id: u64 = data.val_at(0)?; let req = request::Bodies { - block_hashes: try!(try!(data.at(1)).iter().take(MAX_BODIES).map(|x| x.as_val()).collect()) + block_hashes: data.at(1)?.iter() + .take(MAX_BODIES) + .map(|x| x.as_val()) + .collect::>()? }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len())); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len())?; let response = self.provider.block_bodies(req); - let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + let response_len = response.iter().filter(|x| x.is_some()).count(); let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); @@ -765,7 +768,10 @@ impl LightProtocol { stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); for body in response { - stream.append_raw(&body, 1); + match body { + Some(body) => stream.append_raw(&body.into_inner(), 1), + None => stream.append_empty_data(), + }; } stream.out() @@ -776,8 +782,8 @@ impl LightProtocol { // Receive a response for block bodies. fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw)); - let raw_bodies: Vec = try!(raw.at(2)).iter().map(|x| x.as_raw().to_owned()).collect(); + let req_id = self.pre_verify_response(peer, request::Kind::Bodies, &raw)?; + let raw_bodies: Vec = raw.at(2)?.iter().map(|x| x.as_raw().to_owned()).collect(); for handler in &self.handlers { handler.on_block_bodies(&Ctx { @@ -804,13 +810,16 @@ impl LightProtocol { }; let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); + let req_id: u64 = data.val_at(0)?; let req = request::Receipts { - block_hashes: try!(try!(data.at(1)).iter().take(MAX_RECEIPTS).map(|x| x.as_val()).collect()) + block_hashes: data.at(1)?.iter() + .take(MAX_RECEIPTS) + .map(|x| x.as_val()) + .collect::>()? }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len())); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len())?; let response = self.provider.receipts(req); let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); @@ -835,11 +844,11 @@ impl LightProtocol { // Receive a response for receipts. fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw)); - let raw_receipts: Vec> = try!(try!(raw.at(2)) + let req_id = self.pre_verify_response(peer, request::Kind::Receipts, &raw)?; + let raw_receipts: Vec> = raw.at(2)? .iter() .map(|x| x.as_val()) - .collect()); + .collect::>()?; for handler in &self.handlers { handler.on_receipts(&Ctx { @@ -866,24 +875,24 @@ impl LightProtocol { }; let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); + let req_id: u64 = data.val_at(0)?; let req = { - let requests: Result, Error> = try!(data.at(1)).iter().take(MAX_PROOFS).map(|x| { + let requests: Result, Error> = data.at(1)?.iter().take(MAX_PROOFS).map(|x| { Ok(request::StateProof { - block: try!(x.val_at(0)), - key1: try!(x.val_at(1)), - key2: if try!(x.at(2)).is_empty() { None } else { Some(try!(x.val_at(2))) }, - from_level: try!(x.val_at(3)), + block: x.val_at(0)?, + key1: x.val_at(1)?, + key2: if x.at(2)?.is_empty() { None } else { Some(x.val_at(2)?) }, + from_level: x.val_at(3)?, }) }).collect(); request::StateProofs { - requests: try!(requests), + requests: requests?, } }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::StateProofs, req.requests.len())); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::StateProofs, req.requests.len())?; let response = self.provider.proofs(req); let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); @@ -908,9 +917,9 @@ impl LightProtocol { // Receive a response for proofs. fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw)); + let req_id = self.pre_verify_response(peer, request::Kind::StateProofs, &raw)?; - let raw_proofs: Vec> = try!(raw.at(2)).iter() + let raw_proofs: Vec> = raw.at(2)?.iter() .map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect()) .collect(); @@ -939,22 +948,22 @@ impl LightProtocol { }; let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); + let req_id: u64 = data.val_at(0)?; let req = { - let requests: Result, Error> = try!(data.at(1)).iter().take(MAX_CODES).map(|x| { + let requests: Result, Error> = data.at(1)?.iter().take(MAX_CODES).map(|x| { Ok(request::ContractCode { - block_hash: try!(x.val_at(0)), - account_key: try!(x.val_at(1)), + block_hash: x.val_at(0)?, + account_key: x.val_at(1)?, }) }).collect(); request::ContractCodes { - code_requests: try!(requests), + code_requests: requests?, } }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Codes, req.code_requests.len())); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::Codes, req.code_requests.len())?; let response = self.provider.contract_codes(req); let response_len = response.iter().filter(|x| !x.is_empty()).count(); @@ -979,9 +988,11 @@ impl LightProtocol { // Receive a response for contract code. fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw)); + let req_id = self.pre_verify_response(peer, request::Kind::Codes, &raw)?; - let raw_code: Vec = try!(try!(raw.at(2)).iter().map(|x| x.as_val()).collect()); + let raw_code: Vec = raw.at(2)?.iter() + .map(|x| x.as_val()) + .collect::>()?; for handler in &self.handlers { handler.on_code(&Ctx { @@ -1008,23 +1019,23 @@ impl LightProtocol { }; let mut peer = peer.lock(); - let req_id: u64 = try!(data.val_at(0)); + let req_id: u64 = data.val_at(0)?; let req = { - let requests: Result, Error> = try!(data.at(1)).iter().take(MAX_PROOFS).map(|x| { + let requests: Result, Error> = data.at(1)?.iter().take(MAX_PROOFS).map(|x| { Ok(request::HeaderProof { - cht_number: try!(x.val_at(0)), - block_number: try!(x.val_at(1)), - from_level: try!(x.val_at(2)), + cht_number: x.val_at(0)?, + block_number: x.val_at(1)?, + from_level: x.val_at(2)?, }) }).collect(); request::HeaderProofs { - requests: try!(requests), + requests: requests?, } }; - let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::HeaderProofs, req.requests.len())); + let max_cost = peer.deduct_max(&self.flow_params, request::Kind::HeaderProofs, req.requests.len())?; let response = self.provider.header_proofs(req); let response_len = response.iter().filter(|x| &x[..] != ::rlp::EMPTY_LIST_RLP).count(); @@ -1051,13 +1062,15 @@ impl LightProtocol { fn header_proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { fn decode_res(raw: UntrustedRlp) -> Result<(Bytes, Vec), ::rlp::DecoderError> { Ok(( - try!(raw.val_at(0)), - try!(raw.at(1)).iter().map(|x| x.as_raw().to_owned()).collect(), + raw.val_at(0)?, + raw.at(1)?.iter().map(|x| x.as_raw().to_owned()).collect(), )) } - let req_id = try!(self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)); - let raw_proofs: Vec<_> = try!(try!(raw.at(2)).iter().map(decode_res).collect()); + let req_id = self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)?; + let raw_proofs: Vec<_> = raw.at(2)?.iter() + .map(decode_res) + .collect::>()?; for handler in &self.handlers { handler.on_header_proofs(&Ctx { @@ -1074,7 +1087,10 @@ impl LightProtocol { fn relay_transactions(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_TRANSACTIONS: usize = 256; - let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); + let txs: Vec<_> = data.iter() + .take(MAX_TRANSACTIONS) + .map(|x| x.as_val::()) + .collect::>()?; debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 90b8640cd..533d6b389 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -100,7 +100,7 @@ impl<'a> Parser<'a> { fn expect_raw(&mut self, key: Key) -> Result, DecoderError> { trace!(target: "les", "Expecting key {}", key.as_str()); let pre_pos = self.pos; - if let Some((k, val)) = try!(self.get_next()) { + if let Some((k, val)) = self.get_next()? { if k == key { return Ok(val) } } @@ -111,12 +111,12 @@ impl<'a> Parser<'a> { // get the next key and value RLP. fn get_next(&mut self) -> Result)>, DecoderError> { while self.pos < self.rlp.item_count() { - let pair = try!(self.rlp.at(self.pos)); - let k: String = try!(pair.val_at(0)); + let pair = self.rlp.at(self.pos)?; + let k: String = pair.val_at(0)?; self.pos += 1; match Key::from_str(&k) { - Some(key) => return Ok(Some((key , try!(pair.at(1))))), + Some(key) => return Ok(Some((key , pair.at(1)?))), None => continue, } } @@ -205,12 +205,12 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Optio }; let status = Status { - protocol_version: try!(parser.expect(Key::ProtocolVersion)), - network_id: try!(parser.expect(Key::NetworkId)), - head_td: try!(parser.expect(Key::HeadTD)), - head_hash: try!(parser.expect(Key::HeadHash)), - head_num: try!(parser.expect(Key::HeadNum)), - genesis_hash: try!(parser.expect(Key::GenesisHash)), + protocol_version: parser.expect(Key::ProtocolVersion)?, + network_id: parser.expect(Key::NetworkId)?, + head_td: parser.expect(Key::HeadTD)?, + head_hash: parser.expect(Key::HeadHash)?, + head_num: parser.expect(Key::HeadNum)?, + genesis_hash: parser.expect(Key::GenesisHash)?, last_head: None, }; @@ -298,10 +298,10 @@ pub fn parse_announcement(rlp: UntrustedRlp) -> Result Result announcement.serve_headers = true, - Key::ServeStateSince => announcement.serve_state_since = Some(try!(item.as_val())), - Key::ServeChainSince => announcement.serve_chain_since = Some(try!(item.as_val())), + Key::ServeStateSince => announcement.serve_state_since = Some(item.as_val()?), + Key::ServeChainSince => announcement.serve_chain_since = Some(item.as_val()?), Key::TxRelay => announcement.tx_relay = true, _ => return Err(DecoderError::Custom("Nonsensical key in announcement")), } diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 0cfc8cac7..56ff32b54 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -21,6 +21,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; use ethcore::ids::BlockId; use ethcore::transaction::PendingTransaction; +use ethcore::encoded; use network::{PeerId, NodeId}; use net::buffer_flow::FlowParams; @@ -94,11 +95,11 @@ impl Provider for TestProvider { None } - fn block_header(&self, id: BlockId) -> Option { + fn block_header(&self, id: BlockId) -> Option { self.0.client.block_header(id) } - fn block_body(&self, id: BlockId) -> Option { + fn block_body(&self, id: BlockId) -> Option { self.0.client.block_body(id) } @@ -122,7 +123,7 @@ impl Provider for TestProvider { req.account_key.iter().chain(req.account_key.iter()).cloned().collect() } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(Bytes, Vec)> { + fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { None } @@ -273,7 +274,7 @@ fn get_block_headers() { response_stream.append(&req_id).append(&new_buf).begin_list(10); for header in headers { - response_stream.append_raw(&header, 1); + response_stream.append_raw(&header.into_inner(), 1); } response_stream.out() @@ -320,7 +321,7 @@ fn get_block_bodies() { response_stream.append(&req_id).append(&new_buf).begin_list(10); for body in bodies { - response_stream.append_raw(&body, 1); + response_stream.append_raw(&body.into_inner(), 1); } response_stream.out() diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index afc5294fa..0b94077ab 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -21,6 +21,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; +use ethcore::encoded; use util::{Bytes, H256}; @@ -52,9 +53,8 @@ pub trait Provider: Send + Sync { /// /// The returned vector may have any length in the range [0, `max`], but the /// results within must adhere to the `skip` and `reverse` parameters. - fn block_headers(&self, req: request::Headers) -> Vec { + fn block_headers(&self, req: request::Headers) -> Vec { use request::HashOrNumber; - use ethcore::views::HeaderView; if req.max == 0 { return Vec::new() } @@ -67,9 +67,9 @@ pub trait Provider: Send + Sync { return Vec::new(); } Some(header) => { - let num = HeaderView::new(&header).number(); + let num = header.number(); let canon_hash = self.block_header(BlockId::Number(num)) - .map(|h| HeaderView::new(&h).hash()); + .map(|h| h.hash()); if req.max == 1 || canon_hash != Some(hash) { // Non-canonical header or single header requested. @@ -92,19 +92,18 @@ pub trait Provider: Send + Sync { } /// Get a block header by id. - fn block_header(&self, id: BlockId) -> Option; + fn block_header(&self, id: BlockId) -> Option; /// Provide as many as possible of the requested blocks (minus the headers) encoded /// in RLP format. - fn block_bodies(&self, req: request::Bodies) -> Vec { + fn block_bodies(&self, req: request::Bodies) -> Vec> { req.block_hashes.into_iter() .map(|hash| self.block_body(BlockId::Hash(hash))) - .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) .collect() } /// Get a block body by id. - fn block_body(&self, id: BlockId) -> Option; + fn block_body(&self, id: BlockId) -> Option; /// Provide the receipts as many as possible of the requested blocks. /// Returns a vector of RLP-encoded lists of receipts. @@ -169,7 +168,7 @@ pub trait Provider: Send + Sync { None => rlp::EMPTY_LIST_RLP.to_vec(), Some((header, proof)) => { let mut stream = RlpStream::new_list(2); - stream.append_raw(&header, 1).begin_list(proof.len()); + stream.append_raw(&header.into_inner(), 1).begin_list(proof.len()); for node in proof { stream.append_raw(&node, 1); @@ -184,7 +183,7 @@ pub trait Provider: Send + Sync { /// Provide a header proof from a given Canonical Hash Trie as well as the /// corresponding header. The first element is the block header and the /// second is a merkle proof of the CHT. - fn header_proof(&self, req: request::HeaderProof) -> Option<(Bytes, Vec)>; + fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec)>; /// Provide pending transactions. fn ready_transactions(&self) -> Vec; @@ -204,11 +203,11 @@ impl Provider for T { Some(self.pruning_info().earliest_state) } - fn block_header(&self, id: BlockId) -> Option { + fn block_header(&self, id: BlockId) -> Option { BlockChainClient::block_header(self, id) } - fn block_body(&self, id: BlockId) -> Option { + fn block_body(&self, id: BlockId) -> Option { BlockChainClient::block_body(self, id) } @@ -227,7 +226,7 @@ impl Provider for T { self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash)) } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(Bytes, Vec)> { + fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { None } diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index 864f2d4f8..dde8ac2f8 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -20,7 +20,7 @@ use util::H256; /// Either a hash or a number. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub enum HashOrNumber { /// Block hash variant. Hash(H256), @@ -42,7 +42,7 @@ impl From for HashOrNumber { /// A request for block headers. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct Headers { /// Starting block number or hash. pub start: HashOrNumber, @@ -56,7 +56,7 @@ pub struct Headers { /// A request for specific block bodies. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct Bodies { /// Hashes which bodies are being requested for. pub block_hashes: Vec @@ -67,7 +67,7 @@ pub struct Bodies { /// This request is answered with a list of transaction receipts for each block /// requested. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct Receipts { /// Block hashes to return receipts for. pub block_hashes: Vec, @@ -75,7 +75,7 @@ pub struct Receipts { /// A request for a state proof #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct StateProof { /// Block hash to query state from. pub block: H256, @@ -90,7 +90,7 @@ pub struct StateProof { /// A request for state proofs. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct StateProofs { /// All the proof requests. pub requests: Vec, @@ -98,7 +98,7 @@ pub struct StateProofs { /// A request for contract code. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct ContractCode { /// Block hash pub block_hash: H256, @@ -108,7 +108,7 @@ pub struct ContractCode { /// A request for contract code. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct ContractCodes { /// Block hash and account key (== sha3(address)) pairs to fetch code for. pub code_requests: Vec, @@ -116,7 +116,7 @@ pub struct ContractCodes { /// A request for a header proof from the Canonical Hash Trie. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, @@ -128,7 +128,7 @@ pub struct HeaderProof { /// A request for header proofs from the CHT. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub struct HeaderProofs { /// All the proof requests. pub requests: Vec, @@ -136,7 +136,7 @@ pub struct HeaderProofs { /// Kinds of requests. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub enum Kind { /// Requesting headers. Headers, @@ -154,7 +154,7 @@ pub enum Kind { /// Encompasses all possible types of requests in a single structure. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", derive(Binary))] +#[cfg_attr(feature = "ipc", binary)] pub enum Request { /// Requesting headers. Headers(Headers), @@ -194,4 +194,4 @@ impl Request { Request::HeaderProofs(ref req) => req.requests.len(), } } -} \ No newline at end of file +} diff --git a/ethcore/light/src/types/mod.rs b/ethcore/light/src/types/mod.rs index 86d269766..60765dfc5 100644 --- a/ethcore/light/src/types/mod.rs +++ b/ethcore/light/src/types/mod.rs @@ -17,9 +17,8 @@ //! Types used in the public (IPC) api which require custom code generation. #![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues - #[cfg(feature = "ipc")] include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); #[cfg(not(feature = "ipc"))] -include!("mod.rs.in"); \ No newline at end of file +include!("mod.rs.in"); diff --git a/ethcore/res/constructor.json b/ethcore/res/constructor.json new file mode 100644 index 000000000..0be5b3be4 --- /dev/null +++ b/ethcore/res/constructor.json @@ -0,0 +1,30 @@ +{ + "name": "GenesisConstructor", + "engine": { + "null": null + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2" + }, + "genesis": { + "seal": { + "generic": "0x" + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "balance": "1", "constructor": "60606040526000805460ff19166001179055346000575b6075806100246000396000f300606060405263ffffffff60e060020a60003504166394b91deb81146022575b6000565b34600057602c6040565b604080519115158252519081900360200190f35b60005460ff16815600a165627a7a723058207882eb60ebce23178b3fa06d4cd8e5adc17711937ccddacb18a04abca2a2c9ee0029" } + } +} diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index 223978ca4..c1879d459 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -27,6 +27,7 @@ "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x1", + "chainID": "0x3d", "forkBlock": "0x1d4c00", "forkCanonHash": "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" }, diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index d21756250..2adea0f8c 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -27,6 +27,7 @@ "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x2", + "chainID": "0x3e", "forkBlock": "0x1b34d8", "forkCanonHash": "0xf376243aeff1f256d970714c3de9fd78fa4e63cf63e32a51fe1169e375d98145" }, diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index dab19dbc0..5b56c697c 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -127,32 +127,32 @@ impl AccountProvider { let acc = Random.generate().expect("secp context has generation capabilities; qed"); let public = acc.public().clone(); let secret = acc.secret().clone(); - let address = try!(self.sstore.insert_account(secret, password)); + let address = self.sstore.insert_account(secret, password)?; Ok((address, public)) } /// Inserts new account into underlying store. /// Does not unlock account! pub fn insert_account(&self, secret: Secret, password: &str) -> Result { - let address = try!(self.sstore.insert_account(secret, password)); + let address = self.sstore.insert_account(secret, password)?; Ok(address) } /// Import a new presale wallet. pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result { - let address = try!(self.sstore.import_presale(presale_json, password)); + let address = self.sstore.import_presale(presale_json, password)?; Ok(Address::from(address).into()) } /// Import a new presale wallet. pub fn import_wallet(&self, json: &[u8], password: &str) -> Result { - let address = try!(self.sstore.import_wallet(json, password)); + let address = self.sstore.import_wallet(json, password)?; Ok(Address::from(address).into()) } /// Returns addresses of all accounts. pub fn accounts(&self) -> Result, Error> { - let accounts = try!(self.sstore.accounts()); + let accounts = self.sstore.accounts()?; Ok(accounts) } @@ -229,7 +229,7 @@ impl AccountProvider { /// Returns each account along with name and meta. pub fn accounts_info(&self) -> Result, Error> { - let r: HashMap = try!(self.sstore.accounts()) + let r: HashMap = self.sstore.accounts()? .into_iter() .map(|a| (a.clone(), self.account_meta(a).ok().unwrap_or_default())) .collect(); @@ -239,21 +239,21 @@ impl AccountProvider { /// Returns each account along with name and meta. pub fn account_meta(&self, account: Address) -> Result { Ok(AccountMeta { - name: try!(self.sstore.name(&account)), - meta: try!(self.sstore.meta(&account)), + name: self.sstore.name(&account)?, + meta: self.sstore.meta(&account)?, uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid }) } /// Returns each account along with name and meta. pub fn set_account_name(&self, account: Address, name: String) -> Result<(), Error> { - try!(self.sstore.set_name(&account, name)); + self.sstore.set_name(&account, name)?; Ok(()) } /// Returns each account along with name and meta. pub fn set_account_meta(&self, account: Address, meta: String) -> Result<(), Error> { - try!(self.sstore.set_meta(&account, meta)); + self.sstore.set_meta(&account, meta)?; Ok(()) } @@ -265,7 +265,7 @@ impl AccountProvider { /// Permanently removes an account. pub fn kill_account(&self, account: &Address, password: &str) -> Result<(), Error> { - try!(self.sstore.remove_account(account, &password)); + self.sstore.remove_account(account, &password)?; Ok(()) } @@ -278,7 +278,7 @@ impl AccountProvider { fn unlock_account(&self, account: Address, password: String, unlock: Unlock) -> Result<(), Error> { // verify password by signing dump message // result may be discarded - let _ = try!(self.sstore.sign(&account, &password, &Default::default())); + let _ = self.sstore.sign(&account, &password, &Default::default())?; // check if account is already unlocked pernamently, if it is, do nothing let mut unlocked = self.unlocked.write(); @@ -299,7 +299,7 @@ impl AccountProvider { fn password(&self, account: &Address) -> Result { let mut unlocked = self.unlocked.write(); - let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone(); + let data = unlocked.get(account).ok_or(Error::NotUnlocked)?.clone(); if let Unlock::Temp = data.unlock { unlocked.remove(account).expect("data exists: so key must exist: qed"); } @@ -335,25 +335,25 @@ impl AccountProvider { /// Signs the message. If password is not provided the account must be unlocked. pub fn sign(&self, account: Address, password: Option, message: Message) -> Result { - let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); - Ok(try!(self.sstore.sign(&account, &password, &message))) + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok(self.sstore.sign(&account, &password, &message)?) } /// Signs given message with supplied token. Returns a token to use in next signing within this session. pub fn sign_with_token(&self, account: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), Error> { - let is_std_password = try!(self.sstore.test_password(&account, &token)); + let is_std_password = self.sstore.test_password(&account, &token)?; let new_token = random_string(16); let signature = if is_std_password { // Insert to transient store - try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)?; // sign - try!(self.sstore.sign(&account, &token, &message)) + self.sstore.sign(&account, &token, &message)? } else { // check transient store - try!(self.transient_sstore.change_password(&account, &token, &new_token)); + self.transient_sstore.change_password(&account, &token, &new_token)?; // and sign - try!(self.transient_sstore.sign(&account, &new_token, &message)) + self.transient_sstore.sign(&account, &new_token, &message)? }; Ok((signature, new_token)) @@ -363,19 +363,19 @@ impl AccountProvider { pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8]) -> Result<(Vec, AccountToken), Error> { - let is_std_password = try!(self.sstore.test_password(&account, &token)); + let is_std_password = self.sstore.test_password(&account, &token)?; let new_token = random_string(16); let message = if is_std_password { // Insert to transient store - try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)?; // decrypt - try!(self.sstore.decrypt(&account, &token, shared_mac, message)) + self.sstore.decrypt(&account, &token, shared_mac, message)? } else { // check transient store - try!(self.transient_sstore.change_password(&account, &token, &new_token)); + self.transient_sstore.change_password(&account, &token, &new_token)?; // and decrypt - try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message)) + self.transient_sstore.decrypt(&account, &token, shared_mac, message)? }; Ok((message, new_token)) @@ -383,8 +383,8 @@ impl AccountProvider { /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); - Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message))) + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?) } /// Returns the underlying `SecretStore` reference if one exists. diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 743167a75..1eec9110e 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -67,7 +67,7 @@ impl Block { impl Decodable for Block { fn decode(decoder: &D) -> Result where D: Decoder { - if decoder.as_raw().len() != try!(decoder.as_rlp().payload_info()).total() { + if decoder.as_raw().len() != decoder.as_rlp().payload_info()?.total() { return Err(DecoderError::RlpIsTooBig); } let d = decoder.as_rlp(); @@ -75,9 +75,9 @@ impl Decodable for Block { return Err(DecoderError::RlpIncorrectListLen); } Ok(Block { - header: try!(d.val_at(0)), - transactions: try!(d.val_at(1)), - uncles: try!(d.val_at(2)), + header: d.val_at(0)?, + transactions: d.val_at(1)?, + uncles: d.val_at(2)?, }) } } @@ -252,7 +252,7 @@ impl<'x> OpenBlock<'x> { gas_range_target: (U256, U256), extra_data: Bytes, ) -> Result { - let state = try!(State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(), factories)); + let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(), factories)?; let mut r = OpenBlock { block: ExecutedBlock::new(state, tracing), engine: engine, @@ -521,12 +521,12 @@ pub fn enact( ) -> Result { { if ::log::max_log_level() >= ::log::LogLevel::Trace { - let s = try!(State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(), factories.clone())); + let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(), factories.clone())?; trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n", header.number(), s.root(), header.author(), s.balance(&header.author())); } } - let mut b = try!(OpenBlock::new(engine, factories, tracing, db, parent, last_hashes, Address::new(), (3141562.into(), 31415620.into()), vec![])); + let mut b = OpenBlock::new(engine, factories, tracing, db, parent, last_hashes, Address::new(), (3141562.into(), 31415620.into()), vec![])?; b.set_difficulty(*header.difficulty()); b.set_gas_limit(*header.gas_limit()); b.set_timestamp(header.timestamp()); @@ -536,9 +536,9 @@ pub fn enact( b.set_transactions_root(header.transactions_root().clone()); b.set_receipts_root(header.receipts_root().clone()); - try!(push_transactions(&mut b, transactions)); + push_transactions(&mut b, transactions)?; for u in uncles { - try!(b.push_uncle(u.clone())); + b.push_uncle(u.clone())?; } Ok(b.close_and_lock()) } @@ -547,7 +547,7 @@ pub fn enact( #[cfg(not(feature = "slow-blocks"))] fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) -> Result<(), Error> { for t in transactions { - try!(block.push_transaction(t.clone(), None)); + block.push_transaction(t.clone(), None)?; } Ok(()) } @@ -560,7 +560,7 @@ fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) for t in transactions { let hash = t.hash(); let start = time::Instant::now(); - try!(block.push_transaction(t.clone(), None)); + block.push_transaction(t.clone(), None)?; let took = start.elapsed(); if took > time::Duration::from_millis(slow_tx) { warn!("Heavy transaction in block {:?}: {:?}", block.header().number(), hash); @@ -595,9 +595,8 @@ mod tests { use factory::Factories; use state_db::StateDB; use views::BlockView; - use util::{Address, TrieFactory}; + use util::Address; use util::hash::FixedHash; - use util::trie::TrieSpec; use std::sync::Arc; /// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header @@ -628,7 +627,7 @@ mod tests { factories: Factories, ) -> Result { let header = BlockView::new(block_bytes).header_view(); - Ok(try!(try!(enact_bytes(block_bytes, engine, tracing, db, parent, last_hashes, factories)).seal(engine, header.seal()))) + Ok(enact_bytes(block_bytes, engine, tracing, db, parent, last_hashes, factories)?.seal(engine, header.seal())?) } #[test] @@ -637,8 +636,7 @@ mod tests { let spec = Spec::new_test(); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); @@ -653,8 +651,7 @@ mod tests { let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap() .close_and_lock().seal(engine, vec![]).unwrap(); @@ -662,8 +659,7 @@ mod tests { let orig_db = b.drain(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap(); assert_eq!(e.rlp_bytes(), orig_bytes); @@ -681,8 +677,7 @@ mod tests { let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let mut uncle1_header = Header::new(); @@ -697,8 +692,7 @@ mod tests { let orig_db = b.drain(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap(); let bytes = e.rlp_bytes(); diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 47f3a38c4..62e9af250 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -35,6 +35,7 @@ use blockchain::{CacheSize, ImportRoute, Config}; use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; use engines::Engine; +use encoded; const LOG_BLOOMS_LEVELS: usize = 3; const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; @@ -64,7 +65,7 @@ pub trait BlockProvider { self.best_ancient_block().map(|h| self.block_number(&h).expect("Ancient block is always set to an existing block or `None`. Existing block always has a number; qed")) } /// Get raw block data - fn block(&self, hash: &H256) -> Option; + fn block(&self, hash: &H256) -> Option; /// Get the familial details concerning a block. fn block_details(&self, hash: &H256) -> Option; @@ -80,25 +81,25 @@ pub trait BlockProvider { /// Get the partial-header of a block. fn block_header(&self, hash: &H256) -> Option
{ - self.block_header_data(hash).map(|header| decode(&header)) + self.block_header_data(hash).map(|header| header.decode()) } /// Get the header RLP of a block. - fn block_header_data(&self, hash: &H256) -> Option; + fn block_header_data(&self, hash: &H256) -> Option; /// Get the block body (uncles and transactions). - fn block_body(&self, hash: &H256) -> Option; + fn block_body(&self, hash: &H256) -> Option; /// Get a list of uncles for a given block. /// Returns None if block does not exist. fn uncles(&self, hash: &H256) -> Option> { - self.block_body(hash).map(|bytes| BodyView::new(&bytes).uncles()) + self.block_body(hash).map(|body| body.uncles()) } /// Get a list of uncle hashes for a given block. /// Returns None if block does not exist. fn uncle_hashes(&self, hash: &H256) -> Option> { - self.block_body(hash).map(|bytes| BodyView::new(&bytes).uncle_hashes()) + self.block_body(hash).map(|body| body.uncle_hashes()) } /// Get the number of given block's hash. @@ -109,8 +110,8 @@ pub trait BlockProvider { /// Get transaction with given transaction hash. fn transaction(&self, address: &TransactionAddress) -> Option { self.block_body(&address.block_hash) - .and_then(|bytes| self.block_number(&address.block_hash) - .and_then(|n| BodyView::new(&bytes).localized_transaction_at(&address.block_hash, n, address.index))) + .and_then(|body| self.block_number(&address.block_hash) + .and_then(|n| body.view().localized_transaction_at(&address.block_hash, n, address.index))) } /// Get transaction receipt. @@ -122,8 +123,8 @@ pub trait BlockProvider { /// Returns None if block does not exist. fn transactions(&self, hash: &H256) -> Option> { self.block_body(hash) - .and_then(|bytes| self.block_number(hash) - .map(|n| BodyView::new(&bytes).localized_transactions(hash, n))) + .and_then(|body| self.block_number(hash) + .map(|n| body.view().localized_transactions(hash, n))) } /// Returns reference to genesis hash. @@ -224,27 +225,27 @@ impl BlockProvider for BlockChain { } /// Get raw block data - fn block(&self, hash: &H256) -> Option { + fn block(&self, hash: &H256) -> Option { match (self.block_header_data(hash), self.block_body(hash)) { (Some(header), Some(body)) => { let mut block = RlpStream::new_list(3); - let body_rlp = Rlp::new(&body); - block.append_raw(&header, 1); + let body_rlp = body.rlp(); + block.append_raw(header.rlp().as_raw(), 1); block.append_raw(body_rlp.at(0).as_raw(), 1); block.append_raw(body_rlp.at(1).as_raw(), 1); - Some(block.out()) + Some(encoded::Block::new(block.out())) }, _ => None, } } /// Get block header data - fn block_header_data(&self, hash: &H256) -> Option { + fn block_header_data(&self, hash: &H256) -> Option { // Check cache first { let read = self.block_headers.read(); if let Some(v) = read.get(hash) { - return Some(v.clone()); + return Some(encoded::Header::new(v.clone())); } } @@ -252,7 +253,9 @@ impl BlockProvider for BlockChain { { let best_block = self.best_block.read(); if &best_block.hash == hash { - return Some(Rlp::new(&best_block.block).at(0).as_raw().to_vec()); + return Some(encoded::Header::new( + Rlp::new(&best_block.block).at(0).as_raw().to_vec() + )) } } @@ -265,7 +268,7 @@ impl BlockProvider for BlockChain { let bytes: Bytes = UntrustedRlp::new(&b).decompress(RlpType::Blocks).to_vec(); let mut write = self.block_headers.write(); write.insert(hash.clone(), bytes.clone()); - Some(bytes) + Some(encoded::Header::new(bytes)) }, None => None }; @@ -275,12 +278,12 @@ impl BlockProvider for BlockChain { } /// Get block body data - fn block_body(&self, hash: &H256) -> Option { + fn block_body(&self, hash: &H256) -> Option { // Check cache first { let read = self.block_bodies.read(); if let Some(v) = read.get(hash) { - return Some(v.clone()); + return Some(encoded::Body::new(v.clone())); } } @@ -288,7 +291,7 @@ impl BlockProvider for BlockChain { { let best_block = self.best_block.read(); if &best_block.hash == hash { - return Some(Self::block_to_body(&best_block.block)); + return Some(encoded::Body::new(Self::block_to_body(&best_block.block))); } } @@ -301,7 +304,7 @@ impl BlockProvider for BlockChain { let bytes: Bytes = UntrustedRlp::new(&b).decompress(RlpType::Blocks).to_vec(); let mut write = self.block_bodies.write(); write.insert(hash.clone(), bytes.clone()); - Some(bytes) + Some(encoded::Body::new(bytes)) }, None => None }; @@ -358,7 +361,7 @@ impl BlockProvider for BlockChain { let mut logs = blocks.into_iter() .filter_map(|number| self.block_hash(number).map(|hash| (number, hash))) .filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts))) - .filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes()))) + .filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, b.transaction_hashes()))) .flat_map(|(number, hash, mut receipts, mut hashes)| { if receipts.len() != hashes.len() { warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len()); @@ -375,7 +378,8 @@ impl BlockProvider for BlockChain { .enumerate() .flat_map(move |(index, (mut logs, tx_hash))| { let current_log_index = log_index; - log_index -= logs.len(); + let no_of_logs = logs.len(); + log_index -= no_of_logs; logs.reverse(); logs.into_iter() @@ -387,6 +391,7 @@ impl BlockProvider for BlockChain { transaction_hash: tx_hash, // iterating in reverse order transaction_index: receipts_len - index - 1, + transaction_log_index: no_of_logs - i - 1, log_index: current_log_index - i - 1, }) }) @@ -484,7 +489,7 @@ impl BlockChain { // Fetch best block details let best_block_number = bc.block_number(&best_block_hash).unwrap(); let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty; - let best_block_rlp = bc.block(&best_block_hash).unwrap(); + let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner(); let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec()); let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h)); @@ -578,7 +583,7 @@ impl BlockChain { batch.put(db::COL_EXTRA, b"best", &hash); let best_block_total_difficulty = self.block_details(&hash).unwrap().total_difficulty; - let best_block_rlp = self.block(&hash).unwrap(); + let best_block_rlp = self.block(&hash).unwrap().into_inner(); let mut best_block = self.best_block.write(); *best_block = BestBlock { @@ -862,7 +867,7 @@ impl BlockChain { let number = header.number(); let parent_hash = header.parent_hash(); let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash)); - let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), HeaderView::new(&self.best_block_header()), &parent_details, header); + let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), self.best_block_header().view(), &parent_details, header); BlockInfo { hash: hash, @@ -1108,8 +1113,8 @@ impl BlockChain { BlockLocation::BranchBecomingCanonChain(ref data) => { let addresses = data.enacted.iter() .flat_map(|hash| { - let bytes = self.block_body(hash).expect("Enacted block must be in database."); - let hashes = BodyView::new(&bytes).transaction_hashes(); + let body = self.block_body(hash).expect("Enacted block must be in database."); + let hashes = body.transaction_hashes(); hashes.into_iter() .enumerate() .map(|(i, tx_hash)| (tx_hash, Some(TransactionAddress { @@ -1129,8 +1134,8 @@ impl BlockChain { }); let retracted = data.retracted.iter().flat_map(|hash| { - let bytes = self.block_body(hash).expect("Retracted block must be in database."); - let hashes = BodyView::new(&bytes).transaction_hashes(); + let body = self.block_body(hash).expect("Retracted block must be in database."); + let hashes = body.transaction_hashes(); hashes.into_iter().map(|hash| (hash, None)).collect::>>() }); @@ -1179,7 +1184,7 @@ impl BlockChain { let mut blooms: Vec = data.enacted.iter() .map(|hash| self.block_header_data(hash).unwrap()) - .map(|bytes| HeaderView::new(&bytes).log_bloom()) + .map(|h| h.log_bloom()) .map(Bloom::from) .map(Into::into) .collect(); @@ -1212,9 +1217,10 @@ impl BlockChain { } /// Get best block header - pub fn best_block_header(&self) -> Bytes { + pub fn best_block_header(&self) -> encoded::Header { let block = self.best_block.read(); - BlockView::new(&block.block).header_view().rlp().as_raw().to_vec() + let raw = BlockView::new(&block.block).header_view().rlp().as_raw().to_vec(); + encoded::Header::new(raw) } /// Get current cache size. @@ -1329,7 +1335,7 @@ mod tests { } fn new_chain(genesis: &[u8], db: Arc) -> BlockChain { - BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) + BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) } #[test] @@ -1932,6 +1938,7 @@ mod tests { block_number: block1.header().number(), transaction_hash: tx_hash1.clone(), transaction_index: 0, + transaction_log_index: 0, log_index: 0, }, LocalizedLogEntry { @@ -1940,6 +1947,7 @@ mod tests { block_number: block1.header().number(), transaction_hash: tx_hash1.clone(), transaction_index: 0, + transaction_log_index: 1, log_index: 1, }, LocalizedLogEntry { @@ -1948,6 +1956,7 @@ mod tests { block_number: block1.header().number(), transaction_hash: tx_hash2.clone(), transaction_index: 1, + transaction_log_index: 0, log_index: 2, }, LocalizedLogEntry { @@ -1956,6 +1965,7 @@ mod tests { block_number: block2.header().number(), transaction_hash: tx_hash3.clone(), transaction_index: 0, + transaction_log_index: 0, log_index: 0, } ]); @@ -1966,6 +1976,7 @@ mod tests { block_number: block2.header().number(), transaction_hash: tx_hash3.clone(), transaction_index: 0, + transaction_log_index: 0, log_index: 0, } ]); diff --git a/ethcore/src/blockchain/extras.rs b/ethcore/src/blockchain/extras.rs index bf32258f9..98cc137ed 100644 --- a/ethcore/src/blockchain/extras.rs +++ b/ethcore/src/blockchain/extras.rs @@ -157,10 +157,10 @@ impl Decodable for BlockDetails { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let details = BlockDetails { - number: try!(d.val_at(0)), - total_difficulty: try!(d.val_at(1)), - parent: try!(d.val_at(2)), - children: try!(d.val_at(3)), + number: d.val_at(0)?, + total_difficulty: d.val_at(1)?, + parent: d.val_at(2)?, + children: d.val_at(3)?, }; Ok(details) } @@ -193,8 +193,8 @@ impl Decodable for TransactionAddress { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let tx_address = TransactionAddress { - block_hash: try!(d.val_at(0)), - index: try!(d.val_at(1)), + block_hash: d.val_at(0)?, + index: d.val_at(1)?, }; Ok(tx_address) @@ -226,7 +226,7 @@ impl BlockReceipts { impl Decodable for BlockReceipts { fn decode(decoder: &D) -> Result where D: Decoder { Ok(BlockReceipts { - receipts: try!(Decodable::decode(decoder)) + receipts: Decodable::decode(decoder)? }) } } diff --git a/ethcore/src/blooms/bloom_group.rs b/ethcore/src/blooms/bloom_group.rs index 2dbc4e455..435819c94 100644 --- a/ethcore/src/blooms/bloom_group.rs +++ b/ethcore/src/blooms/bloom_group.rs @@ -53,7 +53,7 @@ impl Into for BloomGroup { impl Decodable for BloomGroup { fn decode(decoder: &D) -> Result where D: Decoder { - let blooms = try!(Decodable::decode(decoder)); + let blooms = Decodable::decode(decoder)?; let group = BloomGroup { blooms: blooms }; diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 25f000c89..f57b1248a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -32,7 +32,7 @@ use util::kvdb::*; // other use io::*; -use views::{HeaderView, BodyView, BlockView}; +use views::BlockView; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; use header::BlockNumber; use state::{State, CleanupMode}; @@ -59,7 +59,7 @@ use client::{ use client::Error as ClientError; use env_info::EnvInfo; use executive::{Executive, Executed, TransactOptions, contract_address}; -use receipt::LocalizedReceipt; +use receipt::{Receipt, LocalizedReceipt}; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; use trace; use trace::FlatTransactionTraces; @@ -67,10 +67,11 @@ use evm::{Factory as EvmFactory, Schedule}; use miner::{Miner, MinerService}; use snapshot::{self, io as snapshot_io}; use factory::Factories; -use rlp::{decode, View, UntrustedRlp}; +use rlp::{View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; use client::registry::Registry; +use encoded; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -167,26 +168,33 @@ impl Client { ) -> Result, ClientError> { let path = path.to_path_buf(); - let gb = spec.genesis_block(); - - let db = Arc::new(try!(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database))); - let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); - let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); - + let db = Arc::new(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database)?); let trie_spec = match config.fat_db { true => TrieSpec::Fat, false => TrieSpec::Secure, }; let trie_factory = TrieFactory::new(trie_spec); + let factories = Factories { + vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size), + trie: trie_factory, + accountdb: Default::default(), + }; + let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); let mut state_db = StateDB::new(journal_db, config.state_cache_size); - if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db, &trie_factory)) { + if state_db.journal_db().is_empty() { + // Sets the correct state root. + state_db = spec.ensure_db_good(state_db, &factories)?; let mut batch = DBTransaction::new(&db); - try!(state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash())); - try!(db.write(batch).map_err(ClientError::Database)); + state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash())?; + db.write(batch).map_err(ClientError::Database)?; } + let gb = spec.genesis_block(); + let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); + let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); + trace!("Cleanup journal: DB Earliest = {:?}, Latest = {:?}", state_db.journal_db().earliest_era(), state_db.journal_db().latest_era()); let history = if config.history < MIN_HISTORY_SIZE { @@ -203,8 +211,8 @@ impl Client { for era in earliest..(latest - history + 1) { trace!("Removing era {}", era); let mut batch = DBTransaction::new(&db); - try!(state_db.mark_canonical(&mut batch, era, &chain.block_hash(era).expect("Old block not found in the database"))); - try!(db.write(batch).map_err(ClientError::Database)); + state_db.mark_canonical(&mut batch, era, &chain.block_hash(era).expect("Old block not found in the database"))?; + db.write(batch).map_err(ClientError::Database)?; } } } @@ -221,12 +229,6 @@ impl Client { let awake = match config.mode { Mode::Dark(..) | Mode::Off => false, _ => true }; - let factories = Factories { - vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size), - trie: trie_factory, - accountdb: Default::default(), - }; - let client = Arc::new(Client { enabled: AtomicBool::new(true), sleep_state: Mutex::new(SleepState::new(awake)), @@ -251,7 +253,7 @@ impl Client { last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, - rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), + rng: Mutex::new(OsRng::new().map_err(::util::UtilError::StdIo)?), on_mode_change: Mutex::new(None), registrar: Mutex::new(None), }); @@ -302,17 +304,16 @@ impl Client { /// The env info as of the best block. fn latest_env_info(&self) -> EnvInfo { - let header_data = self.best_block_header(); - let view = HeaderView::new(&header_data); + let header = self.best_block_header(); EnvInfo { - number: view.number(), - author: view.author(), - timestamp: view.timestamp(), - difficulty: view.difficulty(), - last_hashes: self.build_last_hashes(view.hash()), + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), + last_hashes: self.build_last_hashes(header.hash()), gas_used: U256::default(), - gas_limit: view.gas_limit(), + gas_limit: header.gas_limit(), } } @@ -369,9 +370,9 @@ impl Client { let db = self.state_db.lock().boxed_clone_canon(header.parent_hash()); let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); - let locked_block = try!(enact_result.map_err(|e| { + let locked_block = enact_result.map_err(|e| { warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - })); + })?; // Final Verification if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { @@ -510,14 +511,14 @@ impl Client { let chain = self.chain.read(); // verify block. - try!(::snapshot::verify_old_block( + ::snapshot::verify_old_block( &mut *rng, &header, &*self.engine, &*chain, Some(&block_bytes), false, - )); + )?; // Commit results let receipts = ::rlp::decode(&receipts_bytes); @@ -618,7 +619,8 @@ impl Client { /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockId::Latest. - /// Otherwise, this can fail (but may not) if the DB prunes state. + /// Otherwise, this can fail (but may not) if the DB prunes state or the block + /// is unknown. pub fn state_at(&self, id: BlockId) -> Option { // fast path for latest state. match id.clone() { @@ -640,8 +642,7 @@ impl Client { return None; } - let root = HeaderView::new(&header).state_root(); - + let root = header.state_root(); State::from_existing(db, root, self.engine.account_start_nonce(), self.factories.clone()).ok() }) } @@ -664,7 +665,6 @@ impl Client { /// Get a copy of the best block's state. pub fn state(&self) -> State { let header = self.best_block_header(); - let header = HeaderView::new(&header); State::from_existing( self.state_db.lock().boxed_clone_canon(&header.hash()), header.state_root(), @@ -737,7 +737,7 @@ impl Client { pub fn take_snapshot(&self, writer: W, at: BlockId, p: &snapshot::Progress) -> Result<(), EthcoreError> { let db = self.state_db.lock().journal_db().boxed_clone(); let best_block_number = self.chain_info().best_block_number; - let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); + let block_number = self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))?; if best_block_number > self.history + block_number && db.is_pruned() { return Err(snapshot::Error::OldBlockPrunedDB.into()); @@ -763,7 +763,7 @@ impl Client { }, }; - try!(snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p)); + snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p)?; Ok(()) } @@ -827,7 +827,7 @@ impl snapshot::DatabaseRestore for Client { let mut tracedb = self.tracedb.write(); self.miner.clear(); let db = self.db.write(); - try!(db.restore(new_db)); + db.restore(new_db)?; let cache_size = state_db.cache_size(); *state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size); @@ -837,29 +837,27 @@ impl snapshot::DatabaseRestore for Client { } } - impl BlockChainClient for Client { fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { - let header = try!(self.block_header(block).ok_or(CallError::StatePruned)); - let view = HeaderView::new(&header); - let last_hashes = self.build_last_hashes(view.parent_hash()); + let header = self.block_header(block).ok_or(CallError::StatePruned)?; + let last_hashes = self.build_last_hashes(header.parent_hash()); let env_info = EnvInfo { - number: view.number(), - author: view.author(), - timestamp: view.timestamp(), - difficulty: view.difficulty(), + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), last_hashes: last_hashes, gas_used: U256::zero(), gas_limit: U256::max_value(), }; // that's just a copy of the state. - let mut state = try!(self.state_at(block).ok_or(CallError::StatePruned)); + let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - let sender = try!(t.sender().map_err(|e| { + let sender = t.sender().map_err(|e| { let message = format!("Transaction malformed: {:?}", e); ExecutionError::TransactionMalformed(message) - })); + })?; let balance = state.balance(&sender); let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { @@ -867,7 +865,7 @@ impl BlockChainClient for Client { state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let mut ret = try!(Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)); + let mut ret = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)?; // TODO gav move this into Executive. ret.state_diff = original_state.map(|original| state.diff_from(original)); @@ -876,27 +874,26 @@ impl BlockChainClient for Client { } fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { - let address = try!(self.transaction_address(id).ok_or(CallError::TransactionNotFound)); - let header_data = try!(self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let body_data = try!(self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let mut state = try!(self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let txs = BodyView::new(&body_data).transactions(); + let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?; + let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; + let body = self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; + let mut state = self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; + let txs = body.transactions(); if address.index >= txs.len() { return Err(CallError::TransactionNotFound); } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let view = HeaderView::new(&header_data); - let last_hashes = self.build_last_hashes(view.hash()); + let last_hashes = self.build_last_hashes(header.hash()); let mut env_info = EnvInfo { - number: view.number(), - author: view.author(), - timestamp: view.timestamp(), - difficulty: view.difficulty(), + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), last_hashes: last_hashes, gas_used: U256::default(), - gas_limit: view.gas_limit(), + gas_limit: header.gas_limit(), }; for t in txs.iter().take(address.index) { match Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, Default::default()) { @@ -907,7 +904,7 @@ impl BlockChainClient for Client { let t = &txs[address.index]; let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - let mut ret = try!(Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)); + let mut ret = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)?; ret.state_diff = original_state.map(|original| state.diff_from(original)); Ok(ret) @@ -957,11 +954,11 @@ impl BlockChainClient for Client { } } - fn best_block_header(&self) -> Bytes { + fn best_block_header(&self) -> encoded::Header { self.chain.read().best_block_header() } - fn block_header(&self, id: BlockId) -> Option { + fn block_header(&self, id: BlockId) -> Option<::encoded::Header> { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash)) } @@ -975,15 +972,15 @@ impl BlockChainClient for Client { } } - fn block_body(&self, id: BlockId) -> Option { + fn block_body(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash)) } - fn block(&self, id: BlockId) -> Option { + fn block(&self, id: BlockId) -> Option { if let BlockId::Pending = id { if let Some(block) = self.miner.pending_block() { - return Some(block.rlp_bytes(Seal::Without)); + return Some(encoded::Block::new(block.rlp_bytes(Seal::Without))); } } let chain = self.chain.read(); @@ -1126,62 +1123,33 @@ impl BlockChainClient for Client { self.transaction_address(id).map(|addr| addr.block_hash) } - fn uncle(&self, id: UncleId) -> Option { + fn uncle(&self, id: UncleId) -> Option { let index = id.position; - self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) + self.block_body(id.block).and_then(|body| body.view().uncle_rlp_at(index)) + .map(encoded::Header::new) } fn transaction_receipt(&self, id: TransactionId) -> Option { let chain = self.chain.read(); self.transaction_address(id) .and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| { - let t = chain.block_body(&address.block_hash) - .and_then(|block| { - BodyView::new(&block).localized_transaction_at(&address.block_hash, block_number, address.index) - }); + let transaction = chain.block_body(&address.block_hash) + .and_then(|body| body.view().localized_transaction_at(&address.block_hash, block_number, address.index)); - let tx_and_sender = t.and_then(|tx| tx.sender().ok().map(|sender| (tx, sender))); - - match (tx_and_sender, chain.transaction_receipt(&address)) { - (Some((tx, sender)), Some(receipt)) => { - let block_hash = tx.block_hash.clone(); - let block_number = tx.block_number.clone(); - let transaction_hash = tx.hash(); - let transaction_index = tx.transaction_index; - let prior_gas_used = match tx.transaction_index { - 0 => U256::zero(), - i => { - let prior_address = TransactionAddress { block_hash: address.block_hash, index: i - 1 }; - let prior_receipt = chain.transaction_receipt(&prior_address).expect("Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed"); - prior_receipt.gas_used - } - }; - Some(LocalizedReceipt { - transaction_hash: tx.hash(), - transaction_index: tx.transaction_index, - block_hash: tx.block_hash, - block_number: tx.block_number, - cumulative_gas_used: receipt.gas_used, - gas_used: receipt.gas_used - prior_gas_used, - contract_address: match tx.action { - Action::Call(_) => None, - Action::Create => Some(contract_address(&sender, &tx.nonce)) - }, - logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry { - entry: log, - block_hash: block_hash.clone(), - block_number: block_number, - transaction_hash: transaction_hash.clone(), - transaction_index: transaction_index, - log_index: i - }).collect(), - log_bloom: receipt.log_bloom, - state_root: receipt.state_root, + let previous_receipts = (0..address.index + 1) + .map(|index| { + let mut address = address.clone(); + address.index = index; + chain.transaction_receipt(&address) }) - }, - _ => None - } - })) + .collect(); + match (transaction, previous_receipts) { + (Some(transaction), Some(previous_receipts)) => { + Some(transaction_receipt(transaction, previous_receipts)) + }, + _ => None, + } + })) } fn tree_route(&self, from: &H256, to: &H256) -> Option { @@ -1205,7 +1173,7 @@ impl BlockChainClient for Client { } fn import_block(&self, bytes: Bytes) -> Result { - use verification::queue::kind::HasHash; + use verification::queue::kind::BlockLike; use verification::queue::kind::blocks::Unverified; // create unverified block here so the `sha3` calculation can be cached. @@ -1219,7 +1187,7 @@ impl BlockChainClient for Client { return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash()))); } } - Ok(try!(self.block_queue.import(unverified))) + Ok(self.block_queue.import(unverified)?) } fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { @@ -1245,7 +1213,9 @@ impl BlockChainClient for Client { } fn chain_info(&self) -> BlockChainInfo { - self.chain.read().chain_info() + let mut chain_info = self.chain.read().chain_info(); + chain_info.pending_total_difficulty = chain_info.total_difficulty + self.block_queue.total_difficulty(); + chain_info } fn additional_params(&self) -> BTreeMap { @@ -1356,19 +1326,19 @@ impl BlockChainClient for Client { fn block_extra_info(&self, id: BlockId) -> Option> { self.block_header(id) - .map(|block| decode(&block)) - .map(|header| self.engine.extra_info(&header)) + .map(|header| self.engine.extra_info(&header.decode())) } fn uncle_extra_info(&self, id: UncleId) -> Option> { self.uncle(id) - .map(|header| self.engine.extra_info(&decode(&header))) + .map(|header| self.engine.extra_info(&header.decode())) } fn pruning_info(&self) -> PruningInfo { PruningInfo { earliest_chain: self.chain.read().first_block_number().unwrap_or(1), earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0), + state_history_size: Some(self.history), } } @@ -1534,6 +1504,49 @@ impl Drop for Client { } } +/// Returns `LocalizedReceipt` given `LocalizedTransaction` +/// and a vector of receipts from given block up to transaction index. +fn transaction_receipt(tx: LocalizedTransaction, mut receipts: Vec) -> LocalizedReceipt { + assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided."); + + let sender = tx.sender() + .expect("LocalizedTransaction is part of the blockchain; We have only valid transactions in chain; qed"); + let receipt = receipts.pop().expect("Current receipt is provided; qed"); + let prior_gas_used = match tx.transaction_index { + 0 => 0.into(), + i => receipts.get(i - 1).expect("All previous receipts are provided; qed").gas_used, + }; + let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::(); + let transaction_hash = tx.hash(); + let block_hash = tx.block_hash; + let block_number = tx.block_number; + let transaction_index = tx.transaction_index; + + LocalizedReceipt { + transaction_hash: transaction_hash, + transaction_index: transaction_index, + block_hash: block_hash, + block_number:block_number, + cumulative_gas_used: receipt.gas_used, + gas_used: receipt.gas_used - prior_gas_used, + contract_address: match tx.action { + Action::Call(_) => None, + Action::Create => Some(contract_address(&sender, &tx.nonce)) + }, + logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry { + entry: log, + block_hash: block_hash, + block_number: block_number, + transaction_hash: transaction_hash, + transaction_index: transaction_index, + transaction_log_index: i, + log_index: no_of_logs + i, + }).collect(), + log_bloom: receipt.log_bloom, + state_root: receipt.state_root, + } +} + #[cfg(test)] mod tests { @@ -1569,4 +1582,91 @@ mod tests { assert!(client.tree_route(&genesis, &new_hash).is_none()); } + + #[test] + fn should_return_correct_log_index() { + use super::transaction_receipt; + use ethkey::KeyPair; + use log_entry::{LogEntry, LocalizedLogEntry}; + use receipt::{Receipt, LocalizedReceipt}; + use transaction::{Transaction, LocalizedTransaction, Action}; + use util::Hashable; + + // given + let key = KeyPair::from_secret("test".sha3()).unwrap(); + let secret = key.secret(); + + let block_number = 1; + let block_hash = 5.into(); + let state_root = 99.into(); + let gas_used = 10.into(); + let raw_tx = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(10.into()), + value: 0.into(), + data: vec![], + }; + let tx1 = raw_tx.clone().sign(secret, None); + let transaction = LocalizedTransaction { + signed: tx1.clone(), + block_number: block_number, + block_hash: block_hash, + transaction_index: 1, + }; + let logs = vec![LogEntry { + address: 5.into(), + topics: vec![], + data: vec![], + }, LogEntry { + address: 15.into(), + topics: vec![], + data: vec![], + }]; + let receipts = vec![Receipt { + state_root: state_root, + gas_used: 5.into(), + log_bloom: Default::default(), + logs: vec![logs[0].clone()], + }, Receipt { + state_root: state_root, + gas_used: gas_used, + log_bloom: Default::default(), + logs: logs.clone(), + }]; + + // when + let receipt = transaction_receipt(transaction, receipts); + + // then + assert_eq!(receipt, LocalizedReceipt { + transaction_hash: tx1.hash(), + transaction_index: 1, + block_hash: block_hash, + block_number: block_number, + cumulative_gas_used: gas_used, + gas_used: gas_used - 5.into(), + contract_address: None, + logs: vec![LocalizedLogEntry { + entry: logs[0].clone(), + block_hash: block_hash, + block_number: block_number, + transaction_hash: tx1.hash(), + transaction_index: 1, + transaction_log_index: 0, + log_index: 1, + }, LocalizedLogEntry { + entry: logs[1].clone(), + block_hash: block_hash, + block_number: block_number, + transaction_hash: tx1.hash(), + transaction_index: 1, + transaction_log_index: 1, + log_index: 2, + }], + log_bloom: Default::default(), + state_root: state_root, + }); + } } diff --git a/ethcore/src/client/registry.rs b/ethcore/src/client/registry.rs index 7693feaeb..9e60a1251 100644 --- a/ethcore/src/client/registry.rs +++ b/ethcore/src/client/registry.rs @@ -43,7 +43,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` @@ -55,7 +55,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` @@ -67,7 +67,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` @@ -79,7 +79,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` @@ -91,7 +91,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"}` @@ -115,7 +115,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` @@ -127,7 +127,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"}` @@ -139,7 +139,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"}` @@ -163,7 +163,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` @@ -175,7 +175,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` @@ -187,7 +187,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_fixed_bytes().ok_or("Invalid type returned")); util::H256::from_slice(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` @@ -199,7 +199,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_uint().ok_or("Invalid type returned")); util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` @@ -211,7 +211,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_address().ok_or("Invalid type returned")); util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` @@ -223,7 +223,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_string().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_string().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"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"}` @@ -235,7 +235,7 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"}` @@ -259,6 +259,6 @@ impl Registry { ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = try!(r.to_bool().ok_or("Invalid type returned")); r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } } \ No newline at end of file diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a384f1227..da7425a3d 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -39,7 +39,6 @@ use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; use types::mode::Mode; use types::pruning_info::PruningInfo; -use views::BlockView; use verification::queue::QueueInfo; use block::{OpenBlock, SealedBlock}; @@ -47,6 +46,7 @@ use executive::Executed; use error::CallError; use trace::LocalizedTrace; use state_db::StateDB; +use encoded; /// Test client. pub struct TestBlockChainClient { @@ -92,6 +92,8 @@ pub struct TestBlockChainClient { pub first_block: RwLock>, /// Traces to return pub traces: RwLock>>, + /// Pruning history size to report. + pub history: RwLock>, } /// Used for generating test client blocks. @@ -154,6 +156,7 @@ impl TestBlockChainClient { ancient_block: RwLock::new(None), first_block: RwLock::new(None), traces: RwLock::new(None), + history: RwLock::new(None), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().clone(); @@ -260,7 +263,7 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid extra data. pub fn corrupt_block(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); + let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -272,7 +275,7 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid parent hash. pub fn corrupt_block_parent(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); + let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); header.set_parent_hash(H256::from(42)); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -314,6 +317,11 @@ impl TestBlockChainClient { let res = res.into_iter().next().unwrap().expect("Successful import"); assert_eq!(res, TransactionImportResult::Current); } + + /// Set reported history size. + pub fn set_history(&self, h: Option) { + *self.history.write() = h; + } } pub fn get_temp_state_db() -> GuardedTempResult { @@ -336,8 +344,7 @@ impl MiningBlockChainClient for TestBlockChainClient { let engine = &*self.spec.engine; let genesis_header = self.spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - self.spec.ensure_db_good(&mut db, &TrieFactory::default()).unwrap(); + let db = self.spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = vec![genesis_header.hash()]; let mut open_block = OpenBlock::new( @@ -451,7 +458,7 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } - fn uncle(&self, _id: UncleId) -> Option { + fn uncle(&self, _id: UncleId) -> Option { None // Simple default. } @@ -480,34 +487,39 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn best_block_header(&self) -> Bytes { - self.block_header(BlockId::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") + fn best_block_header(&self) -> encoded::Header { + self.block_header(BlockId::Hash(self.chain_info().best_block_hash)) + .expect("Best block always has header.") } - fn block_header(&self, id: BlockId) -> Option { - self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) + fn block_header(&self, id: BlockId) -> Option { + self.block_hash(id) + .and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) + .map(encoded::Header::new) } fn block_number(&self, _id: BlockId) -> Option { unimplemented!() } - fn block_body(&self, id: BlockId) -> Option { + fn block_body(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| { let mut stream = RlpStream::new_list(2); stream.append_raw(Rlp::new(r).at(1).as_raw(), 1); stream.append_raw(Rlp::new(r).at(2).as_raw(), 1); - stream.out() + encoded::Body::new(stream.out()) })) } - fn block(&self, id: BlockId) -> Option { - self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) + fn block(&self, id: BlockId) -> Option { + self.block_hash(id) + .and_then(|hash| self.blocks.read().get(&hash).cloned()) + .map(encoded::Block::new) } fn block_extra_info(&self, id: BlockId) -> Option> { self.block(id) - .map(|block| BlockView::new(&block).header()) + .map(|block| block.view().header()) .map(|header| self.spec.engine.extra_info(&header)) } @@ -516,7 +528,8 @@ impl BlockChainClient for TestBlockChainClient { match id { BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, - _ => BlockStatus::Unknown + BlockId::Latest | BlockId::Pending | BlockId::Earliest => BlockStatus::InChain, + _ => BlockStatus::Unknown, } } @@ -704,6 +717,7 @@ impl BlockChainClient for TestBlockChainClient { PruningInfo { earliest_chain: 1, earliest_state: 1, + state_history_size: *self.history.read(), } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index caba647d1..7e39d5002 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -24,7 +24,6 @@ use header::{BlockNumber}; use transaction::{LocalizedTransaction, SignedTransaction, PendingTransaction}; use log_entry::LocalizedLogEntry; use filter::Filter; -use views::{BlockView}; use error::{ImportResult, CallError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; @@ -40,6 +39,7 @@ use types::blockchain_info::BlockChainInfo; use types::block_status::BlockStatus; use types::mode::Mode; use types::pruning_info::PruningInfo; +use encoded; #[ipc(client_ident="RemoteClient")] /// Blockchain database client. Owns and manages a blockchain and a block queue. @@ -50,17 +50,17 @@ pub trait BlockChainClient : Sync + Send { fn keep_alive(&self) {} /// Get raw block header data by block id. - fn block_header(&self, id: BlockId) -> Option; + fn block_header(&self, id: BlockId) -> Option; /// Look up the block number for the given block ID. fn block_number(&self, id: BlockId) -> Option; /// Get raw block body data by block id. /// Block body is an RLP list of two items: uncles and transactions. - fn block_body(&self, id: BlockId) -> Option; + fn block_body(&self, id: BlockId) -> Option; /// Get raw block data by block header hash. - fn block(&self, id: BlockId) -> Option; + fn block(&self, id: BlockId) -> Option; /// Get block status by block header hash. fn block_status(&self, id: BlockId) -> BlockStatus; @@ -136,7 +136,7 @@ pub trait BlockChainClient : Sync + Send { fn transaction_block(&self, id: TransactionId) -> Option; /// Get uncle with given id. - fn uncle(&self, id: UncleId) -> Option; + fn uncle(&self, id: UncleId) -> Option; /// Get transaction receipt with given hash. fn transaction_receipt(&self, id: TransactionId) -> Option; @@ -173,7 +173,7 @@ pub trait BlockChainClient : Sync + Send { fn additional_params(&self) -> BTreeMap; /// Get the best block header. - fn best_block_header(&self) -> Bytes; + fn best_block_header(&self) -> encoded::Header; /// Returns numbers of blocks containing given bloom. fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option>; @@ -220,8 +220,7 @@ pub trait BlockChainClient : Sync + Send { let mut corpus = Vec::new(); while corpus.is_empty() { for _ in 0..sample_size { - let block_bytes = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); - let block = BlockView::new(&block_bytes); + let block = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); let header = block.header_view(); if header.number() == 0 { return corpus; @@ -277,7 +276,7 @@ pub trait BlockChainClient : Sync + Send { /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; - /// Get the address of a particular blockchain service, if available. + /// Get the address of a particular blockchain service, if available. fn registry_address(&self, name: String) -> Option
; } diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 11d77bc78..72dacddf4 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -107,7 +107,7 @@ impl AuthorityRound { params: params, our_params: our_params, builtins: builtins, - transition_service: try!(IoService::<()>::start()), + transition_service: IoService::<()>::start()?, message_channel: Mutex::new(None), step: AtomicUsize::new(initial_step), proposed: AtomicBool::new(false), @@ -117,7 +117,7 @@ impl AuthorityRound { // Do not initialize timeouts for tests. if should_timeout { let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; - try!(engine.transition_service.register_handler(Arc::new(handler))); + engine.transition_service.register_handler(Arc::new(handler))?; } Ok(engine) } @@ -263,20 +263,20 @@ impl Engine for AuthorityRound { /// Check if the signature belongs to the correct proposer. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let header_step = try!(header_step(header)); + let header_step = header_step(header)?; // Give one step slack if step is lagging, double vote is still not possible. if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 { - let proposer_signature = try!(header_signature(header)); - let ok_sig = try!(verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())); + let proposer_signature = header_signature(header)?; + let ok_sig = verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?; if ok_sig { Ok(()) } else { trace!(target: "poa", "verify_block_unordered: invalid seal signature"); - try!(Err(BlockError::InvalidSeal)) + Err(BlockError::InvalidSeal)? } } else { trace!(target: "poa", "verify_block_unordered: block from the future"); - try!(Err(BlockError::InvalidSeal)) + Err(BlockError::InvalidSeal)? } } @@ -285,11 +285,11 @@ impl Engine for AuthorityRound { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } - let step = try!(header_step(header)); + let step = header_step(header)?; // Check if parent is from a previous step. - if step == try!(header_step(parent)) { + if step == header_step(parent)? { trace!(target: "poa", "Multiple blocks proposed for step {}.", step); - try!(Err(EngineError::DoubleVote(header.author().clone()))); + Err(EngineError::DoubleVote(header.author().clone()))?; } let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; @@ -302,7 +302,7 @@ impl Engine for AuthorityRound { } fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { - try!(t.check_low_s()); + t.check_low_s()?; Ok(()) } @@ -339,7 +339,6 @@ impl Engine for AuthorityRound { #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use env_info::EnvInfo; use header::Header; use error::{Error, BlockError}; @@ -407,10 +406,8 @@ mod tests { let engine = &*spec.engine; engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); - let mut db1 = get_temp_state_db().take(); - spec.ensure_db_good(&mut db1, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - let mut db2 = get_temp_state_db().take(); - spec.ensure_db_good(&mut db2, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db1 = spec.ensure_db_good(get_temp_state_db().take(), &Default::default()).unwrap(); + let db2 = spec.ensure_db_good(get_temp_state_db().take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b1 = b1.close_and_lock(); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 37ac4066b..8112a574e 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -143,10 +143,10 @@ impl Engine for BasicAuthority { use rlp::{UntrustedRlp, View}; // check the signature is legit. - let sig = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); - let signer = public_to_address(&try!(recover(&sig.into(), &header.bare_hash()))); + let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; + let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); if !self.our_params.authorities.contains(&signer) { - return try!(Err(BlockError::InvalidSeal)); + return Err(BlockError::InvalidSeal)?; } Ok(()) } @@ -171,7 +171,7 @@ impl Engine for BasicAuthority { } fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> { - try!(t.check_low_s()); + t.check_low_s()?; Ok(()) } @@ -191,7 +191,6 @@ impl Engine for BasicAuthority { #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use block::*; use env_info::EnvInfo; use error::{BlockError, Error}; @@ -265,8 +264,7 @@ mod tests { engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 74f71168c..ff32f87ec 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -66,7 +66,6 @@ impl Engine for InstantSeal { #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use tests::helpers::*; use spec::Spec; use header::Header; @@ -79,8 +78,7 @@ mod tests { let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 3e5da592d..02e1276cf 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -51,9 +51,9 @@ impl ConsensusMessage { pub fn new_proposal(header: &Header) -> Result { Ok(ConsensusMessage { - signature: try!(UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()), + signature: UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()?, height: header.number() as Height, - round: try!(consensus_round(header)), + round: consensus_round(header)?, step: Step::Propose, block_hash: Some(header.bare_hash()), }) @@ -92,7 +92,7 @@ impl ConsensusMessage { pub fn verify(&self) -> Result { let full_rlp = ::rlp::encode(self); let block_info = Rlp::new(&full_rlp).at(1); - let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3())); + let public_key = recover(&self.signature.into(), &block_info.as_raw().sha3())?; Ok(public_to_address(&public_key)) } @@ -134,7 +134,7 @@ impl Ord for ConsensusMessage { impl Decodable for Step { fn decode(decoder: &D) -> Result where D: Decoder { - match try!(decoder.as_rlp().as_val()) { + match decoder.as_rlp().as_val()? { 0u8 => Ok(Step::Propose), 1 => Ok(Step::Prevote), 2 => Ok(Step::Precommit), @@ -153,13 +153,13 @@ impl Encodable for Step { impl Decodable for ConsensusMessage { fn decode(decoder: &D) -> Result where D: Decoder { let rlp = decoder.as_rlp(); - let m = try!(rlp.at(1)); - let block_message: H256 = try!(m.val_at(3)); + let m = rlp.at(1)?; + let block_message: H256 = m.val_at(3)?; Ok(ConsensusMessage { - signature: try!(rlp.val_at(0)), - height: try!(m.val_at(0)), - round: try!(m.val_at(1)), - step: try!(m.val_at(2)), + signature: rlp.val_at(0)?, + height: m.val_at(0)?, + round: m.val_at(1)?, + step: m.val_at(2)?, block_hash: match block_message.is_zero() { true => None, false => Some(block_message), diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bb6d54ca5..e124445e9 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -109,7 +109,7 @@ impl Tendermint { params: params, our_params: our_params, builtins: builtins, - step_service: try!(IoService::::start()), + step_service: IoService::::start()?, authority: RwLock::new(Address::default()), password: RwLock::new(None), height: AtomicUsize::new(1), @@ -123,7 +123,7 @@ impl Tendermint { proposal: RwLock::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; - try!(engine.step_service.register_handler(Arc::new(handler))); + engine.step_service.register_handler(Arc::new(handler))?; Ok(engine) } @@ -455,11 +455,11 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: &[u8]) -> Result<(), Error> { let rlp = UntrustedRlp::new(rlp); - let message: ConsensusMessage = try!(rlp.as_val()); + let message: ConsensusMessage = rlp.as_val()?; if !self.votes.is_old_or_known(&message) { - let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); + let sender = public_to_address(&recover(&message.signature.into(), &rlp.at(1)?.as_raw().sha3())?); if !self.is_authority(&sender) { - try!(Err(EngineError::NotAuthorized(sender))); + Err(EngineError::NotAuthorized(sender))?; } self.broadcast_message(rlp.as_raw().to_vec()); trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender); @@ -491,10 +491,10 @@ impl Engine for Tendermint { } fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let proposal = try!(ConsensusMessage::new_proposal(header)); - let proposer = try!(proposal.verify()); + let proposal = ConsensusMessage::new_proposal(header)?; + let proposer = proposal.verify()?; if !self.is_authority(&proposer) { - try!(Err(EngineError::NotAuthorized(proposer))) + Err(EngineError::NotAuthorized(proposer))? } let precommit_hash = proposal.precommit_hash(); @@ -502,20 +502,20 @@ impl Engine for Tendermint { let mut signature_count = 0; let mut origins = HashSet::new(); for rlp in UntrustedRlp::new(signatures_field).iter() { - let precommit: ConsensusMessage = ConsensusMessage::new_commit(&proposal, try!(rlp.as_val())); + let precommit: ConsensusMessage = ConsensusMessage::new_commit(&proposal, rlp.as_val()?); let address = match self.votes.get(&precommit) { Some(a) => a, - None => public_to_address(&try!(recover(&precommit.signature.into(), &precommit_hash))), + None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), }; if !self.our_params.authorities.contains(&address) { - try!(Err(EngineError::NotAuthorized(address.to_owned()))) + Err(EngineError::NotAuthorized(address.to_owned()))? } if origins.insert(address) { signature_count += 1; } else { warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); - try!(Err(BlockError::InvalidSeal)); + Err(BlockError::InvalidSeal)?; } } @@ -524,34 +524,34 @@ impl Engine for Tendermint { let signatures_len = signatures_field.len(); // Proposal has to have an empty signature list. if signatures_len != 1 { - try!(Err(EngineError::BadSealFieldSize(OutOfBounds { + Err(EngineError::BadSealFieldSize(OutOfBounds { min: Some(1), max: Some(1), found: signatures_len - }))); + }))?; } - try!(self.is_round_proposer(proposal.height, proposal.round, &proposer)); + self.is_round_proposer(proposal.height, proposal.round, &proposer)?; } Ok(()) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.number() == 0 { - try!(Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); + Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; } let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { - try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); + Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))?; } Ok(()) } fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { - try!(t.check_low_s()); + t.check_low_s()?; Ok(()) } @@ -656,7 +656,6 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use io::{IoContext, IoHandler}; use block::*; use error::{Error, BlockError}; @@ -681,8 +680,7 @@ mod tests { fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec) { let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); @@ -889,9 +887,6 @@ mod tests { fn relays_messages() { let (spec, tap) = setup(); let engine = spec.engine.clone(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let v0 = insert_and_register(&tap, &engine, "0"); let v1 = insert_and_register(&tap, &engine, "1"); @@ -925,9 +920,6 @@ mod tests { fn seal_submission() { let (spec, tap) = setup(); let engine = spec.engine.clone(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let v0 = insert_and_register(&tap, &engine, "0"); let v1 = insert_and_register(&tap, &engine, "1"); diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index feb749e9e..909480570 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -167,7 +167,7 @@ impl Engine for Ethash { fn signing_network_id(&self, env_info: &EnvInfo) -> Option { if env_info.number >= self.ethash_params.eip155_transition { - Some(self.params().network_id) + Some(self.params().chain_id) } else { None } @@ -240,8 +240,8 @@ impl Engine for Ethash { Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))); } - try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); - try!(UntrustedRlp::new(&header.seal()[1]).as_val::()); + UntrustedRlp::new(&header.seal()[0]).as_val::()?; + UntrustedRlp::new(&header.seal()[1]).as_val::()?; // TODO: consider removing these lines. let min_difficulty = self.ethash_params.minimum_difficulty; @@ -312,11 +312,11 @@ impl Engine for Ethash { fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> { if header.number() >= self.ethash_params.homestead_transition { - try!(t.check_low_s()); + t.check_low_s()?; } if let Some(n) = t.network_id() { - if header.number() < self.ethash_params.eip155_transition || n != self.params().network_id { + if header.number() < self.ethash_params.eip155_transition || n != self.params().chain_id { return Err(TransactionError::InvalidNetworkId.into()) } } @@ -433,7 +433,6 @@ impl Header { #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use block::*; use tests::helpers::*; use env_info::EnvInfo; @@ -449,8 +448,7 @@ mod tests { let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close(); @@ -463,8 +461,7 @@ mod tests { let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let mut uncle = Header::new(); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 0f7683046..cf7f4ba06 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -78,7 +78,6 @@ pub fn new_morden() -> Spec { load(include_bytes!("../../res/ethereum/morden.jso #[cfg(test)] mod tests { use util::*; - use util::trie::TrieSpec; use state::*; use super::*; use tests::helpers::*; @@ -90,8 +89,7 @@ mod tests { let engine = &spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap(); assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()), 1u64.into()); assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()), 1u64.into()); diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index 28a2ee9b4..851f6c012 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -159,39 +159,39 @@ impl Gasometer { Request::Gas(gas) }, instructions::MSTORE | instructions::MLOAD => { - Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32))) + Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 32)?) }, instructions::MSTORE8 => { - Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1))) + Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 1)?) }, instructions::RETURN => { - Request::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) + Request::GasMem(default_gas, mem_needed(stack.peek(0), stack.peek(1))?) }, instructions::SHA3 => { - let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31)); + let w = overflowing!(add_gas_usize(Gas::from_u256(*stack.peek(1))?, 31)); let words = w >> 5; let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words); - Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) + Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?) }, instructions::CALLDATACOPY | instructions::CODECOPY => { - Request::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) + Request::GasMemCopy(default_gas, mem_needed(stack.peek(0), stack.peek(2))?, Gas::from_u256(*stack.peek(2))?) }, instructions::EXTCODECOPY => { - Request::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) + Request::GasMemCopy(schedule.extcodecopy_base_gas.into(), mem_needed(stack.peek(1), stack.peek(3))?, Gas::from_u256(*stack.peek(3))?) }, instructions::LOG0...instructions::LOG4 => { let no_of_topics = instructions::get_log_topics(instruction); let log_gas = schedule.log_gas + schedule.log_topic_gas * no_of_topics; - let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas))); + let data_gas = overflowing!(Gas::from_u256(*stack.peek(1))?.overflow_mul(Gas::from(schedule.log_data_gas))); let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas))); - Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) + Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?) }, instructions::CALL | instructions::CALLCODE => { let mut gas = Gas::from(schedule.call_gas); let mem = cmp::max( - try!(mem_needed(stack.peek(5), stack.peek(6))), - try!(mem_needed(stack.peek(3), stack.peek(4))) + mem_needed(stack.peek(5), stack.peek(6))?, + mem_needed(stack.peek(3), stack.peek(4))? ); let address = u256_to_address(stack.peek(1)); @@ -216,8 +216,8 @@ impl Gasometer { instructions::DELEGATECALL => { let gas = Gas::from(schedule.call_gas); let mem = cmp::max( - try!(mem_needed(stack.peek(4), stack.peek(5))), - try!(mem_needed(stack.peek(2), stack.peek(3))) + mem_needed(stack.peek(4), stack.peek(5))?, + mem_needed(stack.peek(2), stack.peek(3))? ); let requested = *stack.peek(0); @@ -225,7 +225,7 @@ impl Gasometer { }, instructions::CREATE => { let gas = Gas::from(schedule.create_gas); - let mem = try!(mem_needed(stack.peek(1), stack.peek(2))); + let mem = mem_needed(stack.peek(1), stack.peek(2))?; Request::GasMemProvide(gas, mem, None) }, @@ -248,7 +248,7 @@ impl Gasometer { } }, Request::GasMem(gas, mem_size) => { - let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?; let gas = overflowing!(gas.overflow_add(mem_gas_cost)); InstructionRequirements { gas_cost: gas, @@ -258,9 +258,9 @@ impl Gasometer { } }, Request::GasMemProvide(gas, mem_size, requested) => { - let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?; let gas = overflowing!(gas.overflow_add(mem_gas_cost)); - let provided = try!(self.gas_provided(schedule, gas, requested)); + let provided = self.gas_provided(schedule, gas, requested)?; let total_gas = overflowing!(gas.overflow_add(provided)); InstructionRequirements { @@ -271,7 +271,7 @@ impl Gasometer { } }, Request::GasMemCopy(gas, mem_size, copy) => { - let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?; let copy = overflowing!(add_gas_usize(copy, 31)) >> 5; let copy_gas = Gas::from(schedule.copy_gas) * copy; let gas = overflowing!(gas.overflow_add(copy_gas)); @@ -303,7 +303,7 @@ impl Gasometer { let req_mem_size_rounded = (overflowing!(mem_size.overflow_add(Gas::from(31 as usize))) >> 5) << 5; let (mem_gas_cost, new_mem_gas) = if req_mem_size_rounded > current_mem_size { - let new_mem_gas = try!(gas_for_mem(req_mem_size_rounded)); + let new_mem_gas = gas_for_mem(req_mem_size_rounded)?; (new_mem_gas - self.current_mem_gas, new_mem_gas) } else { (Gas::from(0), self.current_mem_gas) diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 7b31ec28d..b10bf3246 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -107,7 +107,7 @@ impl evm::Evm for Interpreter { let code = ¶ms.code.as_ref().expect("exec always called with code; qed"); let valid_jump_destinations = self.cache.jump_destinations(¶ms.code_hash, code); - let mut gasometer = Gasometer::::new(try!(Cost::from_u256(params.gas))); + let mut gasometer = Gasometer::::new(Cost::from_u256(params.gas)?); let mut stack = VecStack::with_capacity(ext.schedule().stack_limit, U256::zero()); let mut reader = CodeReader::new(code); let infos = &*instructions::INSTRUCTIONS; @@ -117,14 +117,14 @@ impl evm::Evm for Interpreter { reader.position += 1; let info = &infos[instruction as usize]; - try!(self.verify_instruction(ext, instruction, info, &stack)); + self.verify_instruction(ext, instruction, info, &stack)?; // Calculate gas cost - let requirements = try!(gasometer.requirements(ext, instruction, info, &stack, self.mem.size())); + let requirements = gasometer.requirements(ext, instruction, info, &stack, self.mem.size())?; // TODO: make compile-time removable if too much of a performance hit. let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &requirements.gas_cost.as_u256()); - try!(gasometer.verify_gas(&requirements.gas_cost)); + gasometer.verify_gas(&requirements.gas_cost)?; self.mem.expand(requirements.memory_required_size); gasometer.current_mem_gas = requirements.memory_total_gas; gasometer.current_gas = gasometer.current_gas - requirements.gas_cost; @@ -137,9 +137,9 @@ impl evm::Evm for Interpreter { }; // Execute instruction - let result = try!(self.exec_instruction( + let result = self.exec_instruction( gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, requirements.provide_gas - )); + )?; evm_debug!({ informant.after_instruction(instruction) }); @@ -154,7 +154,7 @@ impl evm::Evm for Interpreter { // Advance match result { InstructionResult::JumpToPosition(position) => { - let pos = try!(self.verify_jump(position, &valid_jump_destinations)); + let pos = self.verify_jump(position, &valid_jump_destinations)?; reader.position = pos; }, InstructionResult::StopExecutionNeedsReturn(gas, off, size) => { @@ -513,7 +513,7 @@ impl Interpreter { stack.push(ext.env_info().gas_limit.clone()); }, _ => { - try!(self.exec_stack_instruction(instruction, stack)); + self.exec_stack_instruction(instruction, stack)?; } }; Ok(InstructionResult::Ok) diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index a6c66b067..57c63d7db 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -122,10 +122,10 @@ impl<'a> Executive<'a> { mut tracer: T, mut vm_tracer: V ) -> Result where T: Tracer, V: VMTracer { - let sender = try!(t.sender().map_err(|e| { + let sender = t.sender().map_err(|e| { let message = format!("Transaction malformed: {:?}", e); ExecutionError::TransactionMalformed(message) - })); + })?; let nonce = self.state.nonce(&sender); let schedule = self.engine.schedule(self.info); @@ -206,7 +206,7 @@ impl<'a> Executive<'a> { }; // finalize here! - Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces(), vm_tracer.drain()))) + Ok(self.finalize(t, substate, gas_left, output, tracer.traces(), vm_tracer.drain())?) } fn exec_vm( diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 666ed3ec2..adc51717d 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -265,26 +265,26 @@ impl Decodable for Header { let r = decoder.as_rlp(); let mut blockheader = Header { - parent_hash: try!(r.val_at(0)), - uncles_hash: try!(r.val_at(1)), - author: try!(r.val_at(2)), - state_root: try!(r.val_at(3)), - transactions_root: try!(r.val_at(4)), - receipts_root: try!(r.val_at(5)), - log_bloom: try!(r.val_at(6)), - difficulty: try!(r.val_at(7)), - number: try!(r.val_at(8)), - gas_limit: try!(r.val_at(9)), - gas_used: try!(r.val_at(10)), - timestamp: try!(r.val_at(11)), - extra_data: try!(r.val_at(12)), + parent_hash: r.val_at(0)?, + uncles_hash: r.val_at(1)?, + author: r.val_at(2)?, + state_root: r.val_at(3)?, + transactions_root: r.val_at(4)?, + receipts_root: r.val_at(5)?, + log_bloom: r.val_at(6)?, + difficulty: r.val_at(7)?, + number: r.val_at(8)?, + gas_limit: r.val_at(9)?, + gas_used: r.val_at(10)?, + timestamp: r.val_at(11)?, + extra_data: r.val_at(12)?, seal: vec![], hash: RefCell::new(Some(r.as_raw().sha3())), bare_hash: RefCell::new(None), }; for i in 13..r.item_count() { - blockheader.seal.push(try!(r.at(i)).as_raw().to_vec()) + blockheader.seal.push(r.at(i)?.as_raw().to_vec()) } Ok(blockheader) diff --git a/ethcore/src/migrations/state/v7.rs b/ethcore/src/migrations/state/v7.rs index 69178188b..d2b11d359 100644 --- a/ethcore/src/migrations/state/v7.rs +++ b/ethcore/src/migrations/state/v7.rs @@ -109,7 +109,7 @@ impl OverlayRecentV7 { // walk all journal entries in the database backwards. // find migrations for any possible inserted keys. fn walk_journal(&mut self, source: Arc) -> Result<(), Error> { - if let Some(val) = try!(source.get(None, V7_LATEST_ERA_KEY).map_err(Error::Custom)) { + if let Some(val) = source.get(None, V7_LATEST_ERA_KEY).map_err(Error::Custom)? { let mut era = decode::(&val); loop { let mut index: usize = 0; @@ -120,7 +120,7 @@ impl OverlayRecentV7 { r.out() }; - if let Some(journal_raw) = try!(source.get(None, &entry_key).map_err(Error::Custom)) { + if let Some(journal_raw) = source.get(None, &entry_key).map_err(Error::Custom)? { let rlp = Rlp::new(&journal_raw); // migrate all inserted keys. @@ -153,8 +153,8 @@ impl OverlayRecentV7 { // replace all possible inserted/deleted keys with their migrated counterparts // and commit the altered entries. fn migrate_journal(&self, source: Arc, mut batch: Batch, dest: &mut Database) -> Result<(), Error> { - if let Some(val) = try!(source.get(None, V7_LATEST_ERA_KEY).map_err(Error::Custom)) { - try!(batch.insert(V7_LATEST_ERA_KEY.into(), val.clone().to_vec(), dest)); + if let Some(val) = source.get(None, V7_LATEST_ERA_KEY).map_err(Error::Custom)? { + batch.insert(V7_LATEST_ERA_KEY.into(), val.clone().to_vec(), dest)?; let mut era = decode::(&val); loop { @@ -166,7 +166,7 @@ impl OverlayRecentV7 { r.out() }; - if let Some(journal_raw) = try!(source.get(None, &entry_key).map_err(Error::Custom)) { + if let Some(journal_raw) = source.get(None, &entry_key).map_err(Error::Custom)? { let rlp = Rlp::new(&journal_raw); let id: H256 = rlp.val_at(0); let mut inserted_keys: Vec<(H256, Bytes)> = Vec::new(); @@ -202,7 +202,7 @@ impl OverlayRecentV7 { stream.append(&deleted_keys); // and insert it into the new database. - try!(batch.insert(entry_key, stream.out(), dest)); + batch.insert(entry_key, stream.out(), dest)?; index += 1; } else { @@ -233,7 +233,7 @@ impl Migration for OverlayRecentV7 { let mut batch = Batch::new(config, col); // check version metadata. - match try!(source.get(None, V7_VERSION_KEY).map_err(Error::Custom)) { + match source.get(None, V7_VERSION_KEY).map_err(Error::Custom)? { Some(ref version) if decode::(&*version) == DB_VERSION => {} _ => return Err(Error::MigrationImpossible), // missing or wrong version } @@ -255,10 +255,10 @@ impl Migration for OverlayRecentV7 { } } - try!(batch.insert(key, value.into_vec(), dest)); + batch.insert(key, value.into_vec(), dest)?; } - try!(self.walk_journal(source.clone())); + self.walk_journal(source.clone())?; self.migrate_journal(source, batch, dest) } } diff --git a/ethcore/src/migrations/v10.rs b/ethcore/src/migrations/v10.rs index 3726d2e97..d0e6aa9fd 100644 --- a/ethcore/src/migrations/v10.rs +++ b/ethcore/src/migrations/v10.rs @@ -32,7 +32,7 @@ use util::{Database, DBTransaction}; /// Can be called on upgraded database with no issues (will do nothing). pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), Error> { trace!(target: "migration", "Account bloom upgrade started"); - let best_block_hash = match try!(source.get(COL_EXTRA, b"best")) { + let best_block_hash = match source.get(COL_EXTRA, b"best")? { // no migration needed None => { trace!(target: "migration", "No best block hash, skipping"); @@ -40,7 +40,7 @@ pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), }, Some(hash) => hash, }; - let best_block_header = match try!(source.get(COL_HEADERS, &best_block_hash)) { + let best_block_header = match source.get(COL_HEADERS, &best_block_hash)? { // no best block, nothing to do None => { trace!(target: "migration", "No best block header, skipping"); @@ -58,9 +58,9 @@ pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), source.clone(), journaldb::Algorithm::OverlayRecent, COL_STATE); - let account_trie = try!(TrieDB::new(state_db.as_hashdb(), &state_root).map_err(|e| Error::Custom(format!("Cannot open trie: {:?}", e)))); - for item in try!(account_trie.iter().map_err(|_| Error::MigrationImpossible)) { - let (ref account_key, _) = try!(item.map_err(|_| Error::MigrationImpossible)); + let account_trie = TrieDB::new(state_db.as_hashdb(), &state_root).map_err(|e| Error::Custom(format!("Cannot open trie: {:?}", e)))?; + for item in account_trie.iter().map_err(|_| Error::MigrationImpossible)? { + let (ref account_key, _) = item.map_err(|_| Error::MigrationImpossible)?; let account_key_hash = H256::from_slice(account_key); bloom.set(&*account_key_hash); } @@ -71,8 +71,8 @@ pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), trace!(target: "migration", "Generated {} bloom updates", bloom_journal.entries.len()); let mut batch = DBTransaction::new(dest); - try!(StateDB::commit_bloom(&mut batch, bloom_journal).map_err(|_| Error::Custom("Failed to commit bloom".to_owned()))); - try!(dest.write(batch)); + StateDB::commit_bloom(&mut batch, bloom_journal).map_err(|_| Error::Custom("Failed to commit bloom".to_owned()))?; + dest.write(batch)?; trace!(target: "migration", "Finished bloom update"); @@ -104,12 +104,12 @@ impl Migration for ToV10 { let mut batch = Batch::new(config, col); for (key, value) in source.iter(col) { self.progress.tick(); - try!(batch.insert(key.to_vec(), value.to_vec(), dest)); + batch.insert(key.to_vec(), value.to_vec(), dest)?; } - try!(batch.commit(dest)); + batch.commit(dest)?; if col == COL_STATE { - try!(generate_bloom(source, dest)); + generate_bloom(source, dest)?; } Ok(()) diff --git a/ethcore/src/migrations/v9.rs b/ethcore/src/migrations/v9.rs index b99de0f51..909fa95f6 100644 --- a/ethcore/src/migrations/v9.rs +++ b/ethcore/src/migrations/v9.rs @@ -63,17 +63,17 @@ impl Migration for ToV9 { self.progress.tick(); match self.extract { Extract::Header => { - try!(batch.insert(key.to_vec(), Rlp::new(&value).at(0).as_raw().to_vec(), dest)) + batch.insert(key.to_vec(), Rlp::new(&value).at(0).as_raw().to_vec(), dest)? }, Extract::Body => { let mut body = RlpStream::new_list(2); let block_rlp = Rlp::new(&value); body.append_raw(block_rlp.at(1).as_raw(), 1); body.append_raw(block_rlp.at(2).as_raw(), 1); - try!(batch.insert(key.to_vec(), body.out(), dest)) + batch.insert(key.to_vec(), body.out(), dest)? }, Extract::All => { - try!(batch.insert(key.to_vec(), value.to_vec(), dest)) + batch.insert(key.to_vec(), value.to_vec(), dest)? } } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c7bff4784..c11f34ecb 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -20,8 +20,6 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; use account_provider::{AccountProvider, Error as AccountError}; -use views::{BlockView, HeaderView}; -use header::Header; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId}; use client::TransactionImportResult; @@ -536,7 +534,7 @@ impl Miner { } fn update_gas_limit(&self, chain: &MiningBlockChainClient) { - let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); + let gas_limit = chain.best_block_header().gas_limit(); let mut queue = self.transaction_queue.lock(); queue.set_gas_limit(gas_limit); if let GasLimit::Auto = self.options.tx_queue_gas_limit { @@ -598,7 +596,7 @@ impl Miner { let schedule = chain.latest_schedule(); let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); - let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); + let best_block_header = chain.best_block_header().decode(); transactions.into_iter() .map(|tx| { if chain.transaction_block(TransactionId::Hash(tx.hash())).is_some() { @@ -698,10 +696,10 @@ impl MinerService for Miner { let mut state = block.state().clone(); let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; - let sender = try!(t.sender().map_err(|e| { + let sender = t.sender().map_err(|e| { let message = format!("Transaction malformed: {:?}", e); ExecutionError::TransactionMalformed(message) - })); + })?; let balance = state.balance(&sender); let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { @@ -709,7 +707,7 @@ impl MinerService for Miner { state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let mut ret = try!(Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)); + let mut ret = Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)?; // TODO gav move this into Executive. ret.state_diff = original_state.map(|original| state.diff_from(original)); @@ -765,7 +763,7 @@ impl MinerService for Miner { fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { if self.seals_internally { if let Some(ref ap) = self.accounts { - try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); + ap.sign(address.clone(), Some(password.clone()), Default::default())?; } let mut sealing_work = self.sealing_work.lock(); sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); @@ -1104,7 +1102,7 @@ impl MinerService for Miner { result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); - try!(chain.import_sealed_block(sealed)); + chain.import_sealed_block(sealed)?; info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); Ok(()) }) @@ -1127,7 +1125,6 @@ impl MinerService for Miner { .map(|hash| { let block = chain.block(BlockId::Hash(*hash)) .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); - let block = BlockView::new(&block); let txs = block.transactions(); // populate sender for tx in &txs { diff --git a/ethcore/src/miner/price_info.rs b/ethcore/src/miner/price_info.rs index e1cf82f99..897440ebe 100644 --- a/ethcore/src/miner/price_info.rs +++ b/ethcore/src/miner/price_info.rs @@ -65,7 +65,7 @@ impl Handler for SetPriceH impl PriceInfo { pub fn get(set_price: F) -> Result<(), ()> { // TODO: Handle each error type properly - let client = try!(Client::new().map_err(|_| ())); + let client = Client::new().map_err(|_| ())?; thread::spawn(move || { let (tx, rx) = mpsc::channel(); let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice") diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 9f5b6399a..a51c70bc0 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -264,7 +264,7 @@ struct VerifiedTransaction { impl VerifiedTransaction { fn new(transaction: SignedTransaction, origin: TransactionOrigin, min_block: Option) -> Result { - try!(transaction.sender()); + transaction.sender()?; Ok(VerifiedTransaction { transaction: transaction, origin: origin, @@ -732,9 +732,9 @@ impl TransactionQueue { } // Verify signature - try!(tx.check_low_s()); + tx.check_low_s()?; - let vtx = try!(VerifiedTransaction::new(tx, origin, min_block)); + let vtx = VerifiedTransaction::new(tx, origin, min_block)?; let client_account = fetch_account(&vtx.sender()); let cost = vtx.transaction.value + vtx.transaction.gas_price * vtx.transaction.gas; @@ -1138,13 +1138,13 @@ impl TransactionQueue { if nonce > next_nonce { // We have a gap - put to future. // Insert transaction (or replace old one with lower gas price) - try!(check_too_cheap( + check_too_cheap( Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash, &mut self.local_transactions) - )); + )?; // Enforce limit in Future let removed = self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); // Return an error if this transaction was not imported because of limit. - try!(check_if_removed(&address, &nonce, removed)); + check_if_removed(&address, &nonce, removed)?; debug!(target: "txqueue", "Importing transaction to future: {:?}", hash); debug!(target: "txqueue", "status: {:?}", self.status()); @@ -1156,9 +1156,9 @@ impl TransactionQueue { self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); // Replace transaction if any - try!(check_too_cheap( + check_too_cheap( Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash, &mut self.local_transactions) - )); + )?; // Keep track of highest nonce stored in current let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n)); self.last_nonces.insert(address, new_max); @@ -1168,7 +1168,7 @@ impl TransactionQueue { // If some transaction were removed because of limit we need to update last_nonces also. self.update_last_nonces(&removed); // Trigger error if the transaction we are importing was removed. - try!(check_if_removed(&address, &nonce, removed)); + check_if_removed(&address, &nonce, removed)?; debug!(target: "txqueue", "Imported transaction to current: {:?}", hash); debug!(target: "txqueue", "status: {:?}", self.status()); diff --git a/ethcore/src/pod_state.rs b/ethcore/src/pod_state.rs index 18045c510..fa6cbc082 100644 --- a/ethcore/src/pod_state.rs +++ b/ethcore/src/pod_state.rs @@ -64,7 +64,7 @@ impl From for PodState { impl fmt::Display for PodState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (add, acc) in &self.0 { - try!(writeln!(f, "{} => {}", add, acc)); + writeln!(f, "{} => {}", add, acc)?; } Ok(()) } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 732d12a5b..b0f1df420 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -77,7 +77,7 @@ impl ClientService { ) -> Result { let panic_handler = PanicHandler::new_in_arc(); - let io_service = try!(IoService::::start()); + let io_service = IoService::::start()?; panic_handler.forward_from(&io_service); info!("Configured for {} using {} engine", Colour::White.bold().paint(spec.name.clone()), Colour::Yellow.bold().paint(spec.engine.name())); @@ -94,7 +94,7 @@ impl ClientService { db_config.wal = config.db_wal; let pruning = config.pruning; - let client = try!(Client::new(config, &spec, client_path, miner, io_service.channel(), &db_config)); + let client = Client::new(config, &spec, client_path, miner, io_service.channel(), &db_config)?; let snapshot_params = SnapServiceParams { engine: spec.engine.clone(), @@ -105,14 +105,14 @@ impl ClientService { snapshot_root: snapshot_path.into(), db_restore: client.clone(), }; - let snapshot = Arc::new(try!(SnapshotService::new(snapshot_params))); + let snapshot = Arc::new(SnapshotService::new(snapshot_params)?); panic_handler.forward_from(&*client); let client_io = Arc::new(ClientIoHandler { client: client.clone(), snapshot: snapshot.clone(), }); - try!(io_service.register_handler(client_io)); + io_service.register_handler(client_io)?; spec.engine.register_message_channel(io_service.channel()); diff --git a/ethcore/src/snapshot/account.rs b/ethcore/src/snapshot/account.rs index 9d5385d92..0dfb72955 100644 --- a/ethcore/src/snapshot/account.rs +++ b/ethcore/src/snapshot/account.rs @@ -100,12 +100,12 @@ impl Account { return Ok(::rlp::NULL_RLP.to_vec()); } - let db = try!(TrieDB::new(acct_db, &self.storage_root)); + let db = TrieDB::new(acct_db, &self.storage_root)?; let mut pairs = Vec::new(); - for item in try!(db.iter()) { - let (k, v) = try!(item); + for item in db.iter()? { + let (k, v) = item?; pairs.push((k, v)); } @@ -158,24 +158,24 @@ impl Account { return Ok((ACC_EMPTY, None)); } - let nonce = try!(rlp.val_at(0)); - let balance = try!(rlp.val_at(1)); + let nonce = rlp.val_at(0)?; + let balance = rlp.val_at(1)?; let code_state: CodeState = { - let raw: u8 = try!(rlp.val_at(2)); - try!(CodeState::from(raw)) + let raw: u8 = rlp.val_at(2)?; + CodeState::from(raw)? }; // load the code if it exists. let (code_hash, new_code) = match code_state { CodeState::Empty => (SHA3_EMPTY, None), CodeState::Inline => { - let code: Bytes = try!(rlp.val_at(3)); + let code: Bytes = rlp.val_at(3)?; let code_hash = acct_db.insert(&code); (code_hash, Some(code)) } CodeState::Hash => { - let code_hash = try!(rlp.val_at(3)); + let code_hash = rlp.val_at(3)?; (code_hash, None) } @@ -185,12 +185,12 @@ impl Account { { let mut storage_trie = TrieDBMut::new(acct_db, &mut storage_root); - let pairs = try!(rlp.at(4)); + let pairs = rlp.at(4)?; for pair_rlp in pairs.iter() { - let k: Bytes = try!(pair_rlp.val_at(0)); - let v: Bytes = try!(pair_rlp.val_at(1)); + let k: Bytes = pair_rlp.val_at(0)?; + let v: Bytes = pair_rlp.val_at(1)?; - try!(storage_trie.insert(&k, &v)); + storage_trie.insert(&k, &v)?; } } diff --git a/ethcore/src/snapshot/block.rs b/ethcore/src/snapshot/block.rs index 94b7a4289..5b3846b6c 100644 --- a/ethcore/src/snapshot/block.rs +++ b/ethcore/src/snapshot/block.rs @@ -89,21 +89,21 @@ impl AbridgedBlock { let mut header: Header = Default::default(); header.set_parent_hash(parent_hash); - header.set_author(try!(rlp.val_at(0))); - header.set_state_root(try!(rlp.val_at(1))); - header.set_log_bloom(try!(rlp.val_at(2))); - header.set_difficulty(try!(rlp.val_at(3))); + header.set_author(rlp.val_at(0)?); + header.set_state_root(rlp.val_at(1)?); + header.set_log_bloom(rlp.val_at(2)?); + header.set_difficulty(rlp.val_at(3)?); header.set_number(number); - header.set_gas_limit(try!(rlp.val_at(4))); - header.set_gas_used(try!(rlp.val_at(5))); - header.set_timestamp(try!(rlp.val_at(6))); - header.set_extra_data(try!(rlp.val_at(7))); + header.set_gas_limit(rlp.val_at(4)?); + header.set_gas_used(rlp.val_at(5)?); + header.set_timestamp(rlp.val_at(6)?); + header.set_extra_data(rlp.val_at(7)?); - let transactions = try!(rlp.val_at(8)); - let uncles: Vec
= try!(rlp.val_at(9)); + let transactions = rlp.val_at(8)?; + let uncles: Vec
= rlp.val_at(9)?; header.set_transactions_root(ordered_trie_root( - try!(rlp.at(8)).iter().map(|r| r.as_raw().to_owned()) + rlp.at(8)?.iter().map(|r| r.as_raw().to_owned()) )); header.set_receipts_root(receipts_root); @@ -113,7 +113,7 @@ impl AbridgedBlock { let mut seal_fields = Vec::new(); for i in (HEADER_FIELDS + BLOCK_FIELDS)..rlp.item_count() { - let seal_rlp = try!(rlp.at(i)); + let seal_rlp = rlp.at(i)?; seal_fields.push(seal_rlp.as_raw().to_owned()); } diff --git a/ethcore/src/snapshot/io.rs b/ethcore/src/snapshot/io.rs index 2d581d571..9ef2ad5cb 100644 --- a/ethcore/src/snapshot/io.rs +++ b/ethcore/src/snapshot/io.rs @@ -60,9 +60,9 @@ impl rlp::Decodable for ChunkInfo { fn decode(decoder: &D) -> Result { let d = decoder.as_rlp(); - let hash = try!(d.val_at(0)); - let len = try!(d.val_at(1)); - let off = try!(d.val_at(2)); + let hash = d.val_at(0)?; + let len = d.val_at(1)?; + let off = d.val_at(2)?; Ok(ChunkInfo(hash, len, off)) } } @@ -88,7 +88,7 @@ impl PackedWriter { /// Create a new "PackedWriter", to write into the file at the given path. pub fn new(path: &Path) -> io::Result { Ok(PackedWriter { - file: try!(File::create(path)), + file: File::create(path)?, state_hashes: Vec::new(), block_hashes: Vec::new(), cur_len: 0, @@ -98,7 +98,7 @@ impl PackedWriter { impl SnapshotWriter for PackedWriter { fn write_state_chunk(&mut self, hash: H256, chunk: &[u8]) -> io::Result<()> { - try!(self.file.write_all(chunk)); + self.file.write_all(chunk)?; let len = chunk.len() as u64; self.state_hashes.push(ChunkInfo(hash, len, self.cur_len)); @@ -108,7 +108,7 @@ impl SnapshotWriter for PackedWriter { } fn write_block_chunk(&mut self, hash: H256, chunk: &[u8]) -> io::Result<()> { - try!(self.file.write_all(chunk)); + self.file.write_all(chunk)?; let len = chunk.len() as u64; self.block_hashes.push(ChunkInfo(hash, len, self.cur_len)); @@ -130,7 +130,7 @@ impl SnapshotWriter for PackedWriter { let manifest_rlp = stream.out(); - try!(self.file.write_all(&manifest_rlp)); + self.file.write_all(&manifest_rlp)?; let off = self.cur_len; trace!(target: "snapshot_io", "writing manifest of len {} to offset {}", manifest_rlp.len(), off); @@ -146,7 +146,7 @@ impl SnapshotWriter for PackedWriter { (off >> 56) as u8, ]; - try!(self.file.write_all(&off_bytes[..])); + self.file.write_all(&off_bytes[..])?; Ok(()) } @@ -161,7 +161,7 @@ impl LooseWriter { /// Create a new LooseWriter which will write into the given directory, /// creating it if it doesn't exist. pub fn new(path: PathBuf) -> io::Result { - try!(fs::create_dir_all(&path)); + fs::create_dir_all(&path)?; Ok(LooseWriter { dir: path, @@ -173,8 +173,8 @@ impl LooseWriter { let mut file_path = self.dir.clone(); file_path.push(hash.hex()); - let mut file = try!(File::create(file_path)); - try!(file.write_all(chunk)); + let mut file = File::create(file_path)?; + file.write_all(chunk)?; Ok(()) } @@ -194,8 +194,8 @@ impl SnapshotWriter for LooseWriter { let mut path = self.dir.clone(); path.push("MANIFEST"); - let mut file = try!(File::create(path)); - try!(file.write_all(&rlp[..])); + let mut file = File::create(path)?; + file.write_all(&rlp[..])?; Ok(()) } @@ -224,18 +224,18 @@ impl PackedReader { /// This will fail if any io errors are encountered or the file /// is not a valid packed snapshot. pub fn new(path: &Path) -> Result, ::error::Error> { - let mut file = try!(File::open(path)); - let file_len = try!(file.metadata()).len(); + let mut file = File::open(path)?; + let file_len = file.metadata()?.len(); if file_len < 8 { // ensure we don't seek before beginning. return Ok(None); } - try!(file.seek(SeekFrom::End(-8))); + file.seek(SeekFrom::End(-8))?; let mut off_bytes = [0u8; 8]; - try!(file.read_exact(&mut off_bytes[..])); + file.read_exact(&mut off_bytes[..])?; let manifest_off: u64 = ((off_bytes[7] as u64) << 56) + @@ -252,20 +252,20 @@ impl PackedReader { let mut manifest_buf = vec![0; manifest_len as usize]; - try!(file.seek(SeekFrom::Start(manifest_off))); - try!(file.read_exact(&mut manifest_buf)); + file.seek(SeekFrom::Start(manifest_off))?; + file.read_exact(&mut manifest_buf)?; let rlp = UntrustedRlp::new(&manifest_buf); - let state: Vec = try!(rlp.val_at(0)); - let blocks: Vec = try!(rlp.val_at(1)); + let state: Vec = rlp.val_at(0)?; + let blocks: Vec = rlp.val_at(1)?; let manifest = ManifestData { state_hashes: state.iter().map(|c| c.0).collect(), block_hashes: blocks.iter().map(|c| c.0).collect(), - state_root: try!(rlp.val_at(2)), - block_number: try!(rlp.val_at(3)), - block_hash: try!(rlp.val_at(4)), + state_root: rlp.val_at(2)?, + block_number: rlp.val_at(3)?, + block_hash: rlp.val_at(4)?, }; Ok(Some(PackedReader { @@ -288,10 +288,10 @@ impl SnapshotReader for PackedReader { let mut file = &self.file; - try!(file.seek(SeekFrom::Start(off))); + file.seek(SeekFrom::Start(off))?; let mut buf = vec![0; len as usize]; - try!(file.read_exact(&mut buf[..])); + file.read_exact(&mut buf[..])?; Ok(buf) } @@ -310,10 +310,10 @@ impl LooseReader { let mut manifest_buf = Vec::new(); dir.push("MANIFEST"); - let mut manifest_file = try!(File::open(&dir)); - try!(manifest_file.read_to_end(&mut manifest_buf)); + let mut manifest_file = File::open(&dir)?; + manifest_file.read_to_end(&mut manifest_buf)?; - let manifest = try!(ManifestData::from_rlp(&manifest_buf[..])); + let manifest = ManifestData::from_rlp(&manifest_buf[..])?; dir.pop(); @@ -334,9 +334,9 @@ impl SnapshotReader for LooseReader { path.push(hash.hex()); let mut buf = Vec::new(); - let mut file = try!(File::open(&path)); + let mut file = File::open(&path)?; - try!(file.read_to_end(&mut buf)); + file.read_to_end(&mut buf)?; Ok(buf) } diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 1e646effc..56fd09200 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -128,22 +128,22 @@ pub fn take_snapshot( writer: W, p: &Progress ) -> Result<(), Error> { - let start_header = try!(chain.block_header(&block_at) - .ok_or(Error::InvalidStartingBlock(BlockId::Hash(block_at)))); + let start_header = chain.block_header(&block_at) + .ok_or(Error::InvalidStartingBlock(BlockId::Hash(block_at)))?; let state_root = start_header.state_root(); let number = start_header.number(); info!("Taking snapshot starting at block {}", number); let writer = Mutex::new(writer); - let (state_hashes, block_hashes) = try!(scope(|scope| { + let (state_hashes, block_hashes) = scope(|scope| { let block_guard = scope.spawn(|| chunk_blocks(chain, block_at, &writer, p)); let state_res = chunk_state(state_db, state_root, &writer, p); state_res.and_then(|state_hashes| { block_guard.join().map(|block_hashes| (state_hashes, block_hashes)) }) - })); + })?; info!("produced {} state chunks and {} block chunks.", state_hashes.len(), block_hashes.len()); @@ -155,7 +155,7 @@ pub fn take_snapshot( block_hash: block_at, }; - try!(writer.into_inner().finish(manifest_data)); + writer.into_inner().finish(manifest_data)?; p.done.store(true, Ordering::SeqCst); @@ -186,12 +186,11 @@ impl<'a> BlockChunker<'a> { for _ in 0..SNAPSHOT_BLOCKS { if self.current_hash == genesis_hash { break } - let (block, receipts) = try!(self.chain.block(&self.current_hash) + let (block, receipts) = self.chain.block(&self.current_hash) .and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r))) - .ok_or(Error::BlockNotFound(self.current_hash))); + .ok_or(Error::BlockNotFound(self.current_hash))?; - let view = BlockView::new(&block); - let abridged_rlp = AbridgedBlock::from_block_view(&view).into_inner(); + let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner(); let pair = { let mut pair_stream = RlpStream::new_list(2); @@ -204,7 +203,7 @@ impl<'a> BlockChunker<'a> { // cut off the chunk if too large. if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() { - try!(self.write_chunk(last)); + self.write_chunk(last)?; loaded_size = pair.len(); } else { loaded_size = new_loaded_size; @@ -213,11 +212,11 @@ impl<'a> BlockChunker<'a> { self.rlps.push_front(pair); last = self.current_hash; - self.current_hash = view.header_view().parent_hash(); + self.current_hash = block.header_view().parent_hash(); } if loaded_size != 0 { - try!(self.write_chunk(last)); + self.write_chunk(last)?; } Ok(()) @@ -230,9 +229,9 @@ impl<'a> BlockChunker<'a> { fn write_chunk(&mut self, last: H256) -> Result<(), Error> { trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len()); - let (last_header, last_details) = try!(self.chain.block_header(&last) + let (last_header, last_details) = self.chain.block_header(&last) .and_then(|n| self.chain.block_details(&last).map(|d| (n, d))) - .ok_or(Error::BlockNotFound(last))); + .ok_or(Error::BlockNotFound(last))?; let parent_number = last_header.number() - 1; let parent_hash = last_header.parent_hash(); @@ -254,7 +253,7 @@ impl<'a> BlockChunker<'a> { let compressed = &self.snappy_buffer[..size]; let hash = compressed.sha3(); - try!(self.writer.lock().write_block_chunk(hash, compressed)); + self.writer.lock().write_block_chunk(hash, compressed)?; trace!(target: "snapshot", "wrote block chunk. hash: {}, size: {}, uncompressed size: {}", hash.hex(), size, raw_data.len()); self.progress.size.fetch_add(size, Ordering::SeqCst); @@ -282,7 +281,7 @@ pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_hash: H256, writer: &Mutex< progress: progress, }; - try!(chunker.chunk_all()); + chunker.chunk_all()?; Ok(chunker.hashes) } @@ -310,7 +309,7 @@ impl<'a> StateChunker<'a> { }; if self.cur_size + pair.len() >= PREFERRED_CHUNK_SIZE { - try!(self.write_chunk()); + self.write_chunk()?; } self.cur_size += pair.len(); @@ -334,7 +333,7 @@ impl<'a> StateChunker<'a> { let compressed = &self.snappy_buffer[..compressed_size]; let hash = compressed.sha3(); - try!(self.writer.lock().write_state_chunk(hash, compressed)); + self.writer.lock().write_state_chunk(hash, compressed)?; trace!(target: "snapshot", "wrote state chunk. size: {}, uncompressed size: {}", compressed_size, raw_data.len()); self.progress.accounts.fetch_add(num_entries, Ordering::SeqCst); @@ -353,7 +352,7 @@ impl<'a> StateChunker<'a> { /// Returns a list of hashes of chunks created, or any error it may /// have encountered. pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex, progress: &'a Progress) -> Result, Error> { - let account_trie = try!(TrieDB::new(db, &root)); + let account_trie = TrieDB::new(db, &root)?; let mut chunker = StateChunker { hashes: Vec::new(), @@ -367,19 +366,19 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex { // if so, load it from the database. - let code = try!(AccountDB::from_hash(db, first_with) + let code = AccountDB::from_hash(db, first_with) .get(&code_hash) - .ok_or_else(|| Error::MissingCode(vec![first_with]))); + .ok_or_else(|| Error::MissingCode(vec![first_with]))?; // and write it again under a different mangled key AccountDBMut::from_hash(db, hash).emplace(code_hash, code); @@ -586,7 +585,7 @@ impl BlockRebuilder { Ok(BlockRebuilder { chain: chain, db: db, - rng: try!(OsRng::new()), + rng: OsRng::new()?, disconnected: Vec::new(), best_number: manifest.block_number, best_hash: manifest.block_hash, @@ -613,22 +612,22 @@ impl BlockRebuilder { } // todo: assert here that these values are consistent with chunks being in order. - let mut cur_number = try!(rlp.val_at::(0)) + 1; - let mut parent_hash = try!(rlp.val_at::(1)); - let parent_total_difficulty = try!(rlp.val_at::(2)); + let mut cur_number = rlp.val_at::(0)? + 1; + let mut parent_hash = rlp.val_at::(1)?; + let parent_total_difficulty = rlp.val_at::(2)?; for idx in 3..item_count { if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) } - let pair = try!(rlp.at(idx)); - let abridged_rlp = try!(pair.at(0)).as_raw().to_owned(); + let pair = rlp.at(idx)?; + let abridged_rlp = pair.at(0)?.as_raw().to_owned(); let abridged_block = AbridgedBlock::from_raw(abridged_rlp); - let receipts: Vec<::receipt::Receipt> = try!(pair.val_at(1)); + let receipts: Vec<::receipt::Receipt> = pair.val_at(1)?; let receipts_root = ordered_trie_root( - try!(pair.at(1)).iter().map(|r| r.as_raw().to_owned()) + pair.at(1)?.iter().map(|r| r.as_raw().to_owned()) ); - let block = try!(abridged_block.to_block(parent_hash, cur_number, receipts_root)); + let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?; let block_bytes = block.rlp_bytes(With); let is_best = cur_number == self.best_number; @@ -642,14 +641,14 @@ impl BlockRebuilder { } } - try!(verify_old_block( + verify_old_block( &mut self.rng, &block.header, engine, &self.chain, Some(&block_bytes), is_best - )); + )?; let mut batch = self.db.transaction(); @@ -693,7 +692,7 @@ impl BlockRebuilder { let best_number = self.best_number; for num in (0..self.fed_blocks).map(|x| best_number - x) { - let hash = try!(self.chain.block_hash(num).ok_or(Error::IncompleteChain)); + let hash = self.chain.block_hash(num).ok_or(Error::IncompleteChain)?; if let Some(canon_hash) = canonical.get(&num).cloned() { if canon_hash != hash { diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index b433108fd..8d0d14ac7 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -97,11 +97,11 @@ impl Restoration { let state_chunks = manifest.state_hashes.iter().cloned().collect(); let block_chunks = manifest.block_hashes.iter().cloned().collect(); - let raw_db = Arc::new(try!(Database::open(params.db_config, &*params.db_path.to_string_lossy()) - .map_err(UtilError::SimpleString))); + let raw_db = Arc::new(Database::open(params.db_config, &*params.db_path.to_string_lossy()) + .map_err(UtilError::SimpleString)?); let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone(), params.engine); - let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), &manifest)); + let blocks = BlockRebuilder::new(chain, raw_db.clone(), &manifest)?; let root = manifest.state_root.clone(); Ok(Restoration { @@ -122,12 +122,12 @@ impl Restoration { // feeds a state chunk, aborts early if `flag` becomes false. fn feed_state(&mut self, hash: H256, chunk: &[u8], flag: &AtomicBool) -> Result<(), Error> { if self.state_chunks_left.remove(&hash) { - let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer)); + let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?; - try!(self.state.feed(&self.snappy_buffer[..len], flag)); + self.state.feed(&self.snappy_buffer[..len], flag)?; if let Some(ref mut writer) = self.writer.as_mut() { - try!(writer.write_state_chunk(hash, chunk)); + writer.write_state_chunk(hash, chunk)?; } } @@ -137,11 +137,11 @@ impl Restoration { // feeds a block chunk fn feed_blocks(&mut self, hash: H256, chunk: &[u8], engine: &Engine, flag: &AtomicBool) -> Result<(), Error> { if self.block_chunks_left.remove(&hash) { - let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer)); + let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?; - try!(self.blocks.feed(&self.snappy_buffer[..len], engine, flag)); + self.blocks.feed(&self.snappy_buffer[..len], engine, flag)?; if let Some(ref mut writer) = self.writer.as_mut() { - try!(writer.write_block_chunk(hash, chunk)); + writer.write_block_chunk(hash, chunk)?; } } @@ -167,13 +167,13 @@ impl Restoration { } // check for missing code. - try!(self.state.check_missing()); + self.state.check_missing()?; // connect out-of-order chunks and verify chain integrity. - try!(self.blocks.finalize(self.canonical_hashes)); + self.blocks.finalize(self.canonical_hashes)?; if let Some(writer) = self.writer { - try!(writer.finish(self.manifest)); + writer.finish(self.manifest)?; } self.guard.disarm(); @@ -315,7 +315,7 @@ impl Service { fn replace_client_db(&self) -> Result<(), Error> { let our_db = self.restoration_db(); - try!(self.db_restore.restore_db(&*our_db.to_string_lossy())); + self.db_restore.restore_db(&*our_db.to_string_lossy())?; Ok(()) } @@ -351,7 +351,7 @@ impl Service { let _ = fs::remove_dir_all(&temp_dir); - let writer = try!(LooseWriter::new(temp_dir.clone())); + let writer = LooseWriter::new(temp_dir.clone())?; let guard = Guard::new(temp_dir.clone()); let res = client.take_snapshot(writer, BlockId::Number(num), &self.progress); @@ -378,12 +378,12 @@ impl Service { *reader = None; if snapshot_dir.exists() { - try!(fs::remove_dir_all(&snapshot_dir)); + fs::remove_dir_all(&snapshot_dir)?; } - try!(fs::rename(temp_dir, &snapshot_dir)); + fs::rename(temp_dir, &snapshot_dir)?; - *reader = Some(try!(LooseReader::new(snapshot_dir))); + *reader = Some(LooseReader::new(snapshot_dir)?); guard.disarm(); Ok(()) @@ -410,11 +410,11 @@ impl Service { } } - try!(fs::create_dir_all(&rest_dir)); + fs::create_dir_all(&rest_dir)?; // make new restoration. let writer = match recover { - true => Some(try!(LooseWriter::new(self.temp_recovery_dir()))), + true => Some(LooseWriter::new(self.temp_recovery_dir())?), false => None }; @@ -432,7 +432,7 @@ impl Service { let state_chunks = params.manifest.state_hashes.len(); let block_chunks = params.manifest.block_hashes.len(); - *res = Some(try!(Restoration::new(params))); + *res = Some(Restoration::new(params)?); *self.status.lock() = RestorationStatus::Ongoing { state_chunks: state_chunks as u32, @@ -454,8 +454,8 @@ impl Service { let recover = rest.as_ref().map_or(false, |rest| rest.writer.is_some()); // destroy the restoration before replacing databases and snapshot. - try!(rest.take().map(Restoration::finalize).unwrap_or(Ok(()))); - try!(self.replace_client_db()); + rest.take().map(Restoration::finalize).unwrap_or(Ok(()))?; + self.replace_client_db()?; if recover { let mut reader = self.reader.write(); @@ -465,13 +465,13 @@ impl Service { if snapshot_dir.exists() { trace!(target: "snapshot", "removing old snapshot dir at {}", snapshot_dir.to_string_lossy()); - try!(fs::remove_dir_all(&snapshot_dir)); + fs::remove_dir_all(&snapshot_dir)?; } trace!(target: "snapshot", "copying restored snapshot files over"); - try!(fs::rename(self.temp_recovery_dir(), &snapshot_dir)); + fs::rename(self.temp_recovery_dir(), &snapshot_dir)?; - *reader = Some(try!(LooseReader::new(snapshot_dir))); + *reader = Some(LooseReader::new(snapshot_dir)?); } let _ = fs::remove_dir_all(self.restoration_dir()); @@ -510,7 +510,7 @@ impl Service { match is_done { true => { - try!(db.flush().map_err(::util::UtilError::SimpleString)); + db.flush().map_err(::util::UtilError::SimpleString)?; drop(db); return self.finalize_restoration(&mut *restoration); }, diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 91d94174e..0dda4ba93 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -20,7 +20,6 @@ use util::Mutex; use client::{BlockChainClient, Client, ChainNotify}; use ids::BlockId; use service::ClientIoMessage; -use views::HeaderView; use io::IoChannel; use util::{H256, Bytes}; @@ -43,7 +42,7 @@ impl Oracle for StandardOracle where F: Send + Sync + Fn() -> bool { fn to_number(&self, hash: H256) -> Option { - self.client.block_header(BlockId::Hash(hash)).map(|h| HeaderView::new(&h).number()) + self.client.block_header(BlockId::Hash(hash)).map(|h| h.number()) } fn is_major_importing(&self) -> bool { diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index b6a688402..f9a4b6f6a 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -19,6 +19,13 @@ use util::*; use builtin::Builtin; use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound, Tendermint}; +use factory::Factories; +use executive::Executive; +use trace::{NoopTracer, NoopVMTracer}; +use action_params::{ActionValue, ActionParams}; +use types::executed::CallType; +use state::{State, Substate}; +use env_info::EnvInfo; use pod_state::*; use account_db::*; use header::{BlockNumber, Header}; @@ -38,6 +45,8 @@ pub struct CommonParams { pub maximum_extra_data_size: usize, /// Network id. pub network_id: u64, + /// Chain id. + pub chain_id: u64, /// Main subprotocol name. pub subprotocol_name: String, /// Minimum gas limit. @@ -52,6 +61,7 @@ impl From for CommonParams { account_start_nonce: p.account_start_nonce.map_or_else(U256::zero, Into::into), maximum_extra_data_size: p.maximum_extra_data_size.into(), network_id: p.network_id.into(), + chain_id: if let Some(n) = p.chain_id { n.into() } else { p.network_id.into() }, subprotocol_name: p.subprotocol_name.unwrap_or_else(|| "eth".to_owned()), min_gas_limit: p.min_gas_limit.into(), fork_block: if let (Some(n), Some(h)) = (p.fork_block, p.fork_hash) { Some((n.into(), h.into())) } else { None }, @@ -96,10 +106,13 @@ pub struct Spec { /// Each seal field, expressed as RLP, concatenated. pub seal_rlp: Bytes, - // May be prepopulated if we know this in advance. + /// Contract constructors to be executed on genesis. + constructors: Vec<(Address, Bytes)>, + + /// May be prepopulated if we know this in advance. state_root_memo: RwLock>, - // Genesis state as plain old data. + /// Genesis state as plain old data. genesis_state: PodState, } @@ -125,6 +138,7 @@ impl From for Spec { timestamp: g.timestamp, extra_data: g.extra_data, seal_rlp: seal_rlp, + constructors: s.accounts.constructors().into_iter().map(|(a, c)| (a.into(), c.into())).collect(), state_root_memo: RwLock::new(g.state_root), genesis_state: From::from(s.accounts), } @@ -235,25 +249,71 @@ impl Spec { } /// Ensure that the given state DB has the trie nodes in for the genesis state. - pub fn ensure_db_good(&self, db: &mut StateDB, factory: &TrieFactory) -> Result> { - if !db.as_hashdb().contains(&self.state_root()) { - trace!(target: "spec", "ensure_db_good: Fresh database? Cannot find state root {}", self.state_root()); - let mut root = H256::new(); + pub fn ensure_db_good(&self, mut db: StateDB, factories: &Factories) -> Result> { + if db.as_hashdb().contains(&self.state_root()) { + return Ok(db) + } + trace!(target: "spec", "ensure_db_good: Fresh database? Cannot find state root {}", self.state_root()); + let mut root = H256::new(); + { + let mut t = factories.trie.create(db.as_hashdb_mut(), &mut root); + for (address, account) in self.genesis_state.get().iter() { + t.insert(&**address, &account.rlp())?; + } + } + + trace!(target: "spec", "ensure_db_good: Populated sec trie; root is {}", root); + for (address, account) in self.genesis_state.get().iter() { + db.note_non_null_account(address); + account.insert_additional(&mut AccountDBMut::new(db.as_hashdb_mut(), address), &factories.trie); + } + + // Execute contract constructors. + let env_info = EnvInfo { + number: 0, + author: self.author, + timestamp: self.timestamp, + difficulty: self.difficulty, + last_hashes: Default::default(), + gas_used: U256::zero(), + gas_limit: U256::max_value(), + }; + let from = Address::default(); + let start_nonce = self.engine.account_start_nonce(); + + let mut state = State::from_existing(db, root, start_nonce, factories.clone())?; + // Mutate the state with each constructor. + for &(ref address, ref constructor) in self.constructors.iter() { + trace!(target: "spec", "ensure_db_good: Creating a contract at {}.", address); + let params = ActionParams { + code_address: address.clone(), + code_hash: constructor.sha3(), + address: address.clone(), + sender: from.clone(), + origin: from.clone(), + gas: U256::max_value(), + gas_price: Default::default(), + value: ActionValue::Transfer(Default::default()), + code: Some(Arc::new(constructor.clone())), + data: None, + call_type: CallType::None, + }; + let mut substate = Substate::new(); { - let mut t = factory.create(db.as_hashdb_mut(), &mut root); - for (address, account) in self.genesis_state.get().iter() { - try!(t.insert(&**address, &account.rlp())); + let mut exec = Executive::new(&mut state, &env_info, self.engine.as_ref(), &factories.vm); + if let Err(e) = exec.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer) { + warn!(target: "spec", "Genesis constructor execution at {} failed: {}.", address, e); } } - trace!(target: "spec", "ensure_db_good: Populated sec trie; root is {}", root); - for (address, account) in self.genesis_state.get().iter() { - db.note_non_null_account(address); - account.insert_additional(&mut AccountDBMut::new(db.as_hashdb_mut(), address), factory); + if let Err(e) = state.commit() { + warn!(target: "spec", "Genesis constructor trie commit at {} failed: {}.", address, e); } - assert!(db.as_hashdb().contains(&self.state_root())); - Ok(true) - } else { Ok(false) } + } + let (root, db) = state.drop(); + + *self.state_root_memo.write() = Some(root); + Ok(db) } /// Loads spec from json file. @@ -270,6 +330,9 @@ impl Spec { /// Create a new Spec which is a NullEngine consensus with a premine of address whose secret is sha3(''). pub fn new_null() -> Spec { load_bundled!("null") } + /// Create a new Spec which constructs a contract at address 5 with storage at 0 equal to 1. + pub fn new_test_constructor() -> Spec { load_bundled!("constructor") } + /// Create a new Spec with InstantSeal consensus which does internal sealing (not requiring work). pub fn new_instant() -> Spec { load_bundled!("instant_seal") } @@ -284,10 +347,10 @@ impl Spec { #[cfg(test)] mod tests { - use std::str::FromStr; - use util::hash::*; - use util::sha3::*; + use util::*; use views::*; + use tests::helpers::get_temp_state_db; + use state::State; use super::*; // https://github.com/ethcore/parity/issues/1840 @@ -304,4 +367,14 @@ mod tests { let genesis = test_spec.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap()); } + + #[test] + fn genesis_constructor() { + let spec = Spec::new_test_constructor(); + let mut db_result = get_temp_state_db(); + let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); + let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(), Default::default()).unwrap(); + let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); + assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()), expected); + } } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index d778412c4..49cebd550 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -450,8 +450,8 @@ impl Account { let mut recorder = TrieRecorder::with_depth(from_level); - let trie = try!(TrieDB::new(db, &self.storage_root)); - let _ = try!(trie.get_recorded(&storage_key, &mut recorder)); + let trie = TrieDB::new(db, &self.storage_root)?; + let _ = trie.get_recorded(&storage_key, &mut recorder)?; Ok(recorder.drain().into_iter().map(|r| r.data).collect()) } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 76bb71645..c9730c1c3 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -515,11 +515,11 @@ impl State { let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true }; let vm_factory = self.factories.vm.clone(); - let e = try!(Executive::new(self, env_info, engine, &vm_factory).transact(t, options)); + let e = Executive::new(self, env_info, engine, &vm_factory).transact(t, options)?; // TODO uncomment once to_pod() works correctly. // trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod())); - try!(self.commit()); + self.commit()?; let receipt = Receipt::new(self.root().clone(), e.cumulative_gas_used, e.logs); trace!(target: "state", "Transaction receipt: {:?}", receipt); Ok(ApplyOutcome{receipt: receipt, trace: e.trace}) @@ -551,15 +551,15 @@ impl State { } { - let mut trie = try!(factories.trie.from_existing(db.as_hashdb_mut(), root)); + let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root)?; for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { a.state = AccountState::Committed; match a.account { Some(ref mut account) => { - try!(trie.insert(address, &account.rlp())); + trie.insert(address, &account.rlp())?; }, None => { - try!(trie.remove(address)); + trie.remove(address)?; }, } } @@ -771,8 +771,8 @@ impl State { /// `account_key` == sha3(address) pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result, Box> { let mut recorder = TrieRecorder::with_depth(from_level); - let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); - let _ = try!(trie.get_recorded(&account_key, &mut recorder)); + let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; + let _ = trie.get_recorded(&account_key, &mut recorder)?; Ok(recorder.drain().into_iter().map(|r| r.data).collect()) } @@ -785,8 +785,8 @@ impl State { pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result, Box> { // TODO: probably could look into cache somehow but it's keyed by // address, not sha3(address). - let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); - let acc = match try!(trie.get(&account_key)) { + let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; + let acc = match trie.get(&account_key)? { Some(rlp) => Account::from_rlp(&rlp), None => return Ok(Vec::new()), }; @@ -798,8 +798,8 @@ impl State { /// Get code by address hash. /// Only works when backed by a secure trie. pub fn code_by_address_hash(&self, account_key: H256) -> Result, Box> { - let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); - let mut acc = match try!(trie.get(&account_key)) { + let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; + let mut acc = match trie.get(&account_key)? { Some(rlp) => Account::from_rlp(&rlp), None => return Ok(None), }; diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index d38449f7a..967c23513 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -195,9 +195,9 @@ impl StateDB { pub fn journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result { { let mut bloom_lock = self.account_bloom.lock(); - try!(Self::commit_bloom(batch, bloom_lock.drain_journal())); + Self::commit_bloom(batch, bloom_lock.drain_journal())?; } - let records = try!(self.db.journal_under(batch, now, id)); + let records = self.db.journal_under(batch, now, id)?; self.commit_hash = Some(id.clone()); self.commit_number = Some(now); Ok(records) diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index fa5522d7f..0dfd8434d 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -24,7 +24,7 @@ use types::filter::Filter; use util::*; use devtools::*; use miner::Miner; -use rlp::{Rlp, View}; +use rlp::View; use spec::Spec; use views::BlockView; use util::stats::Histogram; @@ -103,7 +103,7 @@ fn imports_good_block() { client.import_verified_blocks(); let block = client.block_header(BlockId::Number(1)).unwrap(); - assert!(!block.is_empty()); + assert!(!block.into_inner().is_empty()); } #[test] @@ -128,7 +128,7 @@ fn query_none_block() { fn query_bad_block() { let client_result = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); let client = client_result.reference(); - let bad_block:Option = client.block_header(BlockId::Number(1)); + let bad_block: Option<_> = client.block_header(BlockId::Number(1)); assert!(bad_block.is_none()); } @@ -180,7 +180,7 @@ fn returns_block_body() { let client = client_result.reference(); let block = BlockView::new(&dummy_block); let body = client.block_body(BlockId::Hash(block.header().hash())).unwrap(); - let body = Rlp::new(&body); + let body = body.rlp(); assert_eq!(body.item_count(), 2); assert_eq!(body.at(0).as_raw()[..], block.rlp().at(1).as_raw()[..]); assert_eq!(body.at(1).as_raw()[..], block.rlp().at(2).as_raw()[..]); @@ -192,7 +192,7 @@ fn imports_block_sequence() { let client = client_result.reference(); let block = client.block_header(BlockId::Number(5)).unwrap(); - assert!(!block.is_empty()); + assert!(!block.into_inner().is_empty()); } #[test] @@ -320,4 +320,3 @@ fn does_not_propagate_delayed_transactions() { assert_eq!(2, client.ready_transactions().len()); assert_eq!(2, client.miner().pending_transactions().len()); } - diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 602217d88..d08261306 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -18,7 +18,6 @@ use ethkey::KeyPair; use io::*; use client::{BlockChainClient, Client, ClientConfig}; use util::*; -use util::trie::TrieSpec; use spec::*; use state_db::StateDB; use block::{OpenBlock, Drain}; @@ -157,8 +156,7 @@ pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_numbe let test_engine = &*test_spec.engine; let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - test_spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + let mut db = test_spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let genesis_header = test_spec.genesis_header(); let mut rolling_timestamp = 40; diff --git a/ethcore/src/trace/bloom.rs b/ethcore/src/trace/bloom.rs index 9196453fd..4e2bd4eca 100644 --- a/ethcore/src/trace/bloom.rs +++ b/ethcore/src/trace/bloom.rs @@ -73,7 +73,7 @@ impl Encodable for BlockTracesBloom { impl Decodable for BlockTracesBloomGroup { fn decode(decoder: &D) -> Result where D: Decoder { - let blooms = try!(Decodable::decode(decoder)); + let blooms = Decodable::decode(decoder)?; let group = BlockTracesBloomGroup { blooms: blooms }; diff --git a/ethcore/src/types/account_diff.rs b/ethcore/src/types/account_diff.rs index c8d5c9eec..53ab6775c 100644 --- a/ethcore/src/types/account_diff.rs +++ b/ethcore/src/types/account_diff.rs @@ -22,7 +22,8 @@ use std::collections::BTreeMap; use util::{U256, H256, Uint, Bytes}; use ipc::binary::BinaryConvertable; -#[derive(Debug, PartialEq, Eq, Clone, Binary)] +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "ipc", binary)] /// Diff type for specifying a change (or not). pub enum Diff where T: Eq + BinaryConvertable { /// Both sides are the same. @@ -49,7 +50,8 @@ impl Diff where T: Eq + BinaryConvertable { pub fn is_same(&self) -> bool { match *self { Diff::Same => true, _ => false }} } -#[derive(Debug, PartialEq, Eq, Clone, Binary)] +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "ipc", binary)] /// Account diff. pub struct AccountDiff { /// Change in balance, allowed to be `Diff::Same`. @@ -62,7 +64,8 @@ pub struct AccountDiff { pub storage: BTreeMap>, } -#[derive(Debug, PartialEq, Eq, Clone, Binary)] +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "ipc", binary)] /// Change in existance type. // TODO: include other types of change. pub enum Existance { @@ -77,9 +80,9 @@ pub enum Existance { impl fmt::Display for Existance { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Existance::Born => try!(write!(f, "+++")), - Existance::Alive => try!(write!(f, "***")), - Existance::Died => try!(write!(f, "XXX")), + Existance::Born => write!(f, "+++")?, + Existance::Alive => write!(f, "***")?, + Existance::Died => write!(f, "XXX")?, } Ok(()) } @@ -114,24 +117,24 @@ impl fmt::Display for AccountDiff { use util::bytes::ToPretty; match self.nonce { - Diff::Born(ref x) => try!(write!(f, " non {}", x)), - Diff::Changed(ref pre, ref post) => try!(write!(f, "#{} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - * min(pre, post))), + Diff::Born(ref x) => write!(f, " non {}", x)?, + Diff::Changed(ref pre, ref post) => write!(f, "#{} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - * min(pre, post))?, _ => {}, } match self.balance { - Diff::Born(ref x) => try!(write!(f, " bal {}", x)), - Diff::Changed(ref pre, ref post) => try!(write!(f, "${} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - *min(pre, post))), + Diff::Born(ref x) => write!(f, " bal {}", x)?, + Diff::Changed(ref pre, ref post) => write!(f, "${} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - *min(pre, post))?, _ => {}, } if let Diff::Born(ref x) = self.code { - try!(write!(f, " code {}", x.pretty())); + write!(f, " code {}", x.pretty())?; } - try!(write!(f, "\n")); + write!(f, "\n")?; for (k, dv) in &self.storage { match *dv { - Diff::Born(ref v) => try!(write!(f, " + {} => {}\n", interpreted_hash(k), interpreted_hash(v))), - Diff::Changed(ref pre, ref post) => try!(write!(f, " * {} => {} (was {})\n", interpreted_hash(k), interpreted_hash(post), interpreted_hash(pre))), - Diff::Died(_) => try!(write!(f, " X {}\n", interpreted_hash(k))), + Diff::Born(ref v) => write!(f, " + {} => {}\n", interpreted_hash(k), interpreted_hash(v))?, + Diff::Changed(ref pre, ref post) => write!(f, " * {} => {} (was {})\n", interpreted_hash(k), interpreted_hash(post), interpreted_hash(pre))?, + Diff::Died(_) => write!(f, " X {}\n", interpreted_hash(k))?, _ => {}, } } diff --git a/ethcore/src/types/block_import_error.rs b/ethcore/src/types/block_import_error.rs index e229db6d2..ae29dd5a5 100644 --- a/ethcore/src/types/block_import_error.rs +++ b/ethcore/src/types/block_import_error.rs @@ -20,7 +20,8 @@ use error::{ImportError, BlockError, Error}; use std::convert::From; /// Error dedicated to import block function -#[derive(Binary, Debug)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", binary)] pub enum BlockImportError { /// Import error Import(ImportError), diff --git a/ethcore/src/types/block_status.rs b/ethcore/src/types/block_status.rs index d1c6c7d5b..33ca3f352 100644 --- a/ethcore/src/types/block_status.rs +++ b/ethcore/src/types/block_status.rs @@ -18,7 +18,8 @@ use verification::queue::Status as QueueStatus; /// General block status -#[derive(Debug, Eq, PartialEq, Binary)] +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub enum BlockStatus { /// Part of the blockchain. InChain, @@ -38,4 +39,4 @@ impl From for BlockStatus { QueueStatus::Unknown => BlockStatus::Unknown, } } -} \ No newline at end of file +} diff --git a/ethcore/src/types/blockchain_info.rs b/ethcore/src/types/blockchain_info.rs index 33657ff0e..50887c0b7 100644 --- a/ethcore/src/types/blockchain_info.rs +++ b/ethcore/src/types/blockchain_info.rs @@ -21,7 +21,8 @@ use header::BlockNumber; use types::security_level::SecurityLevel; /// Information about the blockchain gathered together. -#[derive(Clone, Debug, Binary)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "ipc", binary)] pub struct BlockChainInfo { /// Blockchain difficulty. pub total_difficulty: U256, @@ -53,4 +54,4 @@ impl BlockChainInfo { SecurityLevel::PartialProofOfWork(self.best_block_number - self.first_block_number.expect("Guard condition means this is not none")) } } -} \ No newline at end of file +} diff --git a/ethcore/src/types/call_analytics.rs b/ethcore/src/types/call_analytics.rs index 9da2d11ae..5274adfac 100644 --- a/ethcore/src/types/call_analytics.rs +++ b/ethcore/src/types/call_analytics.rs @@ -17,7 +17,8 @@ //! Call analytics related types /// Options concerning what analytics we run on the call. -#[derive(Eq, PartialEq, Default, Clone, Copy, Debug, Binary)] +#[derive(Eq, PartialEq, Default, Clone, Copy, Debug)] +#[cfg_attr(feature = "ipc", binary)] pub struct CallAnalytics { /// Make a transaction trace. pub transaction_tracing: bool, diff --git a/ethcore/src/types/encoded.rs b/ethcore/src/types/encoded.rs new file mode 100644 index 000000000..41db96fcf --- /dev/null +++ b/ethcore/src/types/encoded.rs @@ -0,0 +1,273 @@ +// Copyright 2015, 2016 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 . + +//! Lazily-decoded owning views of RLP-encoded blockchain objects. +//! These views are meant to contain _trusted_ data -- without encoding +//! errors or inconsistencies. +//! +//! In general these views are useful when only a few fields of an object +//! are relevant. In these cases it's more efficient to decode the object piecemeal. +//! When the entirety of the object is needed, it's better to upgrade it to a fully +//! decoded object where parts like the hash can be saved. + +use block::Block as FullBlock; +use header::{BlockNumber, Header as FullHeader}; +use transaction::SignedTransaction; +use views; + +use util::{Address, Hashable, H256, H2048, U256}; +use rlp::{Rlp, View}; + +/// Owning header view. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct Header(Vec); + +impl Header { + /// Create a new owning header view. + /// Expects the data to be an RLP-encoded header -- any other case will likely lead to + /// panics further down the line. + pub fn new(encoded: Vec) -> Self { Header(encoded) } + + /// Upgrade this encoded view to a fully owned `Header` object. + pub fn decode(&self) -> FullHeader { ::rlp::decode(&self.0) } + + /// Get a borrowed header view onto the data. + #[inline] + pub fn view(&self) -> views::HeaderView { views::HeaderView::new(&self.0) } + + /// Get the rlp of the header. + #[inline] + pub fn rlp(&self) -> Rlp { Rlp::new(&self.0) } + + /// Consume the view and return the raw bytes. + pub fn into_inner(self) -> Vec { self.0 } +} + +// forwarders to borrowed view. +impl Header { + /// Returns the header hash. + pub fn hash(&self) -> H256 { self.sha3() } + + /// Returns the parent hash. + pub fn parent_hash(&self) -> H256 { self.view().parent_hash() } + + /// Returns the uncles hash. + pub fn uncles_hash(&self) -> H256 { self.view().uncles_hash() } + + /// Returns the author. + pub fn author(&self) -> Address { self.view().author() } + + /// Returns the state root. + pub fn state_root(&self) -> H256 { self.view().state_root() } + + /// Returns the transaction trie root. + pub fn transactions_root(&self) -> H256 { self.view().transactions_root() } + + /// Returns the receipts trie root + pub fn receipts_root(&self) -> H256 { self.view().receipts_root() } + + /// Returns the block log bloom + pub fn log_bloom(&self) -> H2048 { self.view().log_bloom() } + + /// Difficulty of this block + pub fn difficulty(&self) -> U256 { self.view().difficulty() } + + /// Number of this block. + pub fn number(&self) -> BlockNumber { self.view().number() } + + /// Time this block was produced. + pub fn timestamp(&self) -> u64 { self.view().timestamp() } + + /// Gas limit of this block. + pub fn gas_limit(&self) -> U256 { self.view().gas_limit() } + + /// Total gas used in this block. + pub fn gas_used(&self) -> U256 { self.view().gas_used() } + + /// Block extra data. + pub fn extra_data(&self) -> Vec { self.view().extra_data() } + + /// Engine-specific seal fields. + pub fn seal(&self) -> Vec> { self.view().seal() } +} + +impl Hashable for Header { + fn sha3(&self) -> H256 { + self.0.sha3() + } +} + +/// Owning block body view. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct Body(Vec); + +impl Body { + /// Create a new owning block body view. The raw bytes passed in must be an rlp-encoded block + /// body. + pub fn new(raw: Vec) -> Self { Body(raw) } + + /// Get a borrowed view of the data within. + #[inline] + pub fn view(&self) -> views::BodyView { views::BodyView::new(&self.0) } + + /// Fully decode this block body. + pub fn decode(&self) -> (Vec, Vec) { + (self.view().transactions(), self.view().uncles()) + } + + /// Get the RLP of this block body. + #[inline] + pub fn rlp(&self) -> Rlp { + Rlp::new(&self.0) + } + + /// Consume the view and return the raw bytes. + pub fn into_inner(self) -> Vec { self.0 } +} + +// forwarders to borrowed view. +impl Body { + /// Get a vector of all transactions. + pub fn transactions(&self) -> Vec { self.view().transactions() } + + /// Number of transactions in the block. + pub fn transactions_count(&self) -> usize { self.view().transactions_count() } + + /// A view over each transaction in the block. + pub fn transaction_views(&self) -> Vec { self.view().transaction_views() } + + /// The hash of each transaction in the block. + pub fn transaction_hashes(&self) -> Vec { self.view().transaction_hashes() } + + /// Decode uncle headers. + pub fn uncles(&self) -> Vec { self.view().uncles() } + + /// Number of uncles. + pub fn uncles_count(&self) -> usize { self.view().uncles_count() } + + /// Borrowed view over each uncle. + pub fn uncle_views(&self) -> Vec { self.view().uncle_views() } + + /// Hash of each uncle. + pub fn uncle_hashes(&self) -> Vec { self.view().uncle_hashes() } +} + +/// Owning block view. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct Block(Vec); + +impl Block { + /// Create a new owning block view. The raw bytes passed in must be an rlp-encoded block. + pub fn new(raw: Vec) -> Self { Block(raw) } + + /// Get a borrowed view of the whole block. + #[inline] + pub fn view(&self) -> views::BlockView { views::BlockView::new(&self.0) } + + /// Get a borrowed view of the block header. + #[inline] + pub fn header_view(&self) -> views::HeaderView { self.view().header_view() } + + /// Decode to a full block. + pub fn decode(&self) -> FullBlock { ::rlp::decode(&self.0) } + + /// Get the rlp of this block. + #[inline] + pub fn rlp(&self) -> Rlp { + Rlp::new(&self.0) + } + + /// Consume the view and return the raw bytes. + pub fn into_inner(self) -> Vec { self.0 } +} + +// forwarders to borrowed header view. +impl Block { + /// Returns the header hash. + pub fn hash(&self) -> H256 { self.header_view().sha3() } + + /// Returns the parent hash. + pub fn parent_hash(&self) -> H256 { self.header_view().parent_hash() } + + /// Returns the uncles hash. + pub fn uncles_hash(&self) -> H256 { self.header_view().uncles_hash() } + + /// Returns the author. + pub fn author(&self) -> Address { self.header_view().author() } + + /// Returns the state root. + pub fn state_root(&self) -> H256 { self.header_view().state_root() } + + /// Returns the transaction trie root. + pub fn transactions_root(&self) -> H256 { self.header_view().transactions_root() } + + /// Returns the receipts trie root + pub fn receipts_root(&self) -> H256 { self.header_view().receipts_root() } + + /// Returns the block log bloom + pub fn log_bloom(&self) -> H2048 { self.header_view().log_bloom() } + + /// Difficulty of this block + pub fn difficulty(&self) -> U256 { self.header_view().difficulty() } + + /// Number of this block. + pub fn number(&self) -> BlockNumber { self.header_view().number() } + + /// Time this block was produced. + pub fn timestamp(&self) -> u64 { self.header_view().timestamp() } + + /// Gas limit of this block. + pub fn gas_limit(&self) -> U256 { self.header_view().gas_limit() } + + /// Total gas used in this block. + pub fn gas_used(&self) -> U256 { self.header_view().gas_used() } + + /// Block extra data. + pub fn extra_data(&self) -> Vec { self.header_view().extra_data() } + + /// Engine-specific seal fields. + pub fn seal(&self) -> Vec> { self.header_view().seal() } +} + +// forwarders to body view. +impl Block { + /// Get a vector of all transactions. + pub fn transactions(&self) -> Vec { self.view().transactions() } + + /// Number of transactions in the block. + pub fn transactions_count(&self) -> usize { self.view().transactions_count() } + + /// A view over each transaction in the block. + pub fn transaction_views(&self) -> Vec { self.view().transaction_views() } + + /// The hash of each transaction in the block. + pub fn transaction_hashes(&self) -> Vec { self.view().transaction_hashes() } + + /// Decode uncle headers. + pub fn uncles(&self) -> Vec { self.view().uncles() } + + /// Number of uncles. + pub fn uncles_count(&self) -> usize { self.view().uncles_count() } + + /// Borrowed view over each uncle. + pub fn uncle_views(&self) -> Vec { self.view().uncle_views() } + + /// Hash of each uncle. + pub fn uncle_hashes(&self) -> Vec { self.view().uncle_hashes() } +} diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index 3ce8d2634..1f0ef33c7 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -24,7 +24,8 @@ use types::state_diff::StateDiff; use std::fmt; /// The type of the call-like instruction. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub enum CallType { /// Not a CALL. None, @@ -61,7 +62,8 @@ impl Decodable for CallType { } /// Transaction execution receipt. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct Executed { /// Gas paid up front for execution of transaction. pub gas: U256, @@ -101,7 +103,8 @@ pub struct Executed { } /// Result of executing the transaction. -#[derive(PartialEq, Debug, Clone, Binary)] +#[derive(PartialEq, Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub enum ExecutionError { /// Returned when there gas paid for transaction execution is /// lower than base gas required. @@ -168,7 +171,8 @@ impl fmt::Display for ExecutionError { } /// Result of executing the transaction. -#[derive(PartialEq, Debug, Clone, Binary)] +#[derive(PartialEq, Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub enum CallError { /// Couldn't find the transaction in the chain. TransactionNotFound, diff --git a/ethcore/src/types/filter.rs b/ethcore/src/types/filter.rs index 8aecef43b..f940423e3 100644 --- a/ethcore/src/types/filter.rs +++ b/ethcore/src/types/filter.rs @@ -22,7 +22,8 @@ use client::BlockId; use log_entry::LogEntry; /// Blockchain Filter. -#[derive(Binary, Debug, PartialEq)] +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct Filter { /// Blockchain will be searched from this block. pub from_block: BlockId, diff --git a/ethcore/src/types/ids.rs b/ethcore/src/types/ids.rs index 2828c4798..42b51c54e 100644 --- a/ethcore/src/types/ids.rs +++ b/ethcore/src/types/ids.rs @@ -20,7 +20,8 @@ use util::hash::H256; use header::BlockNumber; /// Uniquely identifies block. -#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Binary)] +#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub enum BlockId { /// Block's sha3. /// Querying by hash is always faster. @@ -36,7 +37,8 @@ pub enum BlockId { } /// Uniquely identifies transaction. -#[derive(Debug, PartialEq, Clone, Hash, Eq, Binary)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub enum TransactionId { /// Transaction's sha3. Hash(H256), @@ -46,7 +48,7 @@ pub enum TransactionId { } /// Uniquely identifies Trace. -#[derive(Binary)] +#[cfg_attr(feature = "ipc", binary)] pub struct TraceId { /// Transaction pub transaction: TransactionId, @@ -55,7 +57,8 @@ pub struct TraceId { } /// Uniquely identifies Uncle. -#[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct UncleId { /// Block id. pub block: BlockId, diff --git a/ethcore/src/types/log_entry.rs b/ethcore/src/types/log_entry.rs index 19531a9e9..79b93b04e 100644 --- a/ethcore/src/types/log_entry.rs +++ b/ethcore/src/types/log_entry.rs @@ -26,7 +26,8 @@ use header::BlockNumber; use ethjson; /// A record of execution for a `LOG` operation. -#[derive(Default, Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct LogEntry { /// The address of the contract executing at the point of the `LOG` operation. pub address: Address, @@ -49,9 +50,9 @@ impl Decodable for LogEntry { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let entry = LogEntry { - address: try!(d.val_at(0)), - topics: try!(d.val_at(1)), - data: try!(d.val_at(2)), + address: d.val_at(0)?, + topics: d.val_at(1)?, + data: d.val_at(2)?, }; Ok(entry) } @@ -81,7 +82,8 @@ impl From for LogEntry { } /// Log localized in a blockchain. -#[derive(Default, Debug, PartialEq, Clone, Binary)] +#[derive(Default, Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct LocalizedLogEntry { /// Plain log entry. pub entry: LogEntry, @@ -95,6 +97,8 @@ pub struct LocalizedLogEntry { pub transaction_index: usize, /// Log position in the block. pub log_index: usize, + /// Log position in the transaction. + pub transaction_log_index: usize, } impl Deref for LocalizedLogEntry { diff --git a/ethcore/src/types/mod.rs b/ethcore/src/types/mod.rs index 959ff694a..6d2072ce2 100644 --- a/ethcore/src/types/mod.rs +++ b/ethcore/src/types/mod.rs @@ -16,5 +16,10 @@ //! Types used in the public api -#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +#![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues + +#[cfg(feature = "ipc")] include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); + +#[cfg(not(feature = "ipc"))] +include!("mod.rs.in"); diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index a5a8fb4ef..567c6fff9 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -35,4 +35,5 @@ pub mod restoration_status; pub mod snapshot_manifest; pub mod mode; pub mod pruning_info; -pub mod security_level; \ No newline at end of file +pub mod security_level; +pub mod encoded; diff --git a/ethcore/src/types/mode.rs b/ethcore/src/types/mode.rs index a1df3c76f..95bcd0d2b 100644 --- a/ethcore/src/types/mode.rs +++ b/ethcore/src/types/mode.rs @@ -20,7 +20,8 @@ pub use std::time::Duration; use client::Mode as ClientMode; /// IPC-capable shadow-type for `client::config::Mode` -#[derive(Clone, Binary, Debug)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "ipc", binary)] pub enum Mode { /// Same as `ClientMode::Off`. Off, diff --git a/ethcore/src/types/pruning_info.rs b/ethcore/src/types/pruning_info.rs index fd5689d2d..443316865 100644 --- a/ethcore/src/types/pruning_info.rs +++ b/ethcore/src/types/pruning_info.rs @@ -21,10 +21,13 @@ //! of which portions of the ancient chain and current state trie are stored as well. /// Client pruning info. See module-level docs for more details. -#[derive(Debug, Clone, Binary)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct PruningInfo { /// The first block which everything can be served after. pub earliest_chain: u64, /// The first block where state requests may be served. pub earliest_state: u64, -} \ No newline at end of file + /// State pruning history size. + pub state_history_size: Option, +} diff --git a/ethcore/src/types/receipt.rs b/ethcore/src/types/receipt.rs index c15f039bc..4e28c55cc 100644 --- a/ethcore/src/types/receipt.rs +++ b/ethcore/src/types/receipt.rs @@ -25,7 +25,8 @@ use header::BlockNumber; use log_entry::{LogEntry, LocalizedLogEntry}; /// Information describing execution of a transaction. -#[derive(Default, Debug, Clone, Binary)] +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct Receipt { /// The state root after executing the transaction. pub state_root: H256, @@ -63,10 +64,10 @@ impl Decodable for Receipt { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let receipt = Receipt { - state_root: try!(d.val_at(0)), - gas_used: try!(d.val_at(1)), - log_bloom: try!(d.val_at(2)), - logs: try!(d.val_at(3)), + state_root: d.val_at(0)?, + gas_used: d.val_at(1)?, + log_bloom: d.val_at(2)?, + logs: d.val_at(3)?, }; Ok(receipt) } @@ -79,7 +80,8 @@ impl HeapSizeOf for Receipt { } /// Receipt with additional info. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct RichReceipt { /// Transaction hash. pub transaction_hash: H256, @@ -100,7 +102,8 @@ pub struct RichReceipt { } /// Receipt with additional info. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct LocalizedReceipt { /// Transaction hash. pub transaction_hash: H256, diff --git a/ethcore/src/types/restoration_status.rs b/ethcore/src/types/restoration_status.rs index 507659d5b..065946ea2 100644 --- a/ethcore/src/types/restoration_status.rs +++ b/ethcore/src/types/restoration_status.rs @@ -17,7 +17,8 @@ //! Restoration status type definition /// Statuses for restorations. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Binary)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "ipc", binary)] pub enum RestorationStatus { /// No restoration. Inactive, diff --git a/ethcore/src/types/security_level.rs b/ethcore/src/types/security_level.rs index 0415b9bb1..48d252b28 100644 --- a/ethcore/src/types/security_level.rs +++ b/ethcore/src/types/security_level.rs @@ -19,7 +19,8 @@ use header::BlockNumber; /// Indication of how secure the chain is. -#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Binary)] +#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub enum SecurityLevel { /// All blocks from genesis to chain head are known to have valid state transitions and PoW. FullState, @@ -35,6 +36,6 @@ impl SecurityLevel { match *self { SecurityLevel::FullState | SecurityLevel::FullProofOfWork => true, _ => false, - } + } } -} \ No newline at end of file +} diff --git a/ethcore/src/types/snapshot_manifest.rs b/ethcore/src/types/snapshot_manifest.rs index dac164b0b..f3f189aed 100644 --- a/ethcore/src/types/snapshot_manifest.rs +++ b/ethcore/src/types/snapshot_manifest.rs @@ -21,7 +21,8 @@ use rlp::*; use util::Bytes; /// Manifest data. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct ManifestData { /// List of state chunk hashes. pub state_hashes: Vec, @@ -52,11 +53,11 @@ impl ManifestData { pub fn from_rlp(raw: &[u8]) -> Result { let decoder = UntrustedRlp::new(raw); - let state_hashes: Vec = try!(decoder.val_at(0)); - let block_hashes: Vec = try!(decoder.val_at(1)); - let state_root: H256 = try!(decoder.val_at(2)); - let block_number: u64 = try!(decoder.val_at(3)); - let block_hash: H256 = try!(decoder.val_at(4)); + let state_hashes: Vec = decoder.val_at(0)?; + let block_hashes: Vec = decoder.val_at(1)?; + let state_root: H256 = decoder.val_at(2)?; + let block_number: u64 = decoder.val_at(3)?; + let block_hash: H256 = decoder.val_at(4)?; Ok(ManifestData { state_hashes: state_hashes, diff --git a/ethcore/src/types/state_diff.rs b/ethcore/src/types/state_diff.rs index 6b8242db6..ad0b8c88e 100644 --- a/ethcore/src/types/state_diff.rs +++ b/ethcore/src/types/state_diff.rs @@ -22,7 +22,8 @@ use std::collections::BTreeMap; use util::Address; use account_diff::*; -#[derive(Debug, PartialEq, Eq, Clone, Binary)] +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "ipc", binary)] /// Expression for the delta between two system states. Encoded the /// delta of every altered account. pub struct StateDiff { @@ -40,7 +41,7 @@ impl StateDiff { impl fmt::Display for StateDiff { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (add, acc) in &self.raw { - try!(write!(f, "{} {}: {}", acc.existance(), add, acc)); + write!(f, "{} {}: {}", acc.existance(), add, acc)?; } Ok(()) } diff --git a/ethcore/src/types/trace_filter.rs b/ethcore/src/types/trace_filter.rs index 5a7ed8429..4085c5bd0 100644 --- a/ethcore/src/types/trace_filter.rs +++ b/ethcore/src/types/trace_filter.rs @@ -21,7 +21,7 @@ use util::{Address}; use types::ids::BlockId; /// Easy to use trace filter. -#[derive(Binary)] +#[cfg_attr(feature = "ipc", binary)] pub struct Filter { /// Range of filtering. pub range: Range, diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 72b873fc9..9b4ee900b 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -21,7 +21,8 @@ use rlp::{Encodable, RlpStream, Decodable, Decoder, DecoderError, Stream, View}; use evm::Error as EvmError; /// Trace evm errors. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub enum Error { /// `OutOfGas` is returned when transaction execution runs out of gas. OutOfGas, @@ -85,7 +86,7 @@ impl Encodable for Error { impl Decodable for Error { fn decode(decoder: &D) -> Result where D: Decoder { use self::Error::*; - let value: u8 = try!(decoder.as_rlp().as_val()); + let value: u8 = decoder.as_rlp().as_val()?; match value { 0 => Ok(OutOfGas), 1 => Ok(BadJumpDestination), diff --git a/ethcore/src/types/trace_types/filter.rs b/ethcore/src/types/trace_types/filter.rs index b12112acd..282aeab9b 100644 --- a/ethcore/src/types/trace_types/filter.rs +++ b/ethcore/src/types/trace_types/filter.rs @@ -28,7 +28,8 @@ use types::trace_types::trace::{Action, Res}; /// Addresses filter. /// /// Used to create bloom possibilities and match filters. -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", binary)] pub struct AddressesFilter { list: Vec
} @@ -74,7 +75,8 @@ impl AddressesFilter { } } -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", binary)] /// Traces filter. pub struct Filter { /// Block range. diff --git a/ethcore/src/types/trace_types/flat.rs b/ethcore/src/types/trace_types/flat.rs index 8a949c210..78701eadb 100644 --- a/ethcore/src/types/trace_types/flat.rs +++ b/ethcore/src/types/trace_types/flat.rs @@ -25,7 +25,8 @@ use super::trace::{Action, Res}; /// Trace localized in vector of traces produced by a single transaction. /// /// Parent and children indexes refer to positions in this vector. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct FlatTrace { /// Type of action performed by a transaction. pub action: Action, @@ -65,11 +66,11 @@ impl Encodable for FlatTrace { impl Decodable for FlatTrace { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); - let v: Vec = try!(d.val_at(3)); + let v: Vec = d.val_at(3)?; let res = FlatTrace { - action: try!(d.val_at(0)), - result: try!(d.val_at(1)), - subtraces: try!(d.val_at(2)), + action: d.val_at(0)?, + result: d.val_at(1)?, + subtraces: d.val_at(2)?, trace_address: v.into_iter().collect(), }; @@ -108,7 +109,7 @@ impl Encodable for FlatTransactionTraces { impl Decodable for FlatTransactionTraces { fn decode(decoder: &D) -> Result where D: Decoder { - Ok(FlatTransactionTraces(try!(Decodable::decode(decoder)))) + Ok(FlatTransactionTraces(Decodable::decode(decoder)?)) } } @@ -149,7 +150,7 @@ impl Encodable for FlatBlockTraces { impl Decodable for FlatBlockTraces { fn decode(decoder: &D) -> Result where D: Decoder { - Ok(FlatBlockTraces(try!(Decodable::decode(decoder)))) + Ok(FlatBlockTraces(Decodable::decode(decoder)?)) } } diff --git a/ethcore/src/types/trace_types/localized.rs b/ethcore/src/types/trace_types/localized.rs index f65c47415..1b8cbd1b8 100644 --- a/ethcore/src/types/trace_types/localized.rs +++ b/ethcore/src/types/trace_types/localized.rs @@ -21,7 +21,8 @@ use super::trace::{Action, Res}; use header::BlockNumber; /// Localized trace. -#[derive(Debug, PartialEq, Clone, Binary)] +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub struct LocalizedTrace { /// Type of action performed by a transaction. pub action: Action, diff --git a/ethcore/src/types/trace_types/trace.rs b/ethcore/src/types/trace_types/trace.rs index 7f5149905..2636caa52 100644 --- a/ethcore/src/types/trace_types/trace.rs +++ b/ethcore/src/types/trace_types/trace.rs @@ -27,7 +27,8 @@ use types::executed::CallType; use super::error::Error; /// `Call` result. -#[derive(Debug, Clone, PartialEq, Default, Binary)] +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "ipc", binary)] pub struct CallResult { /// Gas used by call. pub gas_used: U256, @@ -47,8 +48,8 @@ impl Decodable for CallResult { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = CallResult { - gas_used: try!(d.val_at(0)), - output: try!(d.val_at(1)), + gas_used: d.val_at(0)?, + output: d.val_at(1)?, }; Ok(res) @@ -56,7 +57,8 @@ impl Decodable for CallResult { } /// `Create` result. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct CreateResult { /// Gas used by create. pub gas_used: U256, @@ -79,9 +81,9 @@ impl Decodable for CreateResult { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = CreateResult { - gas_used: try!(d.val_at(0)), - code: try!(d.val_at(1)), - address: try!(d.val_at(2)), + gas_used: d.val_at(0)?, + code: d.val_at(1)?, + address: d.val_at(2)?, }; Ok(res) @@ -96,7 +98,8 @@ impl CreateResult { } /// Description of a _call_ action, either a `CALL` operation or a message transction. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct Call { /// The sending account. pub from: Address, @@ -141,12 +144,12 @@ impl Decodable for Call { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = Call { - from: try!(d.val_at(0)), - to: try!(d.val_at(1)), - value: try!(d.val_at(2)), - gas: try!(d.val_at(3)), - input: try!(d.val_at(4)), - call_type: try!(d.val_at(5)), + from: d.val_at(0)?, + to: d.val_at(1)?, + value: d.val_at(2)?, + gas: d.val_at(3)?, + input: d.val_at(4)?, + call_type: d.val_at(5)?, }; Ok(res) @@ -163,7 +166,8 @@ impl Call { } /// Description of a _create_ action, either a `CREATE` operation or a create transction. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct Create { /// The address of the creator. pub from: Address, @@ -200,10 +204,10 @@ impl Decodable for Create { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = Create { - from: try!(d.val_at(0)), - value: try!(d.val_at(1)), - gas: try!(d.val_at(2)), - init: try!(d.val_at(3)), + from: d.val_at(0)?, + value: d.val_at(1)?, + gas: d.val_at(2)?, + init: d.val_at(3)?, }; Ok(res) @@ -219,7 +223,8 @@ impl Create { } /// Suicide action. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub struct Suicide { /// Suicided address. pub address: Address, @@ -250,9 +255,9 @@ impl Decodable for Suicide { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = Suicide { - address: try!(d.val_at(0)), - refund_address: try!(d.val_at(1)), - balance: try!(d.val_at(2)), + address: d.val_at(0)?, + refund_address: d.val_at(1)?, + balance: d.val_at(2)?, }; Ok(res) @@ -261,7 +266,8 @@ impl Decodable for Suicide { /// Description of an action that we trace; will be either a call or a create. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub enum Action { /// It's a call action. Call(Call), @@ -294,7 +300,7 @@ impl Encodable for Action { impl Decodable for Action { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); - let action_type: u8 = try!(d.val_at(0)); + let action_type: u8 = d.val_at(0)?; match action_type { 0 => d.val_at(1).map(Action::Call), 1 => d.val_at(1).map(Action::Create), @@ -316,7 +322,8 @@ impl Action { } /// The result of the performed action. -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] pub enum Res { /// Successful call action result. Call(CallResult), @@ -364,7 +371,7 @@ impl Encodable for Res { impl Decodable for Res { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); - let action_type: u8 = try!(d.val_at(0)); + let action_type: u8 = d.val_at(0)?; match action_type { 0 => d.val_at(1).map(Res::Call), 1 => d.val_at(1).map(Res::Create), @@ -386,7 +393,8 @@ impl Res { } } -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] /// A diff of some chunk of memory. pub struct MemoryDiff { /// Offset into memory the change begins. @@ -407,13 +415,14 @@ impl Decodable for MemoryDiff { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); Ok(MemoryDiff { - offset: try!(d.val_at(0)), - data: try!(d.val_at(1)), + offset: d.val_at(0)?, + data: d.val_at(1)?, }) } } -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] /// A diff of some storage value. pub struct StorageDiff { /// Which key in storage is changed. @@ -434,13 +443,14 @@ impl Decodable for StorageDiff { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); Ok(StorageDiff { - location: try!(d.val_at(0)), - value: try!(d.val_at(1)), + location: d.val_at(0)?, + value: d.val_at(1)?, }) } } -#[derive(Debug, Clone, PartialEq, Binary)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] /// A record of an executed VM operation. pub struct VMExecutedOperation { /// The total gas used. @@ -467,15 +477,16 @@ impl Decodable for VMExecutedOperation { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); Ok(VMExecutedOperation { - gas_used: try!(d.val_at(0)), - stack_push: try!(d.val_at(1)), - mem_diff: try!(d.val_at(2)), - store_diff: try!(d.val_at(3)), + gas_used: d.val_at(0)?, + stack_push: d.val_at(1)?, + mem_diff: d.val_at(2)?, + store_diff: d.val_at(3)?, }) } } -#[derive(Debug, Clone, PartialEq, Binary, Default)] +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "ipc", binary)] /// A record of the execution of a single VM operation. pub struct VMOperation { /// The program counter. @@ -502,17 +513,18 @@ impl Decodable for VMOperation { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = VMOperation { - pc: try!(d.val_at(0)), - instruction: try!(d.val_at(1)), - gas_cost: try!(d.val_at(2)), - executed: try!(d.val_at(3)), + pc: d.val_at(0)?, + instruction: d.val_at(1)?, + gas_cost: d.val_at(2)?, + executed: d.val_at(3)?, }; Ok(res) } } -#[derive(Debug, Clone, PartialEq, Binary, Default)] +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "ipc", binary)] /// A record of a full VM trace for a CALL/CREATE. pub struct VMTrace { /// The step (i.e. index into operations) at which this trace corresponds. @@ -540,10 +552,10 @@ impl Decodable for VMTrace { fn decode(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); let res = VMTrace { - parent_step: try!(d.val_at(0)), - code: try!(d.val_at(1)), - operations: try!(d.val_at(2)), - subs: try!(d.val_at(3)), + parent_step: d.val_at(0)?, + code: d.val_at(1)?, + operations: d.val_at(2)?, + subs: d.val_at(3)?, }; Ok(res) diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 2cd001bdb..4bd34da18 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -27,7 +27,8 @@ use evm::Schedule; use header::BlockNumber; use ethjson; -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] /// Transaction action type. pub enum Action { /// Create creates new contract. @@ -47,14 +48,15 @@ impl Decodable for Action { if rlp.is_empty() { Ok(Action::Create) } else { - Ok(Action::Call(try!(rlp.as_val()))) + Ok(Action::Call(rlp.as_val()?)) } } } /// A set of information describing an externally-originating message call /// or contract creation operation. -#[derive(Default, Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct Transaction { /// Nonce. pub nonce: U256, @@ -205,7 +207,8 @@ impl Transaction { } /// Signed transaction information. -#[derive(Debug, Clone, Eq, Binary)] +#[derive(Debug, Clone, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct SignedTransaction { /// Plain Transaction. unsigned: Transaction, @@ -244,16 +247,16 @@ impl Decodable for SignedTransaction { } Ok(SignedTransaction { unsigned: Transaction { - nonce: try!(d.val_at(0)), - gas_price: try!(d.val_at(1)), - gas: try!(d.val_at(2)), - action: try!(d.val_at(3)), - value: try!(d.val_at(4)), - data: try!(d.val_at(5)), + nonce: d.val_at(0)?, + gas_price: d.val_at(1)?, + gas: d.val_at(2)?, + action: d.val_at(3)?, + value: d.val_at(4)?, + data: d.val_at(5)?, }, - v: try!(d.val_at(6)), - r: try!(d.val_at(7)), - s: try!(d.val_at(8)), + v: d.val_at(6)?, + r: d.val_at(7)?, + s: d.val_at(8)?, hash: Cell::new(None), sender: Cell::new(None), }) @@ -335,7 +338,7 @@ impl SignedTransaction { match sender { Some(s) => Ok(s), None => { - let s = public_to_address(&try!(self.public_key())); + let s = public_to_address(&self.public_key()?); self.sender.set(Some(s)); Ok(s) } @@ -344,7 +347,7 @@ impl SignedTransaction { /// Returns the public key of the sender. pub fn public_key(&self) -> Result { - Ok(try!(recover(&self.signature(), &self.unsigned.hash(self.network_id())))) + Ok(recover(&self.signature(), &self.unsigned.hash(self.network_id()))?) } /// Do basic validation, checking for valid signature and minimum gas, @@ -360,7 +363,7 @@ impl SignedTransaction { Some(1) if allow_network_id_of_one => {}, _ => return Err(TransactionError::InvalidNetworkId.into()), } - try!(self.sender()); + self.sender()?; if self.gas < U256::from(self.gas_required(&schedule)) { Err(TransactionError::InvalidGasLimit(::util::OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas}).into()) } else { @@ -370,7 +373,8 @@ impl SignedTransaction { } /// Signed Transaction that is a part of canon blockchain. -#[derive(Debug, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct LocalizedTransaction { /// Signed part. pub signed: SignedTransaction, @@ -391,7 +395,8 @@ impl Deref for LocalizedTransaction { } /// Queued transaction with additional information. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] pub struct PendingTransaction { /// Signed transaction data. pub transaction: SignedTransaction, @@ -495,14 +500,14 @@ fn should_agree_with_vitalik() { flushln!("networkid: {:?}", signed.network_id()); }; - test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce") - test_vector("f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112") - test_vector("f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be") - test_vector("f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0") - test_vector("f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554") - test_vector("f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4") - test_vector("f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35") - test_vector("f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332") - test_vector("f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029") - test_vector("f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f") + test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce"); + test_vector("f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112"); + test_vector("f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be"); + test_vector("f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0"); + test_vector("f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554"); + test_vector("f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4"); + test_vector("f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35"); + test_vector("f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332"); + test_vector("f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029"); + test_vector("f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f"); } diff --git a/ethcore/src/types/transaction_import.rs b/ethcore/src/types/transaction_import.rs index cfd3d5243..9be235fb0 100644 --- a/ethcore/src/types/transaction_import.rs +++ b/ethcore/src/types/transaction_import.rs @@ -31,7 +31,8 @@ pub enum TransactionImportResult { binary_fixed_size!(TransactionImportResult); /// Api-level error for transaction import -#[derive(Debug, Clone, Binary)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "ipc", binary)] pub enum TransactionImportError { /// Transaction error Transaction(TransactionError), diff --git a/ethcore/src/types/tree_route.rs b/ethcore/src/types/tree_route.rs index 7a97cac03..ff7362ad2 100644 --- a/ethcore/src/types/tree_route.rs +++ b/ethcore/src/types/tree_route.rs @@ -19,7 +19,8 @@ use util::H256; /// Represents a tree route between `from` block and `to` block: -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", binary)] pub struct TreeRoute { /// A vector of hashes of all blocks, ordered from `from` to `to`. pub blocks: Vec, diff --git a/ethcore/src/types/verification_queue_info.rs b/ethcore/src/types/verification_queue_info.rs index 3361155df..aba7785d8 100644 --- a/ethcore/src/types/verification_queue_info.rs +++ b/ethcore/src/types/verification_queue_info.rs @@ -17,7 +17,8 @@ //! Verification queue info types /// Verification queue status -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", binary)] pub struct VerificationQueueInfo { /// Number of queued items pending verification pub unverified_queue_size: usize, @@ -50,4 +51,4 @@ impl VerificationQueueInfo { pub fn is_empty(&self) -> bool { self.unverified_queue_size + self.verified_queue_size + self.verifying_queue_size == 0 } -} \ No newline at end of file +} diff --git a/ethcore/src/verification/queue/kind.rs b/ethcore/src/verification/queue/kind.rs index 5d4bb7451..3ce86ad47 100644 --- a/ethcore/src/verification/queue/kind.rs +++ b/ethcore/src/verification/queue/kind.rs @@ -19,18 +19,21 @@ use engines::Engine; use error::Error; -use util::{HeapSizeOf, H256}; +use util::{HeapSizeOf, H256, U256}; pub use self::blocks::Blocks; pub use self::headers::Headers; /// Something which can produce a hash and a parent hash. -pub trait HasHash { +pub trait BlockLike { /// Get the hash of this item. fn hash(&self) -> H256; /// Get the hash of this item's parent. fn parent_hash(&self) -> H256; + + /// Get the difficulty of this item. + fn difficulty(&self) -> U256; } /// Defines transitions between stages of verification. @@ -45,13 +48,13 @@ pub trait HasHash { /// consistent. pub trait Kind: 'static + Sized + Send + Sync { /// The first stage: completely unverified. - type Input: Sized + Send + HasHash + HeapSizeOf; + type Input: Sized + Send + BlockLike + HeapSizeOf; /// The second stage: partially verified. - type Unverified: Sized + Send + HasHash + HeapSizeOf; + type Unverified: Sized + Send + BlockLike + HeapSizeOf; /// The third stage: completely verified. - type Verified: Sized + Send + HasHash + HeapSizeOf; + type Verified: Sized + Send + BlockLike + HeapSizeOf; /// Attempt to create the `Unverified` item from the input. fn create(input: Self::Input, engine: &Engine) -> Result; @@ -62,14 +65,14 @@ pub trait Kind: 'static + Sized + Send + Sync { /// The blocks verification module. pub mod blocks { - use super::{Kind, HasHash}; + use super::{Kind, BlockLike}; use engines::Engine; use error::Error; use header::Header; use verification::{PreverifiedBlock, verify_block_basic, verify_block_unordered}; - use util::{Bytes, HeapSizeOf, H256}; + use util::{Bytes, HeapSizeOf, H256, U256}; /// A mode for verifying blocks. pub struct Blocks; @@ -126,7 +129,7 @@ pub mod blocks { } } - impl HasHash for Unverified { + impl BlockLike for Unverified { fn hash(&self) -> H256 { self.header.hash() } @@ -134,9 +137,13 @@ pub mod blocks { fn parent_hash(&self) -> H256 { self.header.parent_hash().clone() } + + fn difficulty(&self) -> U256 { + self.header.difficulty().clone() + } } - impl HasHash for PreverifiedBlock { + impl BlockLike for PreverifiedBlock { fn hash(&self) -> H256 { self.header.hash() } @@ -144,12 +151,16 @@ pub mod blocks { fn parent_hash(&self) -> H256 { self.header.parent_hash().clone() } + + fn difficulty(&self) -> U256 { + self.header.difficulty().clone() + } } } /// Verification for headers. pub mod headers { - use super::{Kind, HasHash}; + use super::{Kind, BlockLike}; use engines::Engine; use error::Error; @@ -157,10 +168,12 @@ pub mod headers { use verification::verify_header_params; use util::hash::H256; + use util::U256; - impl HasHash for Header { + impl BlockLike for Header { fn hash(&self) -> H256 { self.hash() } fn parent_hash(&self) -> H256 { self.parent_hash().clone() } + fn difficulty(&self) -> U256 { self.difficulty().clone() } } /// A mode for verifying headers. diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 673275102..774370b4d 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -26,7 +26,7 @@ use error::*; use engines::Engine; use service::*; -use self::kind::{HasHash, Kind}; +use self::kind::{BlockLike, Kind}; pub use types::verification_queue_info::VerificationQueueInfo as QueueInfo; @@ -132,13 +132,14 @@ pub struct VerificationQueue { deleting: Arc, ready_signal: Arc, empty: Arc, - processing: RwLock>, + processing: RwLock>, // hash to difficulty ticks_since_adjustment: AtomicUsize, max_queue_size: usize, max_mem_use: usize, scale_verifiers: bool, verifier_handles: Vec>, state: Arc<(Mutex, Condvar)>, + total_difficulty: RwLock, } struct QueueSignal { @@ -269,7 +270,7 @@ impl VerificationQueue { more_to_verify: more_to_verify, verification: verification, deleting: deleting, - processing: RwLock::new(HashSet::new()), + processing: RwLock::new(HashMap::new()), empty: empty, ticks_since_adjustment: AtomicUsize::new(0), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), @@ -277,6 +278,7 @@ impl VerificationQueue { scale_verifiers: scale_verifiers, verifier_handles: verifier_handles, state: state, + total_difficulty: RwLock::new(0.into()), } } @@ -434,6 +436,7 @@ impl VerificationQueue { sizes.unverified.store(0, AtomicOrdering::Release); sizes.verifying.store(0, AtomicOrdering::Release); sizes.verified.store(0, AtomicOrdering::Release); + *self.total_difficulty.write() = 0.into(); self.processing.write().clear(); } @@ -448,7 +451,7 @@ impl VerificationQueue { /// Check if the item is currently in the queue pub fn status(&self, hash: &H256) -> Status { - if self.processing.read().contains(hash) { + if self.processing.read().contains_key(hash) { return Status::Queued; } if self.verification.bad.lock().contains(hash) { @@ -461,7 +464,7 @@ impl VerificationQueue { pub fn import(&self, input: K::Input) -> ImportResult { let h = input.hash(); { - if self.processing.read().contains(&h) { + if self.processing.read().contains_key(&h) { return Err(ImportError::AlreadyQueued.into()); } @@ -480,7 +483,11 @@ impl VerificationQueue { Ok(item) => { self.verification.sizes.unverified.fetch_add(item.heap_size_of_children(), AtomicOrdering::SeqCst); - self.processing.write().insert(h.clone()); + self.processing.write().insert(h.clone(), item.difficulty()); + { + let mut td = self.total_difficulty.write(); + *td = *td + item.difficulty(); + } self.verification.unverified.lock().push_back(item); self.more_to_verify.notify_all(); Ok(h) @@ -511,7 +518,10 @@ impl VerificationQueue { bad.reserve(hashes.len()); for hash in hashes { bad.insert(hash.clone()); - processing.remove(hash); + if let Some(difficulty) = processing.remove(hash) { + let mut td = self.total_difficulty.write(); + *td = *td - difficulty; + } } let mut new_verified = VecDeque::new(); @@ -520,7 +530,10 @@ impl VerificationQueue { if bad.contains(&output.parent_hash()) { removed_size += output.heap_size_of_children(); bad.insert(output.hash()); - processing.remove(&output.hash()); + if let Some(difficulty) = processing.remove(&output.hash()) { + let mut td = self.total_difficulty.write(); + *td = *td - difficulty; + } } else { new_verified.push_back(output); } @@ -538,7 +551,10 @@ impl VerificationQueue { } let mut processing = self.processing.write(); for hash in hashes { - processing.remove(hash); + if let Some(difficulty) = processing.remove(hash) { + let mut td = self.total_difficulty.write(); + *td = *td - difficulty; + } } processing.is_empty() } @@ -592,6 +608,11 @@ impl VerificationQueue { } } + /// Get the total difficulty of all the blocks in the queue. + pub fn total_difficulty(&self) -> U256 { + self.total_difficulty.read().clone() + } + /// Get the current number of working verifiers. pub fn num_verifiers(&self) -> usize { match *self.state.0.lock() { @@ -760,6 +781,22 @@ mod tests { } } + #[test] + fn returns_total_difficulty() { + let queue = get_test_queue(false); + let block = get_good_dummy_block(); + let hash = BlockView::new(&block).header().hash().clone(); + if let Err(e) = queue.import(Unverified::new(block)) { + panic!("error importing block that is valid by definition({:?})", e); + } + queue.flush(); + assert_eq!(queue.total_difficulty(), 131072.into()); + queue.drain(10); + assert_eq!(queue.total_difficulty(), 131072.into()); + queue.mark_as_good(&[ hash ]); + assert_eq!(queue.total_difficulty(), 0.into()); + } + #[test] fn returns_ok_for_drained_duplicates() { let queue = get_test_queue(false); diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 661c40c96..2ff4c8963 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -51,19 +51,19 @@ impl HeapSizeOf for PreverifiedBlock { /// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> { - try!(verify_header_params(&header, engine)); - try!(verify_block_integrity(bytes, &header.transactions_root(), &header.uncles_hash())); - try!(engine.verify_block_basic(&header, Some(bytes))); - for u in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { - let u = try!(u); - try!(verify_header_params(&u, engine)); - try!(engine.verify_block_basic(&u, None)); + verify_header_params(&header, engine)?; + verify_block_integrity(bytes, &header.transactions_root(), &header.uncles_hash())?; + engine.verify_block_basic(&header, Some(bytes))?; + for u in UntrustedRlp::new(bytes).at(2)?.iter().map(|rlp| rlp.as_val::
()) { + let u = u?; + verify_header_params(&u, engine)?; + engine.verify_block_basic(&u, None)?; } // Verify transactions. // TODO: either use transaction views or cache the decoded transactions. let v = BlockView::new(bytes); for t in v.transactions() { - try!(engine.verify_transaction_basic(&t, &header)); + engine.verify_transaction_basic(&t, &header)?; } Ok(()) } @@ -73,9 +73,9 @@ pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Res /// Returns a `PreverifiedBlock` structure populated with transactions pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, check_seal: bool) -> Result { if check_seal { - try!(engine.verify_block_unordered(&header, Some(&bytes))); - for u in try!(UntrustedRlp::new(&bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { - try!(engine.verify_block_unordered(&try!(u), None)); + engine.verify_block_unordered(&header, Some(&bytes))?; + for u in UntrustedRlp::new(&bytes).at(2)?.iter().map(|rlp| rlp.as_val::
()) { + engine.verify_block_unordered(&u?, None)?; } } // Verify transactions. @@ -83,7 +83,7 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che { let v = BlockView::new(&bytes); for t in v.transactions() { - try!(engine.verify_transaction(&t, &header)); + engine.verify_transaction(&t, &header)?; transactions.push(t); } } @@ -97,11 +97,11 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che /// Phase 3 verification. Check block information against parent and uncles. pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error> { // TODO: verify timestamp - let parent = try!(bc.block_header(&header.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownParent(header.parent_hash().clone())))); - try!(verify_parent(&header, &parent)); - try!(engine.verify_block_family(&header, &parent, Some(bytes))); + let parent = bc.block_header(&header.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownParent(header.parent_hash().clone())))?; + verify_parent(&header, &parent)?; + engine.verify_block_family(&header, &parent, Some(bytes))?; - let num_uncles = try!(UntrustedRlp::new(bytes).at(2)).item_count(); + let num_uncles = UntrustedRlp::new(bytes).at(2)?.item_count(); if num_uncles != 0 { if num_uncles > engine.maximum_uncle_count() { return Err(From::from(BlockError::TooManyUncles(OutOfBounds { min: None, max: Some(engine.maximum_uncle_count()), found: num_uncles }))); @@ -117,15 +117,15 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & excluded.insert(details.parent.clone()); let b = bc.block(&hash) .expect("parent already known to be stored; qed"); - excluded.extend(BlockView::new(&b).uncle_hashes()); + excluded.extend(b.uncle_hashes()); hash = details.parent; } None => break } } - for uncle in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { - let uncle = try!(uncle); + for uncle in UntrustedRlp::new(bytes).at(2)?.iter().map(|rlp| rlp.as_val::
()) { + let uncle = uncle?; if excluded.contains(&uncle.hash()) { return Err(From::from(BlockError::UncleInChain(uncle.hash()))) } @@ -157,7 +157,7 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & // cB.p^7 -------------/ // cB.p^8 let mut expected_uncle_parent = header.parent_hash().clone(); - let uncle_parent = try!(bc.block_header(&uncle.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownUncleParent(uncle.parent_hash().clone())))); + let uncle_parent = bc.block_header(&uncle.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownUncleParent(uncle.parent_hash().clone())))?; for _ in 0..depth { match bc.block_details(&expected_uncle_parent) { Some(details) => { @@ -170,8 +170,8 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash()))); } - try!(verify_parent(&uncle, &uncle_parent)); - try!(engine.verify_block_family(&uncle, &uncle_parent, Some(bytes))); + verify_parent(&uncle, &uncle_parent)?; + engine.verify_block_family(&uncle, &uncle_parent, Some(bytes))?; } } Ok(()) @@ -234,12 +234,12 @@ fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> { /// Verify block data against header: transactions root and uncles hash. fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &H256) -> Result<(), Error> { let block = UntrustedRlp::new(block); - let tx = try!(block.at(1)); + let tx = block.at(1)?; let expected_root = &ordered_trie_root(tx.iter().map(|r| r.as_raw().to_vec())); //TODO: get rid of vectors here if expected_root != transactions_root { return Err(From::from(BlockError::InvalidTransactionsRoot(Mismatch { expected: expected_root.clone(), found: transactions_root.clone() }))) } - let expected_uncles = &try!(block.at(2)).as_raw().sha3(); + let expected_uncles = &block.at(2)?.as_raw().sha3(); if expected_uncles != uncles_hash { return Err(From::from(BlockError::InvalidUnclesHash(Mismatch { expected: expected_uncles.clone(), found: uncles_hash.clone() }))) } @@ -264,6 +264,7 @@ mod tests { use types::log_entry::{LogEntry, LocalizedLogEntry}; use rlp::View; use time::get_time; + use encoded; fn check_ok(result: Result<(), Error>) { result.unwrap_or_else(|e| panic!("Block verification failed: {:?}", e)); @@ -322,16 +323,20 @@ mod tests { } /// Get raw block data - fn block(&self, hash: &H256) -> Option { - self.blocks.get(hash).cloned() + fn block(&self, hash: &H256) -> Option { + self.blocks.get(hash).cloned().map(encoded::Block::new) } - fn block_header_data(&self, hash: &H256) -> Option { - self.block(hash).map(|b| BlockView::new(&b).header_rlp().as_raw().to_vec()) + fn block_header_data(&self, hash: &H256) -> Option { + self.block(hash) + .map(|b| b.header_view().rlp().as_raw().to_vec()) + .map(encoded::Header::new) } - fn block_body(&self, hash: &H256) -> Option { - self.block(hash).map(|b| BlockChain::block_to_body(&b)) + fn block_body(&self, hash: &H256) -> Option { + self.block(hash) + .map(|b| BlockChain::block_to_body(&b.into_inner())) + .map(encoded::Body::new) } fn best_ancient_block(&self) -> Option { diff --git a/ethcore/src/views/block.rs b/ethcore/src/views/block.rs index ede5b1985..879c2b611 100644 --- a/ethcore/src/views/block.rs +++ b/ethcore/src/views/block.rs @@ -94,7 +94,7 @@ impl<'a> BlockView<'a> { } /// Return List of transactions in given block. - pub fn transaction_views(&self) -> Vec { + pub fn transaction_views(&self) -> Vec> { self.rlp.at(1).iter().map(TransactionView::new_from_rlp).collect() } @@ -132,7 +132,7 @@ impl<'a> BlockView<'a> { } /// Return List of transactions in given block. - pub fn uncle_views(&self) -> Vec { + pub fn uncle_views(&self) -> Vec> { self.rlp.at(2).iter().map(HeaderView::new_from_rlp).collect() } diff --git a/ethcore/src/views/body.rs b/ethcore/src/views/body.rs index c6168f6d8..34ab9679b 100644 --- a/ethcore/src/views/body.rs +++ b/ethcore/src/views/body.rs @@ -71,7 +71,7 @@ impl<'a> BodyView<'a> { } /// Return List of transactions in given block. - pub fn transaction_views(&self) -> Vec { + pub fn transaction_views(&self) -> Vec> { self.rlp.at(0).iter().map(TransactionView::new_from_rlp).collect() } @@ -106,7 +106,7 @@ impl<'a> BodyView<'a> { } /// Return List of transactions in given block. - pub fn uncle_views(&self) -> Vec { + pub fn uncle_views(&self) -> Vec> { self.rlp.at(1).iter().map(HeaderView::new_from_rlp).collect() } diff --git a/ethcrypto/src/lib.rs b/ethcrypto/src/lib.rs index 4cb086365..4053baa9f 100644 --- a/ethcrypto/src/lib.rs +++ b/ethcrypto/src/lib.rs @@ -158,7 +158,7 @@ pub mod aes { let mut encryptor = CbcDecryptor::new(AesSafe128Decryptor::new(k), PkcsPadding, iv.to_vec()); let len = dest.len(); let mut buffer = RefWriteBuffer::new(dest); - try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut buffer, true)); + encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut buffer, true)?; Ok(len - buffer.remaining()) } } @@ -179,7 +179,7 @@ pub mod ecdh { temp }; - let publ = try!(key::PublicKey::from_slice(context, &pdata)); + let publ = key::PublicKey::from_slice(context, &pdata)?; // no way to create SecretKey from raw byte array. let sec: &key::SecretKey = unsafe { ::std::mem::transmute(secret) }; let shared = ecdh::SharedSecret::new_raw(context, &publ, sec); @@ -206,7 +206,7 @@ pub mod ecies { let r = Random.generate() .expect("context known to have key-generation capabilities; qed"); - let z = try!(ecdh::agree(r.secret(), public)); + let z = ecdh::agree(r.secret(), public)?; let mut key = [0u8; 32]; let mut mkey = [0u8; 32]; kdf(&z, &[0u8; 0], &mut key); @@ -243,7 +243,7 @@ pub mod ecies { let r = Random.generate() .expect("context known to have key-generation capabilities"); - let z = try!(ecdh::agree(r.secret(), public)); + let z = ecdh::agree(r.secret(), public)?; let mut key = [0u8; 32]; let mut mkey = [0u8; 32]; kdf(&z, &[0u8; 0], &mut key); @@ -274,7 +274,7 @@ pub mod ecies { let e = &encrypted[1..]; let p = Public::from_slice(&e[0..64]); - let z = try!(ecdh::agree(secret, &p)); + let z = ecdh::agree(secret, &p)?; let mut key = [0u8; 32]; kdf(&z, &[0u8; 0], &mut key); let ekey = &key[0..16]; @@ -314,7 +314,7 @@ pub mod ecies { let e = encrypted; let p = Public::from_slice(&e[0..64]); - let z = try!(ecdh::agree(secret, &p)); + let z = ecdh::agree(secret, &p)?; let mut key = [0u8; 32]; kdf(&z, &[0u8; 0], &mut key); let ekey = &key[0..16]; diff --git a/ethkey/src/bin/ethkey.rs b/ethkey/src/bin/ethkey.rs index af301732b..d1d15c94f 100644 --- a/ethkey/src/bin/ethkey.rs +++ b/ethkey/src/bin/ethkey.rs @@ -160,37 +160,37 @@ fn execute(command: I) -> Result where I: IntoIterator Result<(), fmt::Error> { - try!(writeln!(f, "secret: {}", self.secret.to_hex())); - try!(writeln!(f, "public: {}", self.public.to_hex())); + writeln!(f, "secret: {}", self.secret.to_hex())?; + writeln!(f, "public: {}", self.public.to_hex())?; write!(f, "address: {}", self.address().to_hex()) } } @@ -45,8 +45,8 @@ impl KeyPair { /// Create a pair from secret key pub fn from_secret(secret: Secret) -> Result { let context = &SECP256K1; - let s: key::SecretKey = try!(key::SecretKey::from_slice(context, &secret[..])); - let pub_key = try!(key::PublicKey::from_secret_key(context, &s)); + let s: key::SecretKey = key::SecretKey::from_slice(context, &secret[..])?; + let pub_key = key::PublicKey::from_secret_key(context, &s)?; let serialized = pub_key.serialize_vec(context, false); let mut public = Public::default(); diff --git a/ethkey/src/prefix.rs b/ethkey/src/prefix.rs index 59e64abda..91c24708d 100644 --- a/ethkey/src/prefix.rs +++ b/ethkey/src/prefix.rs @@ -34,7 +34,7 @@ impl Prefix { impl Generator for Prefix { fn generate(self) -> Result { for _ in 0..self.iterations { - let keypair = try!(Random.generate()); + let keypair = Random.generate()?; if keypair.address().starts_with(&self.prefix) { return Ok(keypair) } diff --git a/ethkey/src/random.rs b/ethkey/src/random.rs index 8b0c98c64..f17651c0a 100644 --- a/ethkey/src/random.rs +++ b/ethkey/src/random.rs @@ -23,8 +23,8 @@ pub struct Random; impl Generator for Random { fn generate(self) -> Result { let context = &SECP256K1; - let mut rng = try!(OsRng::new()); - let (sec, publ) = try!(context.generate_keypair(&mut rng)); + let mut rng = OsRng::new()?; + let (sec, publ) = context.generate_keypair(&mut rng)?; Ok(KeyPair::from_keypair(sec, publ)) } diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index ad595cfb9..ef28fb408 100644 --- a/ethkey/src/signature.rs +++ b/ethkey/src/signature.rs @@ -171,7 +171,7 @@ pub fn sign(secret: &Secret, message: &Message) -> Result { let context = &SECP256K1; // no way to create from raw byte array. let sec: &SecretKey = unsafe { mem::transmute(secret) }; - let s = try!(context.sign_recoverable(&try!(SecpMessage::from_slice(&message[..])), sec)); + let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, sec)?; let (rec_id, data) = s.serialize_compact(context); let mut data_arr = [0; 65]; @@ -183,7 +183,7 @@ pub fn sign(secret: &Secret, message: &Message) -> Result { pub fn verify_public(public: &Public, signature: &Signature, message: &Message) -> Result { let context = &SECP256K1; - let rsig = try!(RecoverableSignature::from_compact(context, &signature[0..64], try!(RecoveryId::from_i32(signature[64] as i32)))); + let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?; let sig = rsig.to_standard(context); let pdata: [u8; 65] = { @@ -192,8 +192,8 @@ pub fn verify_public(public: &Public, signature: &Signature, message: &Message) temp }; - let publ = try!(PublicKey::from_slice(context, &pdata)); - match context.verify(&try!(SecpMessage::from_slice(&message[..])), &sig, &publ) { + let publ = PublicKey::from_slice(context, &pdata)?; + match context.verify(&SecpMessage::from_slice(&message[..])?, &sig, &publ) { Ok(_) => Ok(true), Err(SecpError::IncorrectSignature) => Ok(false), Err(x) => Err(Error::from(x)) @@ -201,15 +201,15 @@ pub fn verify_public(public: &Public, signature: &Signature, message: &Message) } pub fn verify_address(address: &Address, signature: &Signature, message: &Message) -> Result { - let public = try!(recover(signature, message)); + let public = recover(signature, message)?; let recovered_address = public_to_address(&public); Ok(address == &recovered_address) } pub fn recover(signature: &Signature, message: &Message) -> Result { let context = &SECP256K1; - let rsig = try!(RecoverableSignature::from_compact(context, &signature[0..64], try!(RecoveryId::from_i32(signature[64] as i32)))); - let pubkey = try!(context.recover(&try!(SecpMessage::from_slice(&message[..])), &rsig)); + let rsig = RecoverableSignature::from_compact(context, &signature[0..64], RecoveryId::from_i32(signature[64] as i32)?)?; + let pubkey = context.recover(&SecpMessage::from_slice(&message[..])?, &rsig)?; let serialized = pubkey.serialize_vec(context, false); let mut public = Public::default(); diff --git a/ethstore/src/account/safe_account.rs b/ethstore/src/account/safe_account.rs index 133e6eeac..76da94021 100644 --- a/ethstore/src/account/safe_account.rs +++ b/ethstore/src/account/safe_account.rs @@ -113,7 +113,7 @@ impl Crypto { let (derived_left_bits, derived_right_bits) = match self.kdf { Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, ¶ms.salt, params.c), - Kdf::Scrypt(ref params) => try!(crypto::derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r)), + Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r)?, }; let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256(); @@ -171,22 +171,22 @@ impl SafeAccount { } pub fn sign(&self, password: &str, message: &Message) -> Result { - let secret = try!(self.crypto.secret(password)); + let secret = self.crypto.secret(password)?; sign(&secret, message).map_err(From::from) } pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let secret = try!(self.crypto.secret(password)); + let secret = self.crypto.secret(password)?; crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from) } pub fn public(&self, password: &str) -> Result { - let secret = try!(self.crypto.secret(password)); - Ok(try!(KeyPair::from_secret(secret)).public().clone()) + let secret = self.crypto.secret(password)?; + Ok(KeyPair::from_secret(secret)?.public().clone()) } pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result { - let secret = try!(self.crypto.secret(old_password)); + let secret = self.crypto.secret(old_password)?; let result = SafeAccount { id: self.id.clone(), version: self.version.clone(), diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 954be2aa9..18f8befec 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -93,11 +93,11 @@ fn main() { fn key_dir(location: &str) -> Result, Error> { let dir: Box = match location { - "parity" => Box::new(try!(ParityDirectory::create(DirectoryType::Main))), - "parity-test" => Box::new(try!(ParityDirectory::create(DirectoryType::Testnet))), - "geth" => Box::new(try!(GethDirectory::create(DirectoryType::Main))), - "geth-test" => Box::new(try!(GethDirectory::create(DirectoryType::Testnet))), - path => Box::new(try!(DiskDirectory::create(path))), + "parity" => Box::new(ParityDirectory::create(DirectoryType::Main)?), + "parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?), + "geth" => Box::new(GethDirectory::create(DirectoryType::Main)?), + "geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?), + path => Box::new(DiskDirectory::create(path)?), }; Ok(dir) @@ -112,9 +112,9 @@ fn format_accounts(accounts: &[Address]) -> String { } fn load_password(path: &str) -> Result { - let mut file = try!(fs::File::open(path)); + let mut file = fs::File::open(path)?; let mut password = String::new(); - try!(file.read_to_string(&mut password)); + file.read_to_string(&mut password)?; // drop EOF let _ = password.pop(); Ok(password) @@ -125,48 +125,48 @@ fn execute(command: I) -> Result where I: IntoIterator Result<(), i32> { use std::ffi; use libc; - let cstr = try!(ffi::CString::new(&*file_path.to_string_lossy()) - .map_err(|_| -1)); + let cstr = ffi::CString::new(&*file_path.to_string_lossy()) + .map_err(|_| -1)?; match unsafe { libc::chmod(cstr.as_ptr(), libc::S_IWUSR | libc::S_IRUSR) } { 0 => Ok(()), x => Err(x), @@ -48,7 +48,7 @@ pub struct DiskDirectory { impl DiskDirectory { pub fn create

(path: P) -> Result where P: AsRef { - try!(fs::create_dir_all(&path)); + fs::create_dir_all(&path)?; Ok(Self::at(path)) } @@ -62,7 +62,7 @@ impl DiskDirectory { fn files(&self) -> Result, Error> { // it's not done using one iterator cause // there is an issue with rustc and it takes tooo much time to compile - let paths = try!(fs::read_dir(&self.path)) + let paths = fs::read_dir(&self.path)? .flat_map(Result::ok) .filter(|entry| { let metadata = entry.metadata().ok(); @@ -102,7 +102,7 @@ impl DiskDirectory { impl KeyDirectory for DiskDirectory { fn load(&self) -> Result, Error> { - let accounts = try!(self.files()) + let accounts = self.files()? .into_iter() .map(|(_, account)| account) .collect(); @@ -134,8 +134,8 @@ impl KeyDirectory for DiskDirectory { keyfile_path.push(filename.as_str()); // save the file - let mut file = try!(fs::File::create(&keyfile_path)); - try!(keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e)))); + let mut file = fs::File::create(&keyfile_path)?; + keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?; if let Err(_) = restrict_permissions_to_owner(keyfile_path.as_path()) { fs::remove_file(keyfile_path).expect("Expected to remove recently created file"); @@ -149,7 +149,7 @@ impl KeyDirectory for DiskDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address - let to_remove = try!(self.files()) + let to_remove = self.files()? .into_iter() .find(|&(_, ref acc)| acc == account); diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index fe2ba8d1d..929ee00d9 100644 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -66,7 +66,7 @@ pub struct GethDirectory { impl GethDirectory { pub fn create(t: DirectoryType) -> Result { let result = GethDirectory { - dir: try!(DiskDirectory::create(geth_keystore(t))), + dir: DiskDirectory::create(geth_keystore(t))?, }; Ok(result) diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 75c21ea13..e381cd9a4 100644 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -45,7 +45,7 @@ pub struct ParityDirectory { impl ParityDirectory { pub fn create(t: DirectoryType) -> Result { let result = ParityDirectory { - dir: try!(DiskDirectory::create(parity_keystore(t))), + dir: DiskDirectory::create(parity_keystore(t))?, }; Ok(result) diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index e100594de..0747ee157 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -38,12 +38,12 @@ impl EthStore { pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { Ok(EthStore { - store: try!(EthMultiStore::open_with_iterations(directory, iterations)), + store: EthMultiStore::open_with_iterations(directory, iterations)?, }) } fn get(&self, address: &Address) -> Result { - let mut accounts = try!(self.store.get(address)).into_iter(); + let mut accounts = self.store.get(address)?.into_iter(); accounts.next().ok_or(Error::InvalidAccount) } } @@ -66,68 +66,68 @@ impl SimpleSecretStore for EthStore { } fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { - let account = try!(self.get(address)); + let account = self.get(address)?; account.sign(password, message) } fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let account = try!(self.get(account)); + let account = self.get(account)?; account.decrypt(password, shared_mac, message) } } impl SecretStore for EthStore { fn import_presale(&self, json: &[u8], password: &str) -> Result { - let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))); + let json_wallet = json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?; let wallet = PresaleWallet::from(json_wallet); - let keypair = try!(wallet.decrypt(password).map_err(|_| Error::InvalidPassword)); + let keypair = wallet.decrypt(password).map_err(|_| Error::InvalidPassword)?; self.insert_account(keypair.secret().clone(), password) } fn import_wallet(&self, json: &[u8], password: &str) -> Result { - let json_keyfile = try!(json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))); + let json_keyfile = json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?; let mut safe_account = SafeAccount::from_file(json_keyfile, None); - let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)); - safe_account.address = try!(KeyPair::from_secret(secret)).address(); + let secret = safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)?; + safe_account.address = KeyPair::from_secret(secret)?.address(); let address = safe_account.address.clone(); - try!(self.store.import(safe_account)); + self.store.import(safe_account)?; Ok(address) } fn test_password(&self, address: &Address, password: &str) -> Result { - let account = try!(self.get(address)); + let account = self.get(address)?; Ok(account.check_password(password)) } fn copy_account(&self, new_store: &SimpleSecretStore, address: &Address, password: &str, new_password: &str) -> Result<(), Error> { - let account = try!(self.get(address)); - let secret = try!(account.crypto.secret(password)); - try!(new_store.insert_account(secret, new_password)); + let account = self.get(address)?; + let secret = account.crypto.secret(password)?; + new_store.insert_account(secret, new_password)?; Ok(()) } fn public(&self, account: &Address, password: &str) -> Result { - let account = try!(self.get(account)); + let account = self.get(account)?; account.public(password) } fn uuid(&self, address: &Address) -> Result { - let account = try!(self.get(address)); + let account = self.get(address)?; Ok(account.id.into()) } fn name(&self, address: &Address) -> Result { - let account = try!(self.get(address)); + let account = self.get(address)?; Ok(account.name.clone()) } fn meta(&self, address: &Address) -> Result { - let account = try!(self.get(address)); + let account = self.get(address)?; Ok(account.meta.clone()) } fn set_name(&self, address: &Address, name: String) -> Result<(), Error> { - let old = try!(self.get(address)); + let old = self.get(address)?; let mut account = old.clone(); account.name = name; @@ -136,7 +136,7 @@ impl SecretStore for EthStore { } fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> { - let old = try!(self.get(address)); + let old = self.get(address)?; let mut account = old.clone(); account.meta = meta; @@ -176,13 +176,13 @@ impl EthMultiStore { iterations: iterations, cache: Default::default(), }; - try!(store.reload_accounts()); + store.reload_accounts()?; Ok(store) } fn reload_accounts(&self) -> Result<(), Error> { let mut cache = self.cache.write(); - let accounts = try!(self.dir.load()); + let accounts = self.dir.load()?; let mut new_accounts = BTreeMap::new(); for account in accounts { @@ -203,9 +203,9 @@ impl EthMultiStore { } } - try!(self.reload_accounts()); + self.reload_accounts()?; let cache = self.cache.read(); - let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount)); + let accounts = cache.get(address).cloned().ok_or(Error::InvalidAccount)?; if accounts.is_empty() { Err(Error::InvalidAccount) } else { @@ -215,7 +215,7 @@ impl EthMultiStore { fn import(&self, account: SafeAccount) -> Result<(), Error> { // save to file - let account = try!(self.dir.insert(account)); + let account = self.dir.insert(account)?; // update cache let mut cache = self.cache.write(); @@ -226,7 +226,7 @@ impl EthMultiStore { fn update(&self, old: SafeAccount, new: SafeAccount) -> Result<(), Error> { // save to file - let account = try!(self.dir.update(new)); + let account = self.dir.update(new)?; // update cache let mut cache = self.cache.write(); @@ -243,21 +243,21 @@ impl EthMultiStore { impl SimpleSecretStore for EthMultiStore { fn insert_account(&self, secret: Secret, password: &str) -> Result { - let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); + let keypair = KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)?; let id: [u8; 16] = Random::random(); let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); let address = account.address.clone(); - try!(self.import(account)); + self.import(account)?; Ok(address) } fn accounts(&self) -> Result, Error> { - try!(self.reload_accounts()); + self.reload_accounts()?; Ok(self.cache.read().keys().cloned().collect()) } fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { - let accounts = try!(self.get(address)); + let accounts = self.get(address)?; for account in accounts { // Skip if password is invalid @@ -266,7 +266,7 @@ impl SimpleSecretStore for EthMultiStore { } // Remove from dir - try!(self.dir.remove(&account)); + self.dir.remove(&account)?; // Remove from cache let mut cache = self.cache.write(); @@ -288,17 +288,17 @@ impl SimpleSecretStore for EthMultiStore { } fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { - let accounts = try!(self.get(address)); + let accounts = self.get(address)?; for account in accounts { // Change password - let new_account = try!(account.change_password(old_password, new_password, self.iterations)); - try!(self.update(account, new_account)); + let new_account = account.change_password(old_password, new_password, self.iterations)?; + self.update(account, new_account)?; } Ok(()) } fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { - let accounts = try!(self.get(address)); + let accounts = self.get(address)?; for account in accounts { if account.check_password(password) { return account.sign(password, message); @@ -309,7 +309,7 @@ impl SimpleSecretStore for EthMultiStore { } fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let accounts = try!(self.get(account)); + let accounts = self.get(account)?; for account in accounts { if account.check_password(password) { return account.decrypt(password, shared_mac, message); diff --git a/ethstore/src/import.rs b/ethstore/src/import.rs index b112bce75..863c7774b 100644 --- a/ethstore/src/import.rs +++ b/ethstore/src/import.rs @@ -20,14 +20,14 @@ use dir::{GethDirectory, KeyDirectory, DirectoryType}; use Error; pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result, Error> { - let accounts = try!(src.load()); - let existing_accounts = try!(dst.load()).into_iter().map(|a| a.address).collect::>(); + let accounts = src.load()?; + let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); accounts.into_iter() .filter(|a| !existing_accounts.contains(&a.address)) .map(|a| { let address = a.address.clone(); - try!(dst.insert(a)); + dst.insert(a)?; Ok(address) }).collect() } @@ -55,15 +55,15 @@ pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet

, testn }; let src = GethDirectory::open(t); - let accounts = try!(src.load()); - let existing_accounts = try!(dst.load()).into_iter().map(|a| a.address).collect::>(); + let accounts = src.load()?; + let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); accounts.into_iter() .filter(|a| !existing_accounts.contains(&a.address)) .filter(|a| desired.contains(&a.address)) .map(|a| { let address = a.address.clone(); - try!(dst.insert(a)); + dst.insert(a)?; Ok(address) }).collect() } diff --git a/ethstore/src/json/bytes.rs b/ethstore/src/json/bytes.rs index fd4a3b995..5745c2cea 100644 --- a/ethstore/src/json/bytes.rs +++ b/ethstore/src/json/bytes.rs @@ -17,8 +17,8 @@ impl Deserialize for Bytes { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { - let s = try!(String::deserialize(deserializer)); - let data = try!(s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e)))); + let s = String::deserialize(deserializer)?; + let data = s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e)))?; Ok(Bytes(data)) } } diff --git a/ethstore/src/json/crypto.rs b/ethstore/src/json/crypto.rs index 99ef026b3..2ce728146 100644 --- a/ethstore/src/json/crypto.rs +++ b/ethstore/src/json/crypto.rs @@ -90,13 +90,13 @@ impl Visitor for CryptoVisitor { let mut mac = None; loop { - match try!(visitor.visit_key()) { - Some(CryptoField::Cipher) => { cipher = Some(try!(visitor.visit_value())); } - Some(CryptoField::CipherParams) => { cipherparams = Some(try!(visitor.visit_value())); } - Some(CryptoField::CipherText) => { ciphertext = Some(try!(visitor.visit_value())); } - Some(CryptoField::Kdf) => { kdf = Some(try!(visitor.visit_value())); } - Some(CryptoField::KdfParams) => { kdfparams = Some(try!(visitor.visit_value())); } - Some(CryptoField::Mac) => { mac = Some(try!(visitor.visit_value())); } + match visitor.visit_key()? { + Some(CryptoField::Cipher) => { cipher = Some(visitor.visit_value()?); } + Some(CryptoField::CipherParams) => { cipherparams = Some(visitor.visit_value()?); } + Some(CryptoField::CipherText) => { ciphertext = Some(visitor.visit_value()?); } + Some(CryptoField::Kdf) => { kdf = Some(visitor.visit_value()?); } + Some(CryptoField::KdfParams) => { kdfparams = Some(visitor.visit_value()?); } + Some(CryptoField::Mac) => { mac = Some(visitor.visit_value()?); } None => { break; } } } @@ -109,7 +109,7 @@ impl Visitor for CryptoVisitor { let ciphertext = match ciphertext { Some(ciphertext) => ciphertext, - None => try!(visitor.missing_field("ciphertext")), + None => visitor.missing_field("ciphertext")?, }; let kdf = match (kdf, kdfparams) { @@ -122,10 +122,10 @@ impl Visitor for CryptoVisitor { let mac = match mac { Some(mac) => mac, - None => try!(visitor.missing_field("mac")), + None => visitor.missing_field("mac")?, }; - try!(visitor.end()); + visitor.end()?; let result = Crypto { cipher: cipher, @@ -142,26 +142,26 @@ impl Serialize for Crypto { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { - let mut state = try!(serializer.serialize_struct("Crypto", 6)); + let mut state = serializer.serialize_struct("Crypto", 6)?; match self.cipher { Cipher::Aes128Ctr(ref params) => { - try!(serializer.serialize_struct_elt(&mut state, "cipher", &CipherSer::Aes128Ctr)); - try!(serializer.serialize_struct_elt(&mut state, "cipherparams", params)); + serializer.serialize_struct_elt(&mut state, "cipher", &CipherSer::Aes128Ctr)?; + serializer.serialize_struct_elt(&mut state, "cipherparams", params)?; }, } - try!(serializer.serialize_struct_elt(&mut state, "ciphertext", &self.ciphertext)); + serializer.serialize_struct_elt(&mut state, "ciphertext", &self.ciphertext)?; match self.kdf { Kdf::Pbkdf2(ref params) => { - try!(serializer.serialize_struct_elt(&mut state, "kdf", &KdfSer::Pbkdf2)); - try!(serializer.serialize_struct_elt(&mut state, "kdfparams", params)); + serializer.serialize_struct_elt(&mut state, "kdf", &KdfSer::Pbkdf2)?; + serializer.serialize_struct_elt(&mut state, "kdfparams", params)?; }, Kdf::Scrypt(ref params) => { - try!(serializer.serialize_struct_elt(&mut state, "kdf", &KdfSer::Scrypt)); - try!(serializer.serialize_struct_elt(&mut state, "kdfparams", params)); + serializer.serialize_struct_elt(&mut state, "kdf", &KdfSer::Scrypt)?; + serializer.serialize_struct_elt(&mut state, "kdfparams", params)?; }, } - try!(serializer.serialize_struct_elt(&mut state, "mac", &self.mac)); + serializer.serialize_struct_elt(&mut state, "mac", &self.mac)?; serializer.serialize_struct_end(state) } } diff --git a/ethstore/src/json/id.rs b/ethstore/src/json/id.rs index cf098c83f..441fbc8c1 100644 --- a/ethstore/src/json/id.rs +++ b/ethstore/src/json/id.rs @@ -62,7 +62,7 @@ impl fmt::Display for Uuid { } fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> { - let from = try!(from.from_hex().map_err(|_| Error::InvalidUuid)); + let from = from.from_hex().map_err(|_| Error::InvalidUuid)?; if from.len() != into.len() { return Err(Error::InvalidUuid); @@ -84,11 +84,11 @@ impl str::FromStr for Uuid { let mut uuid = [0u8; 16]; - try!(copy_into(parts[0], &mut uuid[0..4])); - try!(copy_into(parts[1], &mut uuid[4..6])); - try!(copy_into(parts[2], &mut uuid[6..8])); - try!(copy_into(parts[3], &mut uuid[8..10])); - try!(copy_into(parts[4], &mut uuid[10..16])); + copy_into(parts[0], &mut uuid[0..4])?; + copy_into(parts[1], &mut uuid[4..6])?; + copy_into(parts[2], &mut uuid[6..8])?; + copy_into(parts[3], &mut uuid[8..10])?; + copy_into(parts[4], &mut uuid[10..16])?; Ok(Uuid(uuid)) } diff --git a/ethstore/src/json/kdf.rs b/ethstore/src/json/kdf.rs index af751ad33..c9028f7cc 100644 --- a/ethstore/src/json/kdf.rs +++ b/ethstore/src/json/kdf.rs @@ -134,7 +134,7 @@ impl Serialize for KdfSerParams { impl Deserialize for KdfSerParams { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { - let v = try!(Value::deserialize(deserializer)); + let v = Value::deserialize(deserializer)?; Deserialize::deserialize(&mut value::Deserializer::new(v.clone())).map(KdfSerParams::Pbkdf2) .or_else(|_| Deserialize::deserialize(&mut value::Deserializer::new(v)).map(KdfSerParams::Scrypt)) diff --git a/ethstore/src/json/key_file.rs b/ethstore/src/json/key_file.rs index 323086fa0..fc9b442d5 100644 --- a/ethstore/src/json/key_file.rs +++ b/ethstore/src/json/key_file.rs @@ -93,11 +93,11 @@ impl Visitor for KeyFileVisitor { let mut meta = None; loop { - match try!(visitor.visit_key()) { - Some(KeyFileField::Id) => { id = Some(try!(visitor.visit_value())); } - Some(KeyFileField::Version) => { version = Some(try!(visitor.visit_value())); } - Some(KeyFileField::Crypto) => { crypto = Some(try!(visitor.visit_value())); } - Some(KeyFileField::Address) => { address = Some(try!(visitor.visit_value())); } + match visitor.visit_key()? { + Some(KeyFileField::Id) => { id = Some(visitor.visit_value()?); } + Some(KeyFileField::Version) => { version = Some(visitor.visit_value()?); } + Some(KeyFileField::Crypto) => { crypto = Some(visitor.visit_value()?); } + Some(KeyFileField::Address) => { address = Some(visitor.visit_value()?); } Some(KeyFileField::Name) => { name = visitor.visit_value().ok(); } // ignore anyhing that is not a string to be permissive. Some(KeyFileField::Meta) => { meta = visitor.visit_value().ok(); } // ignore anyhing that is not a string to be permissive. None => { break; } @@ -106,25 +106,25 @@ impl Visitor for KeyFileVisitor { let id = match id { Some(id) => id, - None => try!(visitor.missing_field("id")), + None => visitor.missing_field("id")?, }; let version = match version { Some(version) => version, - None => try!(visitor.missing_field("version")), + None => visitor.missing_field("version")?, }; let crypto = match crypto { Some(crypto) => crypto, - None => try!(visitor.missing_field("crypto")), + None => visitor.missing_field("crypto")?, }; let address = match address { Some(address) => address, - None => try!(visitor.missing_field("address")), + None => visitor.missing_field("address")?, }; - try!(visitor.end()); + visitor.end()?; let result = KeyFile { id: id, diff --git a/ethstore/src/presale.rs b/ethstore/src/presale.rs index ff3bde6f4..b9a15aed5 100644 --- a/ethstore/src/presale.rs +++ b/ethstore/src/presale.rs @@ -32,9 +32,9 @@ impl From for PresaleWallet { impl PresaleWallet { pub fn open

(path: P) -> Result where P: AsRef { - let file = try!(fs::File::open(path)); - let presale = try!(json::PresaleWallet::load(file) - .map_err(|e| Error::InvalidKeyFile(format!("{}", e)))); + let file = fs::File::open(path)?; + let presale = json::PresaleWallet::load(file) + .map_err(|e| Error::InvalidKeyFile(format!("{}", e)))?; Ok(PresaleWallet::from(presale)) } @@ -44,7 +44,7 @@ impl PresaleWallet { pbkdf2(&mut h_mac, password.as_bytes(), 2000, &mut derived_key); let mut key = vec![0; self.ciphertext.len()]; - let len = try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)); + let len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?; let unpadded = &key[..len]; let secret = Secret::from(unpadded.keccak256()); diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 6a22a602d..a036f3c87 100644 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -36,7 +36,7 @@ impl TransientDir { pub fn create() -> Result { let path = random_dir(); let result = TransientDir { - dir: try!(DiskDirectory::create(&path)), + dir: DiskDirectory::create(&path)?, path: path, }; diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 8a08bd163..743b517ec 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -104,9 +104,9 @@ pub struct Success { } impl fmt::Display for Success { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - try!(writeln!(f, "Gas used: {:?}", self.gas_used)); - try!(writeln!(f, "Output: {:?}", self.output)); - try!(writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())); + writeln!(f, "Gas used: {:?}", self.gas_used)?; + writeln!(f, "Output: {:?}", self.output)?; + writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())?; Ok(()) } } @@ -120,8 +120,8 @@ pub struct Failure { } impl fmt::Display for Failure { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - try!(writeln!(f, "Error: {:?}", self.error)); - try!(writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())); + writeln!(f, "Error: {:?}", self.error)?; + writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())?; Ok(()) } } diff --git a/hash-fetch/Cargo.toml b/hash-fetch/Cargo.toml index fa98f2fce..328d53eb1 100644 --- a/hash-fetch/Cargo.toml +++ b/hash-fetch/Cargo.toml @@ -7,9 +7,13 @@ version = "1.5.0" authors = ["Parity Technologies "] [dependencies] -log = "0.3" -rustc-serialize = "0.3" ethabi = "0.2.2" +futures = "0.1" +log = "0.3" +mime = "0.2" mime_guess = "1.6.1" +rand = "0.3" +rustc-serialize = "0.3" fetch = { path = "../util/fetch" } ethcore-util = { path = "../util" } +parity-reactor = { path = "../util/reactor" } diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index 57a1b104f..dd6d26033 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -17,13 +17,15 @@ //! Hash-addressed content resolver & fetcher. use std::{io, fs}; +use std::io::Write; use std::sync::Arc; use std::path::PathBuf; -use util::{Mutex, H256, sha3}; -use fetch::{Fetch, FetchError, Client as FetchClient}; - +use fetch::{Fetch, Response, Error as FetchError, Client as FetchClient}; +use futures::Future; +use parity_reactor::Remote; use urlhint::{ContractClient, URLHintContract, URLHint, URLHintResult}; +use util::{H256, sha3}; /// API for fetching by hash. pub trait HashFetch: Send + Sync + 'static { @@ -33,7 +35,7 @@ pub trait HashFetch: Send + Sync + 'static { /// 2. `on_done` - callback function invoked when the content is ready (or there was error during fetch) /// /// This function may fail immediately when fetch cannot be initialized or content cannot be resolved. - fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error>; + fn fetch(&self, hash: H256, on_done: Box) + Send>); } /// Hash-fetching error. @@ -67,53 +69,94 @@ impl From for Error { } /// Default Hash-fetching client using on-chain contract to resolve hashes to URLs. -pub struct Client { +pub struct Client { contract: URLHintContract, - fetch: Mutex, + fetch: F, + remote: Remote, } impl Client { - /// Creates new instance of the `Client` given on-chain contract client. - pub fn new(contract: Arc) -> Self { + /// Creates new instance of the `Client` given on-chain contract client and task runner. + pub fn new(contract: Arc, remote: Remote) -> Self { + Client::with_fetch(contract, FetchClient::new().unwrap(), remote) + } +} + +impl Client { + + /// Creates new instance of the `Client` given on-chain contract client, fetch service and task runner. + pub fn with_fetch(contract: Arc, fetch: F, remote: Remote) -> Self { Client { contract: URLHintContract::new(contract), - fetch: Mutex::new(FetchClient::default()), + fetch: fetch, + remote: remote, } } } -impl HashFetch for Client { - fn fetch(&self, hash: H256, on_done: Box) + Send>) -> Result<(), Error> { +impl HashFetch for Client { + fn fetch(&self, hash: H256, on_done: Box) + Send>) { debug!(target: "fetch", "Fetching: {:?}", hash); - let url = try!( - self.contract.resolve(hash.to_vec()).map(|content| match content { + let url = self.contract.resolve(hash.to_vec()).map(|content| match content { URLHintResult::Dapp(dapp) => { dapp.url() }, URLHintResult::Content(content) => { content.url }, - }).ok_or_else(|| Error::NoResolution) - ); + }).ok_or_else(|| Error::NoResolution); debug!(target: "fetch", "Resolved {:?} to {:?}. Fetching...", hash, url); - self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| { - fn validate_hash(hash: H256, result: Result) -> Result { - let path = try!(result); - let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); - let content_hash = try!(sha3(&mut file_reader)); + match url { + Err(err) => on_done(Err(err)), + Ok(url) => { + let future = self.fetch.fetch(&url).then(move |result| { + fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { + let response = result?; + // Read the response + let mut reader = io::BufReader::new(response); + let mut writer = io::BufWriter::new(fs::File::create(&path)?); + io::copy(&mut reader, &mut writer)?; + writer.flush()?; - if content_hash != hash { - Err(Error::HashMismatch{ got: content_hash, expected: hash }) - } else { - Ok(path) - } - } + // And validate the hash + let mut file_reader = io::BufReader::new(fs::File::open(&path)?); + let content_hash = sha3(&mut file_reader)?; + if content_hash != hash { + Err(Error::HashMismatch{ got: content_hash, expected: hash }) + } else { + Ok(path) + } + } - debug!(target: "fetch", "Content fetched, validating hash ({:?})", hash); - on_done(validate_hash(hash, result)) - })).map_err(Into::into) + debug!(target: "fetch", "Content fetched, validating hash ({:?})", hash); + let path = random_temp_path(); + let res = validate_hash(path.clone(), hash, result); + if let Err(ref err) = res { + trace!(target: "fetch", "Error: {:?}", err); + // Remove temporary file in case of error + let _ = fs::remove_dir_all(&path); + } + on_done(res); + + Ok(()) as Result<(), ()> + }); + self.remote.spawn(self.fetch.process(future)); + }, + } } } + +fn random_temp_path() -> PathBuf { + use ::rand::Rng; + use ::std::env; + + let mut rng = ::rand::OsRng::new().expect("Reliable random source is required to work."); + let file: String = rng.gen_ascii_chars().take(12).collect(); + + let mut path = env::temp_dir(); + path.push(file); + path +} diff --git a/hash-fetch/src/lib.rs b/hash-fetch/src/lib.rs index e333b6df2..1b66b0ea5 100644 --- a/hash-fetch/src/lib.rs +++ b/hash-fetch/src/lib.rs @@ -20,11 +20,17 @@ #[macro_use] extern crate log; -extern crate rustc_serialize; -extern crate mime_guess; +#[macro_use] +extern crate mime; + extern crate ethabi; extern crate ethcore_util as util; -extern crate fetch; +pub extern crate fetch; +extern crate futures; +extern crate mime_guess; +extern crate rand; +extern crate rustc_serialize; +extern crate parity_reactor; mod client; diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 79575e8f3..98f680ff2 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -19,6 +19,7 @@ use std::fmt; use std::sync::Arc; use rustc_serialize::hex::ToHex; +use mime::Mime; use mime_guess; use ethabi::{Interface, Contract, Token}; @@ -76,7 +77,7 @@ pub struct Content { /// URL of the content pub url: String, /// MIME type of the content - pub mime: String, + pub mime: Mime, /// Content owner address pub owner: Address, } @@ -118,12 +119,12 @@ impl URLHintContract { fn urlhint_address(&self) -> Option

{ let res = || { - let get_address = try!(self.registrar.function("getAddress".into()).map_err(as_string)); - let params = try!(get_address.encode_call( + let get_address = self.registrar.function("getAddress".into()).map_err(as_string)?; + let params = get_address.encode_call( vec![Token::FixedBytes((*"githubhint".sha3()).to_vec()), Token::String("A".into())] - ).map_err(as_string)); - let output = try!(self.client.call(try!(self.client.registrar()), params)); - let result = try!(get_address.decode_output(output).map_err(as_string)); + ).map_err(as_string)?; + let output = self.client.call(self.client.registrar()?, params)?; + let result = get_address.decode_output(output).map_err(as_string)?; match result.get(0) { Some(&Token::Address(address)) if address != *Address::default() => Ok(address.into()), @@ -183,7 +184,7 @@ impl URLHintContract { let commit = GithubApp::commit(&commit); if commit == Some(Default::default()) { - let mime = guess_mime_type(&account_slash_repo).unwrap_or("application/octet-stream".into()); + let mime = guess_mime_type(&account_slash_repo).unwrap_or(mime!(Application/_)); return Some(URLHintResult::Content(Content { url: account_slash_repo, mime: mime, @@ -235,7 +236,7 @@ impl URLHint for URLHintContract { } } -fn guess_mime_type(url: &str) -> Option { +fn guess_mime_type(url: &str) -> Option { const CONTENT_TYPE: &'static str = "content-type="; let mut it = url.split('#'); @@ -247,14 +248,14 @@ fn guess_mime_type(url: &str) -> Option { for meta in metas.split('&') { let meta = meta.to_lowercase(); if meta.starts_with(CONTENT_TYPE) { - return Some(meta[CONTENT_TYPE.len()..].to_owned()); + return meta[CONTENT_TYPE.len()..].parse().ok(); } } } url.and_then(|url| { url.split('.').last() }).and_then(|extension| { - mime_guess::get_mime_type_str(extension).map(Into::into) + mime_guess::get_mime_type_opt(extension) }) } @@ -369,7 +370,7 @@ mod tests { // then assert_eq!(res, Some(URLHintResult::Content(Content { url: "https://ethcore.io/assets/images/ethcore-black-horizontal.png".into(), - mime: "image/png".into(), + mime: mime!(Image/Png), owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(), }))) } @@ -401,9 +402,9 @@ mod tests { assert_eq!(guess_mime_type(url1), None); - assert_eq!(guess_mime_type(url2), Some("image/png".into())); - assert_eq!(guess_mime_type(url3), Some("image/png".into())); - assert_eq!(guess_mime_type(url4), Some("image/jpeg".into())); - assert_eq!(guess_mime_type(url5), Some("image/png".into())); + assert_eq!(guess_mime_type(url2), Some(mime!(Image/Png))); + assert_eq!(guess_mime_type(url3), Some(mime!(Image/Png))); + assert_eq!(guess_mime_type(url4), Some(mime!(Image/Jpeg))); + assert_eq!(guess_mime_type(url5), Some(mime!(Image/Png))); } } diff --git a/ipc-common-types/src/types/release_track.rs b/ipc-common-types/src/types/release_track.rs index 3fcc0bb64..0e68a9a31 100644 --- a/ipc-common-types/src/types/release_track.rs +++ b/ipc-common-types/src/types/release_track.rs @@ -19,7 +19,8 @@ use std::fmt; /// A release's track. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Binary)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[binary] pub enum ReleaseTrack { /// Stable track. Stable, @@ -36,9 +37,9 @@ pub enum ReleaseTrack { impl fmt::Display for ReleaseTrack { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}", match *self { - ReleaseTrack::Stable => "stable", - ReleaseTrack::Beta => "beta", - ReleaseTrack::Nightly => "nightly", + ReleaseTrack::Stable => "stable", + ReleaseTrack::Beta => "beta", + ReleaseTrack::Nightly => "nightly", ReleaseTrack::Testing => "testing", ReleaseTrack::Unknown => "unknown", }) @@ -48,35 +49,35 @@ impl fmt::Display for ReleaseTrack { impl<'a> From<&'a str> for ReleaseTrack { fn from(s: &'a str) -> Self { match s { - "stable" => ReleaseTrack::Stable, - "beta" => ReleaseTrack::Beta, - "nightly" => ReleaseTrack::Nightly, - "testing" => ReleaseTrack::Testing, - _ => ReleaseTrack::Unknown, - } + "stable" => ReleaseTrack::Stable, + "beta" => ReleaseTrack::Beta, + "nightly" => ReleaseTrack::Nightly, + "testing" => ReleaseTrack::Testing, + _ => ReleaseTrack::Unknown, + } } } impl From for ReleaseTrack { fn from(i: u8) -> Self { match i { - 1 => ReleaseTrack::Stable, - 2 => ReleaseTrack::Beta, - 3 => ReleaseTrack::Nightly, - 4 => ReleaseTrack::Testing, - _ => ReleaseTrack::Unknown, - } + 1 => ReleaseTrack::Stable, + 2 => ReleaseTrack::Beta, + 3 => ReleaseTrack::Nightly, + 4 => ReleaseTrack::Testing, + _ => ReleaseTrack::Unknown, + } } } impl Into for ReleaseTrack { fn into(self) -> u8 { match self { - ReleaseTrack::Stable => 1, - ReleaseTrack::Beta => 2, - ReleaseTrack::Nightly => 3, - ReleaseTrack::Testing => 4, - ReleaseTrack::Unknown => 0, - } + ReleaseTrack::Stable => 1, + ReleaseTrack::Beta => 2, + ReleaseTrack::Nightly => 3, + ReleaseTrack::Testing => 4, + ReleaseTrack::Unknown => 0, + } } } diff --git a/ipc-common-types/src/types/version_info.rs b/ipc-common-types/src/types/version_info.rs index cbffbe198..bd869bf01 100644 --- a/ipc-common-types/src/types/version_info.rs +++ b/ipc-common-types/src/types/version_info.rs @@ -22,8 +22,9 @@ use util::{H160}; use util::misc::raw_package_info; use release_track::ReleaseTrack; -/// Version information of a particular release. -#[derive(Debug, Clone, PartialEq, Binary)] +/// Version information of a particular release. +#[derive(Debug, Clone, PartialEq)] +#[binary] pub struct VersionInfo { /// The track on which it was released. pub track: ReleaseTrack, diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs index 27eb8fb0b..9cc49d53f 100644 --- a/ipc/codegen/src/codegen.rs +++ b/ipc/codegen/src/codegen.rs @@ -111,7 +111,7 @@ fn push_invoke_signature_aster( let arg_ty = &inputs[skip-1].ty; let mut tree = builder.item() - .attr().word("derive(Binary)") + .attr().word("binary") .attr().word("allow(non_camel_case_types)") .struct_(name_str.as_str()) .field(arg_name.as_str()) @@ -140,7 +140,7 @@ fn push_invoke_signature_aster( FunctionRetTy::Ty(ref ty) => { let name_str = format!("{}_output", named_signature.ident.name.as_str()); let tree = builder.item() - .attr().word("derive(Binary)") + .attr().word("binary") .attr().word("allow(non_camel_case_types)") .struct_(name_str.as_str()) .field(format!("payload")).ty().build(ty.clone()); @@ -326,7 +326,7 @@ pub fn has_ptr(ty: &P) -> bool { /// fn commit(&self, f: u32) -> u32 /// /// the expanded implementation will generate method for the client like that -/// #[derive(Serialize)] +/// #[binary] /// struct Request<'a> { /// f: &'a u32, /// } @@ -358,7 +358,7 @@ fn implement_client_method_body( .build(static_ty.clone()); let mut tree = builder.item() - .attr().word("derive(Binary)") + .attr().word("binary") .struct_("Request") .generics() .lifetime_name("'a") diff --git a/ipc/codegen/src/lib.rs b/ipc/codegen/src/lib.rs index caa0228fc..5ba51ab7b 100644 --- a/ipc/codegen/src/lib.rs +++ b/ipc/codegen/src/lib.rs @@ -42,6 +42,10 @@ extern crate rustc_plugin; #[cfg(not(feature = "with-syntex"))] use syntax::feature_gate::AttributeType; +#[cfg(feature = "with-syntex")] +use syntax::{ast, fold}; + + #[cfg(feature = "with-syntex")] include!(concat!(env!("OUT_DIR"), "/lib.rs")); @@ -56,29 +60,44 @@ pub fn expand(src: &std::path::Path, dst: &std::path::Path) { } #[cfg(feature = "with-syntex")] -pub fn register_cleaner(reg: &mut syntex::Registry) { - use syntax::{ast, fold}; +struct StripAttributeFolder<'a> { + attr_title: &'a str, +} - #[cfg(feature = "with-syntex")] - fn strip_attributes(krate: ast::Crate) -> ast::Crate { - struct StripAttributeFolder; - impl fold::Folder for StripAttributeFolder { - fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { - match attr.node.value.node { - ast::MetaItemKind::List(ref n, _) if n == &"ipc" => { return None; } - ast::MetaItemKind::Word(ref n) if n == &"ipc" => { return None; } - _ => {} - } - - Some(attr) - } - - fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { - fold::noop_fold_mac(mac, self) - } +#[cfg(feature = "with-syntex")] +impl<'a> fold::Folder for StripAttributeFolder<'a> { + fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { + match attr.node.value.node { + ast::MetaItemKind::List(ref n, _) if n == self.attr_title => { return None; } + ast::MetaItemKind::Word(ref n) if n == self.attr_title => { return None; } + _ => {} } - fold::Folder::fold_crate(&mut StripAttributeFolder, krate) + Some(attr) + } + + fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { + fold::noop_fold_mac(mac, self) + } +} + +#[cfg(feature = "with-syntex")] +pub fn register_cleaner_ipc(reg: &mut syntex::Registry) { + #[cfg(feature = "with-syntex")] + fn strip_attributes(krate: ast::Crate) -> ast::Crate { + let mut folder = StripAttributeFolder { attr_title: "ipc" }; + fold::Folder::fold_crate(&mut folder, krate) + } + + reg.add_post_expansion_pass(strip_attributes); +} + +#[cfg(feature = "with-syntex")] +pub fn register_cleaner_binary(reg: &mut syntex::Registry) { + #[cfg(feature = "with-syntex")] + fn strip_attributes(krate: ast::Crate) -> ast::Crate { + let mut folder = StripAttributeFolder { attr_title: "binary" }; + fold::Folder::fold_crate(&mut folder, krate) } reg.add_post_expansion_pass(strip_attributes); @@ -90,9 +109,10 @@ pub fn register(reg: &mut syntex::Registry) { reg.add_attr("feature(custom_attribute)"); reg.add_decorator("ipc", codegen::expand_ipc_implementation); - reg.add_decorator("derive_Binary", serialization::expand_serialization_implementation); + reg.add_decorator("binary", serialization::expand_serialization_implementation); - register_cleaner(reg); + register_cleaner_ipc(reg); + register_cleaner_binary(reg); } #[cfg(not(feature = "with-syntex"))] @@ -102,11 +122,12 @@ pub fn register(reg: &mut rustc_plugin::Registry) { syntax::ext::base::MultiDecorator( Box::new(codegen::expand_ipc_implementation))); reg.register_syntax_extension( - syntax::parse::token::intern("derive_Binary"), + syntax::parse::token::intern("binary"), syntax::ext::base::MultiDecorator( Box::new(serialization::expand_serialization_implementation))); reg.register_attribute("ipc".to_owned(), AttributeType::Normal); + reg.register_attribute("binary".to_owned(), AttributeType::Normal); } #[derive(Debug)] @@ -124,13 +145,31 @@ pub fn derive_ipc_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { } pub fn cleanup_ipc(src_path: &str) -> Result<(), Error> { + cleanup(src_path, AttributeKind::Ipc) +} + +pub fn cleanup_binary(src_path: &str) -> Result<(), Error> { + cleanup(src_path, AttributeKind::Binary) +} + +enum AttributeKind { + Ipc, + Binary, +} + +fn cleanup(src_path: &str, attr: AttributeKind) -> Result<(), Error> { use std::env; use std::path::{Path, PathBuf}; let out_dir = env::var_os("OUT_DIR").unwrap(); - let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; let mut registry = syntex::Registry::new(); - register_cleaner(&mut registry); + + match attr { + AttributeKind::Ipc => { register_cleaner_ipc(&mut registry); } + AttributeKind::Binary => { register_cleaner_binary(&mut registry); } + } + if let Err(_) = registry.expand("", &Path::new(src_path), &Path::new(&out_dir).join(&file_name)) { // will be reported by compiler @@ -144,7 +183,7 @@ pub fn derive_ipc(src_path: &str) -> Result<(), Error> { use std::path::{Path, PathBuf}; let out_dir = env::var_os("OUT_DIR").unwrap(); - let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; let final_path = Path::new(&out_dir).join(&file_name); @@ -178,7 +217,7 @@ pub fn derive_binary(src_path: &str) -> Result<(), Error> { use std::path::{Path, PathBuf}; let out_dir = env::var_os("OUT_DIR").unwrap(); - let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; let final_path = Path::new(&out_dir).join(&file_name); let mut registry = syntex::Registry::new(); @@ -190,3 +229,8 @@ pub fn derive_binary(src_path: &str) -> Result<(), Error> { Ok(()) } + +pub fn derive_binary_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { + if has_feature { derive_binary(src_path) } + else { cleanup_binary(src_path) } +} diff --git a/ipc/codegen/src/serialization.rs b/ipc/codegen/src/serialization.rs index ed5b490c3..4d6e19295 100644 --- a/ipc/codegen/src/serialization.rs +++ b/ipc/codegen/src/serialization.rs @@ -388,8 +388,7 @@ fn binary_expr_enum( span: Span, enum_def: &ast::EnumDef, ) -> Result { - let arms: Vec<_> = try!( - enum_def.variants.iter() + let arms: Vec<_> = try!(enum_def.variants.iter() .enumerate() .map(|(variant_index, variant)| { binary_expr_variant( @@ -403,8 +402,7 @@ fn binary_expr_enum( variant_index, ) }) - .collect() - ); + .collect()); let (size_arms, write_arms, mut read_arms) = ( arms.iter().map(|x| x.size.clone()).collect::>(), diff --git a/ipc/nano/src/lib.rs b/ipc/nano/src/lib.rs index ce40183e9..8334dbf47 100644 --- a/ipc/nano/src/lib.rs +++ b/ipc/nano/src/lib.rs @@ -65,17 +65,17 @@ impl Deref for GuardedSocket where S: WithSocket { /// creates socket and connects endpoint to it /// for duplex (paired) connections with the service pub fn init_duplex_client(socket_addr: &str) -> Result, SocketError> where S: WithSocket { - let mut socket = try!(Socket::new(Protocol::Pair).map_err(|e| { + let mut socket = Socket::new(Protocol::Pair).map_err(|e| { warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); SocketError::DuplexLink - })); + })?; socket.set_receive_timeout(DEFAULT_CONNECTION_TIMEOUT).unwrap(); - let endpoint = try!(socket.connect(socket_addr).map_err(|e| { + let endpoint = socket.connect(socket_addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); SocketError::DuplexLink - })); + })?; Ok(GuardedSocket { client: Arc::new(S::init(socket)), @@ -87,19 +87,19 @@ pub fn init_duplex_client(socket_addr: &str) -> Result, Sock /// creates socket and connects endpoint to it /// for request-reply connections to the service pub fn client(socket_addr: &str, receive_timeout: Option) -> Result, SocketError> where S: WithSocket { - let mut socket = try!(Socket::new(Protocol::Req).map_err(|e| { + let mut socket = Socket::new(Protocol::Req).map_err(|e| { warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); SocketError::RequestLink - })); + })?; if let Some(timeout) = receive_timeout { socket.set_receive_timeout(timeout).unwrap(); } - let endpoint = try!(socket.connect(socket_addr).map_err(|e| { + let endpoint = socket.connect(socket_addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); SocketError::RequestLink - })); + })?; trace!(target: "ipc", "Created client for {}", socket_addr); Ok(GuardedSocket { @@ -210,15 +210,15 @@ impl Worker where S: IpcInterface { /// Add exclusive socket for paired client /// Only one connection over this address is allowed pub fn add_duplex(&mut self, addr: &str) -> Result<(), SocketError> { - let mut socket = try!(Socket::new(Protocol::Pair).map_err(|e| { + let mut socket = Socket::new(Protocol::Pair).map_err(|e| { warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); SocketError::DuplexLink - })); + })?; - let endpoint = try!(socket.bind(addr).map_err(|e| { + let endpoint = socket.bind(addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", addr, e); SocketError::DuplexLink - })); + })?; self.sockets.push((socket, endpoint)); @@ -232,16 +232,16 @@ impl Worker where S: IpcInterface { /// Add generic socket for request-reply style communications /// with multiple clients pub fn add_reqrep(&mut self, addr: &str) -> Result<(), SocketError> { - let mut socket = try!(Socket::new(Protocol::Rep).map_err(|e| { + let mut socket = Socket::new(Protocol::Rep).map_err(|e| { warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); SocketError::DuplexLink - })); + })?; - let endpoint = try!(socket.bind(addr).map_err(|e| { + let endpoint = socket.bind(addr).map_err(|e| { warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", addr, e); SocketError::DuplexLink - })); + })?; self.sockets.push((socket, endpoint)); diff --git a/ipc/rpc/src/binary.rs b/ipc/rpc/src/binary.rs index eed894fce..f343fe7af 100644 --- a/ipc/rpc/src/binary.rs +++ b/ipc/rpc/src/binary.rs @@ -121,7 +121,7 @@ impl BinaryConvertable for Option where T: BinaryConvertable { fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { if buffer.len() == 0 { return Self::from_empty_bytes(); } - Ok(Some(try!(T::from_bytes(buffer, length_stack)))) + Ok(Some(T::from_bytes(buffer, length_stack)?)) } fn from_empty_bytes() -> Result { @@ -144,12 +144,12 @@ impl BinaryConvertable for Result<(), E> { fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { match *self { Ok(_) => Err(BinaryConvertError::empty()), - Err(ref e) => Ok(try!(e.to_bytes(buffer, length_stack))), + Err(ref e) => Ok(e.to_bytes(buffer, length_stack)?), } } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { - Ok(Err(try!(E::from_bytes(&buffer, length_stack)))) + Ok(Err(E::from_bytes(&buffer, length_stack)?)) } fn from_empty_bytes() -> Result { @@ -172,13 +172,13 @@ impl BinaryConvertable for Result { fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { match *self { - Ok(ref r) => Ok(try!(r.to_bytes(buffer, length_stack))), + Ok(ref r) => Ok(r.to_bytes(buffer, length_stack)?), Err(_) => Err(BinaryConvertError::empty()), } } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { - Ok(Ok(try!(R::from_bytes(&buffer, length_stack)))) + Ok(Ok(R::from_bytes(&buffer, length_stack)?)) } fn from_empty_bytes() -> Result { @@ -203,14 +203,14 @@ impl BinaryConvertable for Result { buffer[0] = 0; if r.size() > 0 { - Ok(try!(r.to_bytes(&mut buffer[1..], length_stack))) + Ok(r.to_bytes(&mut buffer[1..], length_stack)?) } else { Ok(()) } }, Err(ref e) => { buffer[0] = 1; if e.size() > 0 { - Ok(try!(e.to_bytes(&mut buffer[1..], length_stack))) + Ok(e.to_bytes(&mut buffer[1..], length_stack)?) } else { Ok(()) } }, @@ -221,11 +221,11 @@ impl BinaryConvertable for Result { match buffer.len() { - 1 => Ok(Ok(try!(R::from_empty_bytes()))), - _ => Ok(Ok(try!(R::from_bytes(&buffer[1..], length_stack)))), + 1 => Ok(Ok(R::from_empty_bytes()?)), + _ => Ok(Ok(R::from_bytes(&buffer[1..], length_stack)?)), } } - 1 => Ok(Err(try!(E::from_bytes(&buffer[1..], length_stack)))), + 1 => Ok(Err(E::from_bytes(&buffer[1..], length_stack)?)), _ => Err(BinaryConvertError::variant(buffer[0])) } } @@ -260,13 +260,13 @@ impl BinaryConvertable for BTreeMap where K : BinaryConvertable + Or if key_size > 0 { let item_end = offset + key_size; - try!(key.to_bytes(&mut buffer[offset..item_end], length_stack)); + key.to_bytes(&mut buffer[offset..item_end], length_stack)?; offset = item_end; } if val_size > 0 { let item_end = offset + key_size; - try!(val.to_bytes(&mut buffer[offset..item_end], length_stack)); + val.to_bytes(&mut buffer[offset..item_end], length_stack)?; offset = item_end; } } @@ -282,29 +282,29 @@ impl BinaryConvertable for BTreeMap where K : BinaryConvertable + Or loop { let key_size = match K::len_params() { 0 => mem::size_of::(), - _ => try!(length_stack.pop_front().ok_or(BinaryConvertError::length())), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, }; let key = if key_size == 0 { - try!(K::from_empty_bytes()) + K::from_empty_bytes()? } else { if index + key_size > buffer.len() { return Err(BinaryConvertError::boundaries()) } - try!(K::from_bytes(&buffer[index..index+key_size], length_stack)) + K::from_bytes(&buffer[index..index+key_size], length_stack)? }; index = index + key_size; let val_size = match V::len_params() { 0 => mem::size_of::(), - _ => try!(length_stack.pop_front().ok_or(BinaryConvertError::length())), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, }; let val = if val_size == 0 { - try!(V::from_empty_bytes()) + V::from_empty_bytes()? } else { if index + val_size > buffer.len() { return Err(BinaryConvertError::boundaries()) } - try!(V::from_bytes(&buffer[index..index+val_size], length_stack)) + V::from_bytes(&buffer[index..index+val_size], length_stack)? }; result.insert(key, val); index = index + val_size; @@ -344,7 +344,7 @@ impl BinaryConvertable for VecDeque where T: BinaryConvertable { }; if next_size > 0 { let item_end = offset + next_size; - try!(item.to_bytes(&mut buffer[offset..item_end], length_stack)); + item.to_bytes(&mut buffer[offset..item_end], length_stack)?; offset = item_end; } } @@ -364,16 +364,16 @@ impl BinaryConvertable for VecDeque where T: BinaryConvertable { loop { let next_size = match T::len_params() { 0 => mem::size_of::(), - _ => try!(length_stack.pop_front().ok_or(BinaryConvertError::length())), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, }; let item = if next_size == 0 { - try!(T::from_empty_bytes()) + T::from_empty_bytes()? } else { if index + next_size > buffer.len() { return Err(BinaryConvertError::boundaries()) } - try!(T::from_bytes(&buffer[index..index+next_size], length_stack)) + T::from_bytes(&buffer[index..index+next_size], length_stack)? }; result.push_back(item); @@ -413,7 +413,7 @@ impl BinaryConvertable for Vec where T: BinaryConvertable { }; if next_size > 0 { let item_end = offset + next_size; - try!(item.to_bytes(&mut buffer[offset..item_end], length_stack)); + item.to_bytes(&mut buffer[offset..item_end], length_stack)?; offset = item_end; } } @@ -433,16 +433,16 @@ impl BinaryConvertable for Vec where T: BinaryConvertable { loop { let next_size = match T::len_params() { 0 => mem::size_of::(), - _ => try!(length_stack.pop_front().ok_or(BinaryConvertError::length())), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, }; let item = if next_size == 0 { - try!(T::from_empty_bytes()) + T::from_empty_bytes()? } else { if index + next_size > buffer.len() { return Err(BinaryConvertError::boundaries()) } - try!(T::from_bytes(&buffer[index..index+next_size], length_stack)) + T::from_bytes(&buffer[index..index+next_size], length_stack)? }; result.push(item); @@ -498,13 +498,13 @@ impl BinaryConvertable for Range where T: BinaryConvertable { } fn to_bytes(&self, buffer: &mut[u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { - try!(self.start.to_bytes(&mut buffer[..mem::size_of::()], length_stack)); - try!(self.end.to_bytes(&mut buffer[mem::size_of::() + 1..], length_stack)); + self.start.to_bytes(&mut buffer[..mem::size_of::()], length_stack)?; + self.end.to_bytes(&mut buffer[mem::size_of::() + 1..], length_stack)?; Ok(()) } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { - Ok(try!(T::from_bytes(&buffer[..mem::size_of::()], length_stack))..try!(T::from_bytes(&buffer[mem::size_of::()+1..], length_stack))) + Ok(T::from_bytes(&buffer[..mem::size_of::()], length_stack)?..T::from_bytes(&buffer[mem::size_of::()+1..], length_stack)?) } fn len_params() -> usize { @@ -519,15 +519,15 @@ impl BinaryConvertable for ::std::cell::RefCell where T: BinaryConvertable } fn from_empty_bytes() -> Result { - Ok(::std::cell::RefCell::new(try!(T::from_empty_bytes()))) + Ok(::std::cell::RefCell::new(T::from_empty_bytes()?)) } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { - Ok(::std::cell::RefCell::new(try!(T::from_bytes(buffer, length_stack)))) + Ok(::std::cell::RefCell::new(T::from_bytes(buffer, length_stack)?)) } fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { - try!(self.borrow().to_bytes(buffer, length_stack)); + self.borrow().to_bytes(buffer, length_stack)?; Ok(()) } @@ -542,15 +542,15 @@ impl BinaryConvertable for ::std::cell::Cell where T: BinaryConvertable + } fn from_empty_bytes() -> Result { - Ok(::std::cell::Cell::new(try!(T::from_empty_bytes()))) + Ok(::std::cell::Cell::new(T::from_empty_bytes()?)) } fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { - Ok(::std::cell::Cell::new(try!(T::from_bytes(buffer, length_stack)))) + Ok(::std::cell::Cell::new(T::from_bytes(buffer, length_stack)?)) } fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { - try!(self.get().to_bytes(buffer, length_stack)); + self.get().to_bytes(buffer, length_stack)?; Ok(()) } @@ -596,34 +596,33 @@ pub fn deserialize_from(r: &mut R) -> Result let fixed_size = mem::size_of::(); let mut payload_buffer = Vec::with_capacity(fixed_size); unsafe { payload_buffer.set_len(fixed_size); } - let bytes_read = try!(r.read(&mut payload_buffer)); + let bytes_read = r.read(&mut payload_buffer)?; if bytes_read != mem::size_of::() { return Err(BinaryError::Serialization(BinaryConvertError::size(fixed_size, bytes_read))) } - Ok(try!(T::from_bytes(&payload_buffer[..], &mut fake_stack))) + Ok(T::from_bytes(&payload_buffer[..], &mut fake_stack)?) }, _ => { let mut payload = Vec::new(); - try!(r.read_to_end(&mut payload)); + r.read_to_end(&mut payload)?; - let stack_len = try!(u64::from_bytes(&payload[0..8], &mut fake_stack)) as usize; + let stack_len = u64::from_bytes(&payload[0..8], &mut fake_stack)? as usize; let mut length_stack = VecDeque::::with_capacity(stack_len); if stack_len > 0 { for idx in 0..stack_len { - let stack_item = try!(u64::from_bytes(&payload[8 + idx*8..8 + (idx+1)*8], &mut fake_stack)); + let stack_item = u64::from_bytes(&payload[8 + idx*8..8 + (idx+1)*8], &mut fake_stack)?; length_stack.push_back(stack_item as usize); } } - //try!(r.read(&mut size_buffer).map_err(|_| BinaryConvertError)); - let size = try!(u64::from_bytes(&payload[8+stack_len*8..16+stack_len*8], &mut fake_stack)) as usize; + let size = u64::from_bytes(&payload[8+stack_len*8..16+stack_len*8], &mut fake_stack)? as usize; match size { 0 => { - Ok(try!(T::from_empty_bytes())) + Ok(T::from_empty_bytes()?) }, _ => { - Ok(try!(T::from_bytes(&payload[16+stack_len*8..], &mut length_stack))) + Ok(T::from_bytes(&payload[16+stack_len*8..], &mut length_stack)?) } } }, @@ -647,8 +646,8 @@ pub fn serialize_into(t: &T, w: &mut W) -> Result<(), BinaryError> let fixed_size = mem::size_of::(); let mut buffer = Vec::with_capacity(fixed_size); unsafe { buffer.set_len(fixed_size); } - try!(t.to_bytes(&mut buffer[..], &mut fake_stack)); - try!(w.write(&buffer[..])); + t.to_bytes(&mut buffer[..], &mut fake_stack)?; + w.write(&buffer[..])?; Ok(()) }, _ => { @@ -657,37 +656,37 @@ pub fn serialize_into(t: &T, w: &mut W) -> Result<(), BinaryError> let size = t.size(); if size == 0 { - try!(w.write(&size_buffer)); - try!(w.write(&size_buffer)); + w.write(&size_buffer)?; + w.write(&size_buffer)?; return Ok(()); } let mut buffer = Vec::with_capacity(size); unsafe { buffer.set_len(size); } - try!(t.to_bytes(&mut buffer[..], &mut length_stack)); + t.to_bytes(&mut buffer[..], &mut length_stack)?; let stack_len = length_stack.len(); - try!((stack_len as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)); - try!(w.write(&size_buffer[..])); + (stack_len as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)?; + w.write(&size_buffer[..])?; if stack_len > 0 { let mut header_buffer = Vec::with_capacity(stack_len * 8); unsafe { header_buffer.set_len(stack_len * 8); }; - try!((stack_len as u64).to_bytes(&mut header_buffer[0..8], &mut fake_stack)); + (stack_len as u64).to_bytes(&mut header_buffer[0..8], &mut fake_stack)?; let mut idx = 0; loop { match length_stack.pop_front() { - Some(val) => try!((val as u64).to_bytes(&mut header_buffer[idx * 8..(idx+1) * 8], &mut fake_stack)), + Some(val) => (val as u64).to_bytes(&mut header_buffer[idx * 8..(idx+1) * 8], &mut fake_stack)?, None => { break; } } idx = idx + 1; } - try!(w.write(&header_buffer[..])); + w.write(&header_buffer[..])?; } - try!((size as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)); - try!(w.write(&size_buffer[..])); + (size as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)?; + w.write(&size_buffer[..])?; - try!(w.write(&buffer[..])); + w.write(&buffer[..])?; Ok(()) }, @@ -697,7 +696,7 @@ pub fn serialize_into(t: &T, w: &mut W) -> Result<(), BinaryError> pub fn serialize(t: &T) -> Result, BinaryError> { use std::io::Cursor; let mut buff = Cursor::new(Vec::new()); - try!(serialize_into(t, &mut buff)); + serialize_into(t, &mut buff)?; let into_inner = buff.into_inner(); Ok(into_inner) } diff --git a/ipc/tests/binary.rs.in b/ipc/tests/binary.rs.in index 34e33609f..513007b22 100644 --- a/ipc/tests/binary.rs.in +++ b/ipc/tests/binary.rs.in @@ -17,37 +17,40 @@ use util::Bytes; -#[derive(Binary)] +#[binary] pub enum Root { Top, Middle(u32, u64), } -#[derive(Binary, PartialEq, Debug)] +#[derive(PartialEq, Debug)] +#[binary] pub struct DoubleRoot { pub x1: u32, pub x2: u64, pub x3: u32, } -#[derive(Binary, PartialEq, Debug)] +#[derive(PartialEq, Debug)] +#[binary] pub struct ReferenceStruct<'a> { pub ref_data: &'a u64, } -#[derive(Binary, PartialEq, Debug)] +#[derive(PartialEq, Debug)] +#[binary] pub enum EnumWithStruct { Left, Right { how_much: u64 }, } -#[derive(Binary)] +#[binary] pub struct TwoVec { v1: Vec, v2: Vec, } -#[derive(Binary)] +#[binary] struct ChunkSet { items: Vec, } diff --git a/ipc/tests/nested.rs.in b/ipc/tests/nested.rs.in index a12371ecf..763cd9e71 100644 --- a/ipc/tests/nested.rs.in +++ b/ipc/tests/nested.rs.in @@ -30,7 +30,7 @@ pub trait DBWriter { impl IpcConfig for DBWriter {} -#[derive(Binary)] +#[binary] pub enum DBError { Write, Read } #[ipc] diff --git a/ipc/tests/service.rs.in b/ipc/tests/service.rs.in index 9bfe93cb3..84cc38427 100644 --- a/ipc/tests/service.rs.in +++ b/ipc/tests/service.rs.in @@ -22,7 +22,7 @@ pub struct Service { pub rollbacks: RwLock, } -#[derive(Binary)] +#[binary] pub struct CustomData { pub a: u64, pub b: u64, diff --git a/js/.eslintrc.json b/js/.eslintrc.json index 198750580..686745982 100644 --- a/js/.eslintrc.json +++ b/js/.eslintrc.json @@ -11,11 +11,12 @@ "FileReader": true }, "rules": { - "object-curly-spacing": ["error", "always"], - "no-debugger": "error", - "no-alert": "error", + "curly": ["error", "all"], "jsx-quotes": ["error", "prefer-single"], - "react/jsx-curly-spacing": ["error", "always"], - "object-property-newline": 0 + "no-alert": "error", + "no-debugger": "error", + "object-curly-spacing": ["error", "always"], + "object-property-newline": 0, + "react/jsx-curly-spacing": ["error", "always"] } } diff --git a/js/package.json b/js/package.json index df59d43d9..294278384 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.133", + "version": "0.2.163", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -26,9 +26,9 @@ ], "scripts": { "build": "npm run build:lib && npm run build:dll && npm run build:app", - "build:app": "webpack --config webpack/app --progress", - "build:lib": "webpack --config webpack/libraries --progress", - "build:dll": "webpack --config webpack/vendor --progress", + "build:app": "webpack --config webpack/app", + "build:lib": "webpack --config webpack/libraries", + "build:dll": "webpack --config webpack/vendor", "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app", "ci:build:app": "NODE_ENV=production webpack --config webpack/app", "ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries", @@ -51,19 +51,19 @@ }, "devDependencies": { "babel-cli": "6.18.0", - "babel-core": "6.20.0", + "babel-core": "6.21.0", "babel-eslint": "7.1.1", - "babel-loader": "6.2.8", - "babel-plugin-lodash": "3.2.10", + "babel-loader": "6.2.10", + "babel-plugin-lodash": "3.2.11", "babel-plugin-react-intl": "2.2.0", - "babel-plugin-transform-class-properties": "6.18.0", + "babel-plugin-transform-class-properties": "6.19.0", "babel-plugin-transform-decorators-legacy": "1.3.4", "babel-plugin-transform-object-rest-spread": "6.20.2", "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.15.0", "babel-plugin-webpack-alias": "2.1.2", "babel-polyfill": "6.20.0", - "babel-preset-env": "1.0.2", + "babel-preset-env": "1.1.4", "babel-preset-es2015": "6.18.0", "babel-preset-es2016": "6.16.0", "babel-preset-es2017": "6.16.0", @@ -80,56 +80,58 @@ "coveralls": "2.11.15", "css-loader": "0.26.1", "ejs-loader": "0.3.0", - "enzyme": "2.6.0", + "enzyme": "2.7.0", "eslint": "3.11.1", "eslint-config-semistandard": "7.0.0", "eslint-config-standard": "6.2.1", "eslint-config-standard-react": "4.2.0", "eslint-plugin-promise": "3.4.0", - "eslint-plugin-react": "6.7.1", + "eslint-plugin-react": "6.8.0", "eslint-plugin-standard": "2.0.1", "express": "4.14.0", "extract-loader": "0.1.0", "extract-text-webpack-plugin": "2.0.0-beta.4", "file-loader": "0.9.0", - "happypack": "3.0.0", + "happypack": "3.0.2", "html-loader": "0.4.4", "html-webpack-plugin": "2.24.1", - "http-proxy-middleware": "0.17.2", + "http-proxy-middleware": "0.17.3", "husky": "0.11.9", "ignore-styles": "5.0.1", - "image-webpack-loader": "3.0.0", + "image-webpack-loader": "3.1.0", "istanbul": "1.0.0-alpha.2", - "jsdom": "9.8.3", + "jsdom": "9.9.1", "json-loader": "0.5.4", "mocha": "3.2.0", "mock-local-storage": "1.0.2", - "mock-socket": "6.0.3", + "mock-socket": "6.0.4", "nock": "9.0.2", "postcss-import": "9.0.0", - "postcss-loader": "1.2.0", + "postcss-loader": "1.2.1", "postcss-nested": "1.0.0", "postcss-simple-vars": "3.0.0", "progress": "1.1.8", + "progress-bar-webpack-plugin": "1.9.1", "raw-loader": "0.5.1", "react-addons-perf": "15.4.1", "react-addons-test-utils": "15.4.1", "react-hot-loader": "3.0.0-beta.6", "react-intl-aggregate-webpack-plugin": "0.0.1", "rucksack-css": "0.9.1", + "script-ext-html-webpack-plugin": "1.3.5", "serviceworker-webpack-plugin": "0.1.7", "sinon": "1.17.6", "sinon-as-promised": "4.0.2", "sinon-chai": "2.8.0", "style-loader": "0.13.1", - "stylelint": "7.6.0", - "stylelint-config-standard": "15.0.0", + "stylelint": "7.7.0", + "stylelint-config-standard": "15.0.1", "url-loader": "0.5.7", - "webpack": "2.1.0-beta.27", - "webpack-dev-middleware": "1.8.4", + "webpack": "2.2.0-rc.2", + "webpack-dev-middleware": "1.9.0", "webpack-error-notification": "0.1.6", - "webpack-hot-middleware": "2.13.2", - "websocket": "1.0.23" + "webpack-hot-middleware": "2.14.0", + "websocket": "1.0.24" }, "dependencies": { "bignumber.js": "3.0.1", @@ -167,6 +169,7 @@ "react-dom": "15.4.1", "react-dropzone": "3.7.3", "react-intl": "2.1.5", + "react-portal": "3.0.0", "react-redux": "4.4.6", "react-router": "3.0.0", "react-router-redux": "4.0.7", diff --git a/js/scripts/test.js b/js/scripts/test.js index 78f2f99bd..f7bfd140f 100644 --- a/js/scripts/test.js +++ b/js/scripts/test.js @@ -1 +1 @@ -// test script 6 +// test script 7 diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js index 5b81f3f95..ca1b04138 100644 --- a/js/src/3rdparty/email-verification/index.js +++ b/js/src/3rdparty/email-verification/index.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/email-verification/terms-of-service.js b/js/src/3rdparty/email-verification/terms-of-service.js index 263b7e8f0..4a1c7f1d9 100644 --- a/js/src/3rdparty/email-verification/terms-of-service.js +++ b/js/src/3rdparty/email-verification/terms-of-service.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify @@ -18,6 +18,10 @@ import React from 'react'; export default ( ); diff --git a/js/src/3rdparty/etherscan/account.js b/js/src/3rdparty/etherscan/account.js index cb99c22c6..7b8c431a0 100644 --- a/js/src/3rdparty/etherscan/account.js +++ b/js/src/3rdparty/etherscan/account.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/account.spec.js b/js/src/3rdparty/etherscan/account.spec.js index 9dc36c254..aef48a93c 100644 --- a/js/src/3rdparty/etherscan/account.spec.js +++ b/js/src/3rdparty/etherscan/account.spec.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/call.js b/js/src/3rdparty/etherscan/call.js index 5c6cd5945..69c45a902 100644 --- a/js/src/3rdparty/etherscan/call.js +++ b/js/src/3rdparty/etherscan/call.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/index.js b/js/src/3rdparty/etherscan/index.js index ada1503cd..34b88bb68 100644 --- a/js/src/3rdparty/etherscan/index.js +++ b/js/src/3rdparty/etherscan/index.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js index e85572850..158939356 100644 --- a/js/src/3rdparty/etherscan/links.js +++ b/js/src/3rdparty/etherscan/links.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/stats.js b/js/src/3rdparty/etherscan/stats.js index ecefefe7d..02a8027b2 100644 --- a/js/src/3rdparty/etherscan/stats.js +++ b/js/src/3rdparty/etherscan/stats.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/etherscan/stats.spec.js b/js/src/3rdparty/etherscan/stats.spec.js index 62152b6be..0f4d1a4a1 100644 --- a/js/src/3rdparty/etherscan/stats.spec.js +++ b/js/src/3rdparty/etherscan/stats.spec.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/helpers.spec.js b/js/src/3rdparty/shapeshift/helpers.spec.js index f2ea0f3f9..8ccec6791 100644 --- a/js/src/3rdparty/shapeshift/helpers.spec.js +++ b/js/src/3rdparty/shapeshift/helpers.spec.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/index.js b/js/src/3rdparty/shapeshift/index.js index 5dd91e4f4..a6c62320e 100644 --- a/js/src/3rdparty/shapeshift/index.js +++ b/js/src/3rdparty/shapeshift/index.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/rpc.js b/js/src/3rdparty/shapeshift/rpc.js index 919f0b07f..e3f1f58bd 100644 --- a/js/src/3rdparty/shapeshift/rpc.js +++ b/js/src/3rdparty/shapeshift/rpc.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/rpc.spec.js b/js/src/3rdparty/shapeshift/rpc.spec.js index 47d4e0052..582b77a53 100644 --- a/js/src/3rdparty/shapeshift/rpc.spec.js +++ b/js/src/3rdparty/shapeshift/rpc.spec.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/shapeshift.js b/js/src/3rdparty/shapeshift/shapeshift.js index 5743fcac1..39b8365ec 100644 --- a/js/src/3rdparty/shapeshift/shapeshift.js +++ b/js/src/3rdparty/shapeshift/shapeshift.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/shapeshift/shapeshift.spec.js b/js/src/3rdparty/shapeshift/shapeshift.spec.js index 01180e130..9fe2ca1f0 100644 --- a/js/src/3rdparty/shapeshift/shapeshift.spec.js +++ b/js/src/3rdparty/shapeshift/shapeshift.spec.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index 65761223b..23e978972 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/3rdparty/sms-verification/terms-of-service.js b/js/src/3rdparty/sms-verification/terms-of-service.js index f61b3c97d..d0bffb6bc 100644 --- a/js/src/3rdparty/sms-verification/terms-of-service.js +++ b/js/src/3rdparty/sms-verification/terms-of-service.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify diff --git a/js/src/abi/encoder/encoder.js b/js/src/abi/encoder/encoder.js index 8634b9511..6797e978d 100644 --- a/js/src/abi/encoder/encoder.js +++ b/js/src/abi/encoder/encoder.js @@ -25,7 +25,7 @@ export default class Encoder { throw new Error('tokens should be array of Token'); } - const mediates = tokens.map((token) => Encoder.encodeToken(token)); + const mediates = tokens.map((token, index) => Encoder.encodeToken(token, index)); const inits = mediates .map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx))) .join(''); @@ -36,37 +36,40 @@ export default class Encoder { return `${inits}${closings}`; } - static encodeToken (token) { + static encodeToken (token, index = 0) { if (!isInstanceOf(token, Token)) { throw new Error('token should be instanceof Token'); } - switch (token.type) { - case 'address': - return new Mediate('raw', padAddress(token.value)); + try { + switch (token.type) { + case 'address': + return new Mediate('raw', padAddress(token.value)); - case 'int': - case 'uint': - return new Mediate('raw', padU32(token.value)); + case 'int': + case 'uint': + return new Mediate('raw', padU32(token.value)); - case 'bool': - return new Mediate('raw', padBool(token.value)); + case 'bool': + return new Mediate('raw', padBool(token.value)); - case 'fixedBytes': - return new Mediate('raw', padFixedBytes(token.value)); + case 'fixedBytes': + return new Mediate('raw', padFixedBytes(token.value)); - case 'bytes': - return new Mediate('prefixed', padBytes(token.value)); + case 'bytes': + return new Mediate('prefixed', padBytes(token.value)); - case 'string': - return new Mediate('prefixed', padString(token.value)); + case 'string': + return new Mediate('prefixed', padString(token.value)); - case 'fixedArray': - case 'array': - return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token))); - - default: - throw new Error(`Invalid token type ${token.type} in encodeToken`); + case 'fixedArray': + case 'array': + return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token))); + } + } catch (e) { + throw new Error(`Cannot encode token #${index} [${token.type}: ${token.value}]. ${e.message}`); } + + throw new Error(`Invalid token type ${token.type} in encodeToken`); } } diff --git a/js/src/abi/spec/interface.js b/js/src/abi/spec/interface.js index 9116f5ca3..3e1b5de4d 100644 --- a/js/src/abi/spec/interface.js +++ b/js/src/abi/spec/interface.js @@ -41,6 +41,10 @@ export default class Interface { } encodeTokens (paramTypes, values) { + return Interface.encodeTokens(paramTypes, values); + } + + static encodeTokens (paramTypes, values) { const createToken = function (paramType, value) { if (paramType.subtype) { return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry))); diff --git a/js/src/api/api.js b/js/src/api/api.js index 9e0ad11b8..bb622ab46 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -114,7 +114,11 @@ export default class Api { } }) .catch((error) => { - console.error('pollMethod', error); + // Don't print if the request is rejected: that's ok + if (error.type !== 'REQUEST_REJECTED') { + console.error('pollMethod', error); + } + reject(error); }); }; diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 68c0371a1..af22191e5 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import Abi from '../../abi'; +import Abi from '~/abi'; let nextSubscriptionId = 0; @@ -53,6 +53,10 @@ export default class Contract { this._subscribedToBlock = false; this._blockSubscriptionId = null; + + if (api && api.patch && api.patch.contract) { + api.patch.contract(this); + } } get address () { @@ -89,9 +93,17 @@ export default class Contract { return this; } - deploy (options, values, statecb) { - let gas; + deployEstimateGas (options, values) { + const _options = this._encodeOptions(this.constructors[0], options, values); + return this._api.eth + .estimateGas(_options) + .then((gasEst) => { + return [gasEst, gasEst.mul(1.2)]; + }); + } + + deploy (options, values, statecb) { const setState = (state) => { if (!statecb) { return; @@ -102,31 +114,34 @@ export default class Contract { setState({ state: 'estimateGas' }); - return this._api.eth - .estimateGas(this._encodeOptions(this.constructors[0], options, values)) - .then((_gas) => { - gas = _gas.mul(1.2); + return this + .deployEstimateGas(options, values) + .then(([gasEst, gas]) => { options.gas = gas.toFixed(0); setState({ state: 'postTransaction', gas }); - return this._api.parity.postTransaction(this._encodeOptions(this.constructors[0], options, values)); - }) - .then((requestId) => { - setState({ state: 'checkRequest', requestId }); - return this._pollCheckRequest(requestId); - }) - .then((txhash) => { - setState({ state: 'getTransactionReceipt', txhash }); - return this._pollTransactionReceipt(txhash, gas); - }) - .then((receipt) => { - if (receipt.gasUsed.eq(gas)) { - throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); - } - setState({ state: 'hasReceipt', receipt }); - this._address = receipt.contractAddress; - return this._address; + const _options = this._encodeOptions(this.constructors[0], options, values); + + return this._api.parity + .postTransaction(_options) + .then((requestId) => { + setState({ state: 'checkRequest', requestId }); + return this._pollCheckRequest(requestId); + }) + .then((txhash) => { + setState({ state: 'getTransactionReceipt', txhash }); + return this._pollTransactionReceipt(txhash, gas); + }) + .then((receipt) => { + if (receipt.gasUsed.eq(gas)) { + throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); + } + + setState({ state: 'hasReceipt', receipt }); + this._address = receipt.contractAddress; + return this._address; + }); }) .then((address) => { setState({ state: 'getCode' }); @@ -192,7 +207,7 @@ export default class Contract { getCallData = (func, options, values) => { let data = options.data; - const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null; + const tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null; const call = tokens ? func.encodeCall(tokens) : null; if (data && data.substr(0, 2) === '0x') { @@ -214,6 +229,8 @@ export default class Contract { } _bindFunction = (func) => { + func.contract = this; + func.call = (options, values = []) => { const callParams = this._encodeOptions(func, this._addOptionsTo(options), values); @@ -226,13 +243,13 @@ export default class Contract { if (!func.constant) { func.postTransaction = (options, values = []) => { - return this._api.parity - .postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values)); + const _options = this._encodeOptions(func, this._addOptionsTo(options), values); + return this._api.parity.postTransaction(_options); }; func.estimateGas = (options, values = []) => { - return this._api.eth - .estimateGas(this._encodeOptions(func, this._addOptionsTo(options), values)); + const _options = this._encodeOptions(func, this._addOptionsTo(options), values); + return this._api.eth.estimateGas(_options); }; } diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index 4cbcbdce4..7a41da109 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -24,6 +24,10 @@ export function inAddress (address) { return inHex(address); } +export function inAddresses (addresses) { + return (addresses || []).map(inAddress); +} + export function inBlockNumber (blockNumber) { if (isString(blockNumber)) { switch (blockNumber) { @@ -137,6 +141,10 @@ export function inOptions (options) { options[key] = inNumber16((new BigNumber(options[key])).round()); break; + case 'minBlock': + options[key] = options[key] ? inNumber16(options[key]) : null; + break; + case 'value': case 'nonce': options[key] = inNumber16(options[key]); diff --git a/js/src/api/format/input.spec.js b/js/src/api/format/input.spec.js index 27d200b93..699ac93c2 100644 --- a/js/src/api/format/input.spec.js +++ b/js/src/api/format/input.spec.js @@ -204,7 +204,7 @@ describe('api/format/input', () => { }); }); - ['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => { + ['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => { it(`formats ${input} number as hexnumber`, () => { const block = {}; block[input] = 0x123; @@ -214,6 +214,10 @@ describe('api/format/input', () => { }); }); + it('passes minBlock as null when specified as such', () => { + expect(inOptions({ minBlock: null })).to.deep.equal({ minBlock: null }); + }); + it('ignores and passes through unknown keys', () => { expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); }); diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index ca1d54ede..cf707cbd1 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -19,25 +19,33 @@ import BigNumber from 'bignumber.js'; import { toChecksumAddress } from '../../abi/util/address'; export function outAccountInfo (infos) { - const ret = {}; + return Object + .keys(infos) + .reduce((ret, _address) => { + const info = infos[_address]; + const address = outAddress(_address); - Object.keys(infos).forEach((address) => { - const info = infos[address]; + ret[address] = { + name: info.name + }; - ret[outAddress(address)] = { - name: info.name, - uuid: info.uuid, - meta: JSON.parse(info.meta) - }; - }); + if (info.meta) { + ret[address].uuid = info.uuid; + ret[address].meta = JSON.parse(info.meta); + } - return ret; + return ret; + }, {}); } export function outAddress (address) { return toChecksumAddress(address); } +export function outAddresses (addresses) { + return (addresses || []).map(outAddress); +} + export function outBlock (block) { if (block) { Object.keys(block).forEach((key) => { @@ -66,6 +74,20 @@ export function outBlock (block) { return block; } +export function outChainStatus (status) { + if (status) { + Object.keys(status).forEach((key) => { + switch (key) { + case 'blockGap': + status[key] = status[key].map(outNumber); + break; + } + }); + } + + return status; +} + export function outDate (date) { return new Date(outNumber(date).toNumber() * 1000); } @@ -77,6 +99,7 @@ export function outHistogram (histogram) { case 'bucketBounds': case 'counts': histogram[key] = histogram[key].map(outNumber); + break; } }); } @@ -190,6 +213,10 @@ export function outTransaction (tx) { tx[key] = outNumber(tx[key]); break; + case 'minBlock': + tx[key] = tx[key] ? outNumber(tx[key]) : null; + break; + case 'creates': case 'from': case 'to': diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index 1958b57d8..cdf96601e 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import { outBlock, outAccountInfo, outAddress, outDate, outHistogram, outNumber, outPeers, outReceipt, outSyncing, outTransaction, outTrace } from './output'; +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeers, outReceipt, outSyncing, outTransaction, outTrace } from './output'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; describe('api/format/output', () => { @@ -35,6 +35,14 @@ describe('api/format/output', () => { } }); }); + + it('returns objects without meta & uuid as required', () => { + expect(outAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { name: 'name' } } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { name: 'name' } + }); + }); }); describe('outAddress', () => { @@ -114,6 +122,18 @@ describe('api/format/output', () => { }); }); + describe('outChainStatus', () => { + it('formats blockGap values', () => { + const status = { + blockGap: [0x1234, '0x5678'] + }; + + expect(outChainStatus(status)).to.deep.equal({ + blockGap: [new BigNumber(0x1234), new BigNumber(0x5678)] + }); + }); + }); + describe('outDate', () => { it('converts a second date in unix timestamp', () => { expect(outDate(0x57513668)).to.deep.equal(new Date('2016-06-03T07:48:56.000Z')); @@ -271,7 +291,7 @@ describe('api/format/output', () => { }); }); - ['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => { + ['blockNumber', 'gasPrice', 'gas', 'minBlock', 'nonce', 'transactionIndex', 'value'].forEach((input) => { it(`formats ${input} number as hexnumber`, () => { const block = {}; block[input] = 0x123; @@ -282,6 +302,10 @@ describe('api/format/output', () => { }); }); + it('passes minBlock as null when null', () => { + expect(outTransaction({ minBlock: null })).to.deep.equal({ minBlock: null }); + }); + it('ignores and passes through unknown keys', () => { expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); }); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 2605e41e5..174ad3fbf 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input'; -import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output'; +import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions } from '../../format/input'; +import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output'; export default class Parity { constructor (transport) { @@ -27,23 +27,29 @@ export default class Parity { .execute('parity_acceptNonReservedPeers'); } - accounts () { - return this._transport - .execute('parity_accounts') - .then(outAccountInfo); - } - accountsInfo () { return this._transport .execute('parity_accountsInfo') .then(outAccountInfo); } + allAccountsInfo () { + return this._transport + .execute('parity_allAccountsInfo') + .then(outAccountInfo); + } + addReservedPeer (encode) { return this._transport .execute('parity_addReservedPeer', encode); } + chainStatus () { + return this._transport + .execute('parity_chainStatus') + .then(outChainStatus); + } + changePassword (account, password, newPassword) { return this._transport .execute('parity_changePassword', inAddress(account), password, newPassword); @@ -122,6 +128,18 @@ export default class Parity { .execute('parity_generateSecretPhrase'); } + getDappsAddresses (dappId) { + return this._transport + .execute('parity_getDappsAddresses', dappId) + .then(outAddresses); + } + + getNewDappsWhitelist () { + return this._transport + .execute('parity_getNewDappsWhitelist') + .then((addresses) => addresses ? addresses.map(outAddress) : null); + } + hashContent (url) { return this._transport .execute('parity_hashContent', url); @@ -129,8 +147,8 @@ export default class Parity { importGethAccounts (accounts) { return this._transport - .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) - .then((accounts) => (accounts || []).map(outAddress)); + .execute('parity_importGethAccounts', inAddresses) + .then(outAddresses); } killAccount (account, password) { @@ -138,6 +156,11 @@ export default class Parity { .execute('parity_killAccount', inAddress(account), password); } + listRecentDapps () { + return this._transport + .execute('parity_listRecentDapps'); + } + removeAddress (address) { return this._transport .execute('parity_removeAddress', inAddress(address)); @@ -146,7 +169,7 @@ export default class Parity { listGethAccounts () { return this._transport .execute('parity_listGethAccounts') - .then((accounts) => (accounts || []).map(outAddress)); + .then(outAddresses); } localTransactions () { @@ -283,6 +306,11 @@ export default class Parity { .execute('parity_setAuthor', inAddress(address)); } + setDappsAddresses (dappId, addresses) { + return this._transport + .execute('parity_setDappsAddresses', dappId, inAddresses(addresses)); + } + setExtraData (data) { return this._transport .execute('parity_setExtraData', inData(data)); @@ -303,6 +331,11 @@ export default class Parity { .execute('parity_setMode', mode); } + setNewDappsWhitelist (addresses) { + return this._transport + .execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null); + } + setTransactionsLimit (quantity) { return this._transport .execute('parity_setTransactionsLimit', inNumber16(quantity)); diff --git a/js/src/api/rpc/parity/parity.spec.js b/js/src/api/rpc/parity/parity.spec.js index b58c8f85c..6b15a8fe1 100644 --- a/js/src/api/rpc/parity/parity.spec.js +++ b/js/src/api/rpc/parity/parity.spec.js @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import BigNumber from 'bignumber.js'; import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import { isBigNumber } from '../../../../test/types'; @@ -45,6 +46,22 @@ describe('api/rpc/parity', () => { }); }); + describe('chainStatus', () => { + it('retrieves the chain status', () => { + mockHttp([{ method: 'parity_chainStatus', reply: { + result: { + 'blockGap': [0x123, 0x456] + } + } }]); + + return instance.chainStatus().then((result) => { + expect(result).to.deep.equal({ + 'blockGap': [new BigNumber(0x123), new BigNumber(0x456)] + }); + }); + }); + }); + describe('gasFloorTarget', () => { it('returns the gasfloor, formatted', () => { mockHttp([{ method: 'parity_gasFloorTarget', reply: { result: '0x123456' } }]); diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js index 403da6c0a..44afa2f6e 100644 --- a/js/src/api/rpc/signer/signer.js +++ b/js/src/api/rpc/signer/signer.js @@ -37,6 +37,11 @@ export default class Signer { .execute('signer_generateAuthorizationToken'); } + generateWebProxyAccessToken () { + return this._transport + .execute('signer_generateWebProxyAccessToken'); + } + rejectRequest (requestId) { return this._transport .execute('signer_rejectRequest', inNumber16(requestId)); diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 6485a665c..7a7a61bfa 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -24,7 +24,7 @@ import Signer from './signer'; const events = { 'logging': { module: 'logging' }, 'eth_blockNumber': { module: 'eth' }, - 'parity_accountsInfo': { module: 'personal' }, + 'parity_allAccountsInfo': { module: 'personal' }, 'eth_accounts': { module: 'personal' }, 'signer_requestsToConfirm': { module: 'signer' } }; diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index e00169b6a..5e3280424 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -46,9 +46,9 @@ export default class Personal { _accountsInfo = () => { return this._api.parity - .accountsInfo() + .allAccountsInfo() .then((info) => { - this._updateSubscriptions('parity_accountsInfo', null, info); + this._updateSubscriptions('parity_allAccountsInfo', null, info); }); } diff --git a/js/src/api/subscriptions/personal.spec.js b/js/src/api/subscriptions/personal.spec.js index c7d8da2b9..b00354f64 100644 --- a/js/src/api/subscriptions/personal.spec.js +++ b/js/src/api/subscriptions/personal.spec.js @@ -28,16 +28,16 @@ const TEST_LIST = ['0xfa64203C044691aA57251aF95f4b48d85eC00Dd5']; function stubApi (accounts, info) { const _calls = { - accountsInfo: [], + allAccountsInfo: [], listAccounts: [] }; return { _calls, parity: { - accountsInfo: () => { + allAccountsInfo: () => { const stub = sinon.stub().resolves(info || TEST_INFO)(); - _calls.accountsInfo.push(stub); + _calls.allAccountsInfo.push(stub); return stub; } }, @@ -86,8 +86,8 @@ describe('api/subscriptions/personal', () => { expect(personal.isStarted).to.be.true; }); - it('calls parity_accountsInfo', () => { - expect(api._calls.accountsInfo.length).to.be.ok; + it('calls parity_allAccountsInfo', () => { + expect(api._calls.allAccountsInfo.length).to.be.ok; }); it('calls eth_accounts', () => { @@ -96,7 +96,7 @@ describe('api/subscriptions/personal', () => { it('updates subscribers', () => { expect(cb.firstCall).to.have.been.calledWith('eth_accounts', null, TEST_LIST); - expect(cb.secondCall).to.have.been.calledWith('parity_accountsInfo', null, TEST_INFO); + expect(cb.secondCall).to.have.been.calledWith('parity_allAccountsInfo', null, TEST_INFO); }); }); @@ -112,7 +112,7 @@ describe('api/subscriptions/personal', () => { }); it('calls personal_accountsInfo', () => { - expect(api._calls.accountsInfo.length).to.be.ok; + expect(api._calls.allAccountsInfo.length).to.be.ok; }); it('calls personal_listAccounts', () => { diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 2de500309..591cf3062 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -22,7 +22,7 @@ import TransportError from '../error'; /* global WebSocket */ export default class Ws extends JsonRpcBase { - constructor (url, token) { + constructor (url, token, connect = true) { super(); this._url = url; @@ -32,23 +32,34 @@ export default class Ws extends JsonRpcBase { this._connecting = false; this._connected = false; this._lastError = null; - this._autoConnect = true; + this._autoConnect = false; this._retries = 0; this._reconnectTimeoutId = null; - this._connect(); + this._connectPromise = null; + this._connectPromiseFunctions = {}; + + if (connect) { + this.connect(); + } } - updateToken (token) { + updateToken (token, connect = true) { this._token = token; - this._autoConnect = true; + // this._autoConnect = true; - this._connect(); + if (connect) { + this.connect(); + } } - _connect () { + connect () { + if (this._connected) { + return Promise.resolve(); + } + if (this._connecting) { - return; + return this._connectPromise || Promise.resolve(); } if (this._reconnectTimeoutId) { @@ -104,10 +115,17 @@ export default class Ws extends JsonRpcBase { window._parityWS = this; } + + this._connectPromise = new Promise((resolve, reject) => { + this._connectPromiseFunctions = { resolve, reject }; + }); + + return this._connectPromise; } _onOpen = (event) => { - console.log('ws:onOpen', event); + console.log('ws:onOpen'); + this._connected = true; this._connecting = false; this._autoConnect = true; @@ -116,6 +134,11 @@ export default class Ws extends JsonRpcBase { Object.keys(this._messages) .filter((id) => this._messages[id].queued) .forEach(this._send); + + this._connectPromiseFunctions.resolve(); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; } _onClose = (event) => { @@ -135,13 +158,20 @@ export default class Ws extends JsonRpcBase { console.log('ws:onClose', `trying again in ${time}...`); this._reconnectTimeoutId = setTimeout(() => { - this._connect(); + this.connect(); }, timeout); return; } - console.log('ws:onClose', event); + if (this._connectPromise) { + this._connectPromiseFunctions.reject(event); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + } + + console.log('ws:onClose'); } _onError = (event) => { @@ -149,10 +179,17 @@ export default class Ws extends JsonRpcBase { // ie. don't print if error == closed window.setTimeout(() => { if (this._connected) { - console.error('ws:onError', event); + console.error('ws:onError'); event.timestamp = Date.now(); this._lastError = event; + + if (this._connectPromise) { + this._connectPromiseFunctions.reject(event); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + } } }, 50); } @@ -172,7 +209,10 @@ export default class Ws extends JsonRpcBase { if (result.error) { this.error(event.data); - console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`); + // Don't print error if request rejected... + if (!/rejected/.test(result.error.message)) { + console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`); + } const error = new TransportError(method, result.error.code, result.error.message); reject(error); diff --git a/js/src/api/util/decode.js b/js/src/api/util/decode.js index a6a2ddda4..0e0164bec 100644 --- a/js/src/api/util/decode.js +++ b/js/src/api/util/decode.js @@ -47,8 +47,6 @@ export function decodeMethodInput (methodAbi, paramdata) { throw new Error('Input to decodeMethodInput should be a hex value'); } else if (paramdata.substr(0, 2) === '0x') { return decodeMethodInput(methodAbi, paramdata.slice(2)); - } else if (paramdata.length % 64 !== 0) { - throw new Error('Parameter length in decodeMethodInput not a multiple of 64 characters'); } } diff --git a/js/src/api/util/decode.spec.js b/js/src/api/util/decode.spec.js index 4652c7c5b..fa0102365 100644 --- a/js/src/api/util/decode.spec.js +++ b/js/src/api/util/decode.spec.js @@ -48,10 +48,6 @@ describe('api/util/decode', () => { expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/); }); - it('throws on invalid lengths', () => { - expect(() => decodeMethodInput({}, DATA.slice(-32))).to.throw(/not a multiple of/); - }); - it('correctly decodes valid inputs', () => { expect(decodeMethodInput({ type: 'function', diff --git a/js/src/contracts/abi/badgereg.json b/js/src/contracts/abi/badgereg.json index 3d18ba393..6ae56393b 100644 --- a/js/src/contracts/abi/badgereg.json +++ b/js/src/contracts/abi/badgereg.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_newAddr","type":"address"}],"name":"setAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 8075f456e..370236a26 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -31,8 +31,13 @@ export default class BadgeReg { this.contracts = {}; // by name } + getContract () { + return this._registry.getContract('badgereg'); + } + certifierCount () { - return this._registry.getContract('badgereg') + return this + .getContract() .then((badgeReg) => { return badgeReg.instance.badgeCount.call({}, []) .then((count) => count.valueOf()); @@ -43,7 +48,9 @@ export default class BadgeReg { if (this.certifiers[id]) { return Promise.resolve(this.certifiers[id]); } - return this._registry.getContract('badgereg') + + return this + .getContract() .then((badgeReg) => { return badgeReg.instance.badge.call({}, [ id ]); }) @@ -56,6 +63,7 @@ export default class BadgeReg { name = name === ZERO32 ? null : hex2Ascii(name); + return this.fetchMeta(id) .then(({ title, icon }) => { const data = { address, id, name, title, icon }; @@ -66,7 +74,8 @@ export default class BadgeReg { } fetchMeta (id) { - return this._registry.getContract('badgereg') + return this + .getContract() .then((badgeReg) => { return Promise.all([ badgeReg.instance.meta.call({}, [id, 'TITLE']), @@ -76,7 +85,11 @@ export default class BadgeReg { .then(([ title, icon ]) => { title = bytesToHex(title); title = title === ZERO32 ? null : hex2Ascii(title); - if (bytesToHex(icon) === ZERO32) icon = null; + + if (bytesToHex(icon) === ZERO32) { + icon = null; + } + return { title, icon }; }); } @@ -85,6 +98,7 @@ export default class BadgeReg { if (!this.contracts[certifier]) { this.contracts[certifier] = this._api.newContract(ABI, certifier); } + const contract = this.contracts[certifier]; return contract.instance.certified.call({}, [address]); diff --git a/js/src/dapps/basiccoin/AddressSelect/addressSelect.css b/js/src/dapps/basiccoin/AddressSelect/addressSelect.css index 818906708..5fe4b8c8d 100644 --- a/js/src/dapps/basiccoin/AddressSelect/addressSelect.css +++ b/js/src/dapps/basiccoin/AddressSelect/addressSelect.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Application/Header/header.css b/js/src/dapps/basiccoin/Application/Header/header.css index 5416d7c05..e3692f336 100644 --- a/js/src/dapps/basiccoin/Application/Header/header.css +++ b/js/src/dapps/basiccoin/Application/Header/header.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Application/Loading/loading.css b/js/src/dapps/basiccoin/Application/Loading/loading.css index 915cc77dc..4e2a7f2fa 100644 --- a/js/src/dapps/basiccoin/Application/Loading/loading.css +++ b/js/src/dapps/basiccoin/Application/Loading/loading.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Application/application.css b/js/src/dapps/basiccoin/Application/application.css index 97b66be87..abc4bf9ab 100644 --- a/js/src/dapps/basiccoin/Application/application.css +++ b/js/src/dapps/basiccoin/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index 2863258da..a808a3372 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -83,7 +83,7 @@ export default class Application extends Component { Promise .all([ attachInstances(), - api.parity.accounts() + api.parity.accountsInfo() ]) .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { accountsInfo = accountsInfo || {}; @@ -95,7 +95,7 @@ export default class Application extends Component { accounts: Object .keys(accountsInfo) .sort((a, b) => { - return (accountsInfo[b].uuid || '').localeCompare(accountsInfo[a].uuid || ''); + return (accountsInfo[b].name || '').localeCompare(accountsInfo[a].name || ''); }) .reduce((accounts, address) => { accounts[address] = Object.assign(accountsInfo[address], { address }); diff --git a/js/src/dapps/basiccoin/Container/container.css b/js/src/dapps/basiccoin/Container/container.css index 7cc00e11e..79f362775 100644 --- a/js/src/dapps/basiccoin/Container/container.css +++ b/js/src/dapps/basiccoin/Container/container.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.css b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.css index f24729cf7..bc1e2d188 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.css +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js index 33beb4a94..c6b8e2152 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js @@ -126,7 +126,7 @@ export default class Deployment extends Component { const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const hasError = !!(nameError || tlaError || totalSupplyError); const error = `${styles.input} ${styles.error}`; - const addresses = Object.keys(accounts).filter((address) => accounts[address].uuid); + const addresses = Object.keys(accounts); //
// diff --git a/js/src/dapps/basiccoin/Deploy/Event/event.css b/js/src/dapps/basiccoin/Deploy/Event/event.css index 40768f6dc..c347a43f9 100644 --- a/js/src/dapps/basiccoin/Deploy/Event/event.css +++ b/js/src/dapps/basiccoin/Deploy/Event/event.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Deploy/Events/events.css b/js/src/dapps/basiccoin/Deploy/Events/events.css index 3cefaacb0..b57d7cda0 100644 --- a/js/src/dapps/basiccoin/Deploy/Events/events.css +++ b/js/src/dapps/basiccoin/Deploy/Events/events.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/IdentityIcon/identityIcon.css b/js/src/dapps/basiccoin/IdentityIcon/identityIcon.css index 6abbcf808..4047c0e7e 100644 --- a/js/src/dapps/basiccoin/IdentityIcon/identityIcon.css +++ b/js/src/dapps/basiccoin/IdentityIcon/identityIcon.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Overview/Owner/owner.css b/js/src/dapps/basiccoin/Overview/Owner/owner.css index f94945722..b3a30d3c0 100644 --- a/js/src/dapps/basiccoin/Overview/Owner/owner.css +++ b/js/src/dapps/basiccoin/Overview/Owner/owner.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Overview/Token/token.css b/js/src/dapps/basiccoin/Overview/Token/token.css index 5324aa7c5..311f3778b 100644 --- a/js/src/dapps/basiccoin/Overview/Token/token.css +++ b/js/src/dapps/basiccoin/Overview/Token/token.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Overview/overview.css b/js/src/dapps/basiccoin/Overview/overview.css index 9757a8145..40a666571 100644 --- a/js/src/dapps/basiccoin/Overview/overview.css +++ b/js/src/dapps/basiccoin/Overview/overview.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Overview/overview.js b/js/src/dapps/basiccoin/Overview/overview.js index ecbf4e412..22a983387 100644 --- a/js/src/dapps/basiccoin/Overview/overview.js +++ b/js/src/dapps/basiccoin/Overview/overview.js @@ -94,10 +94,7 @@ export default class Overview extends Component { loadOwners () { const { accounts } = this.context; - const addresses = Object - .values(accounts) - .filter((account) => account.uuid) - .map((account) => account.address); + const addresses = Object.keys(accounts); loadOwnedTokens(addresses) .then(({ tokens, total }) => { diff --git a/js/src/dapps/basiccoin/Transfer/Send/send.css b/js/src/dapps/basiccoin/Transfer/Send/send.css index f24729cf7..bc1e2d188 100644 --- a/js/src/dapps/basiccoin/Transfer/Send/send.css +++ b/js/src/dapps/basiccoin/Transfer/Send/send.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/Transfer/Send/send.js b/js/src/dapps/basiccoin/Transfer/Send/send.js index daec42ac7..459c58da2 100644 --- a/js/src/dapps/basiccoin/Transfer/Send/send.js +++ b/js/src/dapps/basiccoin/Transfer/Send/send.js @@ -303,12 +303,9 @@ export default class Send extends Component { loadBalances () { const { accounts } = this.context; - const myAccounts = Object - .values(accounts) - .filter((account) => account.uuid) - .map((account) => account.address); + const addresses = Object.keys(accounts); - loadBalances(myAccounts) + loadBalances(addresses) .then((_tokens) => { const tokens = _tokens.filter((token) => { for (let index = 0; index < token.balances.length; index++) { diff --git a/js/src/dapps/basiccoin/_form.css b/js/src/dapps/basiccoin/_form.css index ffafdbeaf..16ba3da45 100644 --- a/js/src/dapps/basiccoin/_form.css +++ b/js/src/dapps/basiccoin/_form.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/basiccoin/_status.css b/js/src/dapps/basiccoin/_status.css index 8fd93cfe4..18cc5d9bf 100644 --- a/js/src/dapps/basiccoin/_status.css +++ b/js/src/dapps/basiccoin/_status.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Application/application.css b/js/src/dapps/dappreg/Application/application.css index f171d8127..da4bc1b5e 100644 --- a/js/src/dapps/dappreg/Application/application.css +++ b/js/src/dapps/dappreg/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Button/button.css b/js/src/dapps/dappreg/Button/button.css index a66736e46..734c20ae2 100644 --- a/js/src/dapps/dappreg/Button/button.css +++ b/js/src/dapps/dappreg/Button/button.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/ButtonBar/buttonBar.css b/js/src/dapps/dappreg/ButtonBar/buttonBar.css index 0aa84ee29..e8a831225 100644 --- a/js/src/dapps/dappreg/ButtonBar/buttonBar.css +++ b/js/src/dapps/dappreg/ButtonBar/buttonBar.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Dapp/dapp.css b/js/src/dapps/dappreg/Dapp/dapp.css index 0fef89cab..b6061da80 100644 --- a/js/src/dapps/dappreg/Dapp/dapp.css +++ b/js/src/dapps/dappreg/Dapp/dapp.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Input/input.css b/js/src/dapps/dappreg/Input/input.css index bd453a510..390133528 100644 --- a/js/src/dapps/dappreg/Input/input.css +++ b/js/src/dapps/dappreg/Input/input.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Modal/modal.css b/js/src/dapps/dappreg/Modal/modal.css index 9c40393b2..0fa409f56 100644 --- a/js/src/dapps/dappreg/Modal/modal.css +++ b/js/src/dapps/dappreg/Modal/modal.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/Warning/warning.css b/js/src/dapps/dappreg/Warning/warning.css index bc1cd40d9..3114d2b64 100644 --- a/js/src/dapps/dappreg/Warning/warning.css +++ b/js/src/dapps/dappreg/Warning/warning.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/dappreg/dappsStore.js b/js/src/dapps/dappreg/dappsStore.js index 94913a2eb..2c6fc689e 100644 --- a/js/src/dapps/dappreg/dappsStore.js +++ b/js/src/dapps/dappreg/dappsStore.js @@ -26,7 +26,6 @@ let instance = null; export default class DappsStore { @observable accounts = []; - @observable addresses = []; @observable apps = []; @observable contractOwner = null; @observable currentAccount = null; @@ -191,7 +190,7 @@ export default class DappsStore { @action setAccounts = (accountsInfo) => { transaction(() => { - this.addresses = Object + this.accounts = Object .keys(accountsInfo) .map((address) => { const account = accountsInfo[address]; @@ -199,7 +198,6 @@ export default class DappsStore { return account; }); - this.accounts = this.addresses.filter((account) => account.uuid); this.currentAccount = this.accounts[0]; }); @@ -315,7 +313,7 @@ export default class DappsStore { this .setApps(appsInfo.map(([appId, owner]) => { const isOwner = !!this.accounts.find((account) => account.address === owner); - const account = this.addresses.find((account) => account.address === owner); + const account = this.accounts.find((account) => account.address === owner); const id = api.util.bytesToHex(appId); return { @@ -445,7 +443,7 @@ export default class DappsStore { _loadAccounts () { return api.parity - .accounts() + .accountsInfo() .then(this.setAccounts) .catch((error) => { console.error('Store:loadAccounts', error); diff --git a/js/src/dapps/githubhint/Application/application.css b/js/src/dapps/githubhint/Application/application.css index 61929f552..15daac7c9 100644 --- a/js/src/dapps/githubhint/Application/application.css +++ b/js/src/dapps/githubhint/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/githubhint/Button/button.css b/js/src/dapps/githubhint/Button/button.css index 28519094b..94b7f97f9 100644 --- a/js/src/dapps/githubhint/Button/button.css +++ b/js/src/dapps/githubhint/Button/button.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/githubhint/Events/events.css b/js/src/dapps/githubhint/Events/events.css index a33c09236..400a291bc 100644 --- a/js/src/dapps/githubhint/Events/events.css +++ b/js/src/dapps/githubhint/Events/events.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/githubhint/IdentityIcon/identityIcon.css b/js/src/dapps/githubhint/IdentityIcon/identityIcon.css index 3fa1df99e..6e708cac3 100644 --- a/js/src/dapps/githubhint/IdentityIcon/identityIcon.css +++ b/js/src/dapps/githubhint/IdentityIcon/identityIcon.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/githubhint/Loading/loading.css b/js/src/dapps/githubhint/Loading/loading.css index b77d1a237..4885f1af2 100644 --- a/js/src/dapps/githubhint/Loading/loading.css +++ b/js/src/dapps/githubhint/Loading/loading.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index ed143968f..6aae1c8e9 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -28,7 +28,7 @@ export function attachInterface () { return Promise .all([ registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), - api.parity.accounts() + api.parity.accountsInfo() ]); }) .then(([address, accountsInfo]) => { @@ -37,7 +37,6 @@ export function attachInterface () { const contract = api.newContract(abis.githubhint, address); const accounts = Object .keys(accountsInfo) - .filter((address) => accountsInfo[address].uuid) .reduce((obj, address) => { const account = accountsInfo[address]; diff --git a/js/src/dapps/registry/Accounts/accounts.css b/js/src/dapps/registry/Accounts/accounts.css index 344a92867..73bd9ce5f 100644 --- a/js/src/dapps/registry/Accounts/accounts.css +++ b/js/src/dapps/registry/Accounts/accounts.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/registry/Accounts/accounts.js b/js/src/dapps/registry/Accounts/accounts.js index 076a8bfa4..29bcb7add 100644 --- a/js/src/dapps/registry/Accounts/accounts.js +++ b/js/src/dapps/registry/Accounts/accounts.js @@ -15,22 +15,26 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import IconMenu from 'material-ui/IconMenu'; import IconButton from 'material-ui/IconButton/IconButton'; import AccountIcon from 'material-ui/svg-icons/action/account-circle'; import MenuItem from 'material-ui/MenuItem'; import IdentityIcon from '../IdentityIcon'; -import renderAddress from '../ui/address'; +import Address from '../ui/address'; +import { select } from './actions'; import styles from './accounts.css'; -export default class Accounts extends Component { +class Accounts extends Component { static propTypes = { - actions: PropTypes.object.isRequired, all: PropTypes.object.isRequired, - selected: PropTypes.object + selected: PropTypes.object, + + select: PropTypes.func.isRequired } render () { @@ -41,8 +45,17 @@ export default class Accounts extends Component { const accountsButton = ( { selected - ? () - : () + ? ( + + ) : ( + + ) } ); @@ -61,20 +74,27 @@ export default class Accounts extends Component { } renderAccount = (account) => { - const { all, selected } = this.props; + const { selected } = this.props; const isSelected = selected && selected.address === account.address; return ( - { renderAddress(account.address, all, {}) } +
); }; onAccountSelect = (e, address) => { - this.props.actions.select(address); + this.props.select(address); }; } + +const mapStateToProps = (state) => state.accounts; +const mapDispatchToProps = (dispatch) => bindActionCreators({ select }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Accounts); diff --git a/js/src/dapps/registry/Application/application.css b/js/src/dapps/registry/Application/application.css index b46afbcf7..7b235d730 100644 --- a/js/src/dapps/registry/Application/application.css +++ b/js/src/dapps/registry/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/registry/Application/application.js b/js/src/dapps/registry/Application/application.js index 0d1f7e50a..abfacc497 100644 --- a/js/src/dapps/registry/Application/application.js +++ b/js/src/dapps/registry/Application/application.js @@ -30,6 +30,7 @@ import Events from '../Events'; import Lookup from '../Lookup'; import Names from '../Names'; import Records from '../Records'; +import Reverse from '../Reverse'; export default class Application extends Component { static childContextTypes = { @@ -42,26 +43,14 @@ export default class Application extends Component { } static propTypes = { - actions: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired, - contacts: PropTypes.object.isRequired, - contract: nullableProptype(PropTypes.object.isRequired), - fee: nullableProptype(PropTypes.object.isRequired), - lookup: PropTypes.object.isRequired, - events: PropTypes.object.isRequired, - names: PropTypes.object.isRequired, - records: PropTypes.object.isRequired + contract: nullableProptype(PropTypes.object).isRequired, + fee: nullableProptype(PropTypes.object).isRequired }; render () { const { api } = window.parity; - const { - actions, - accounts, contacts, - contract, fee, - lookup, - events - } = this.props; + const { contract, fee } = this.props; let warning = null; return ( @@ -69,13 +58,13 @@ export default class Application extends Component { { warning }

RΞgistry

- +
{ contract && fee ? (
- + { this.renderActions() } - +
WARNING: The name registry is experimental. Please ensure that you understand the risks, benefits & consequences of registering a name before doing so. A non-refundable fee of { api.util.fromWei(fee).toFormat(3) }ETH is required for all registrations.
@@ -88,15 +77,7 @@ export default class Application extends Component { } renderActions () { - const { - actions, - accounts, - fee, - names, - records - } = this.props; - - const hasAccount = !!accounts.selected; + const hasAccount = !!this.props.accounts.selected; if (!hasAccount) { return ( @@ -111,8 +92,9 @@ export default class Application extends Component { return (
- - + + +
); } diff --git a/js/src/dapps/registry/Container.js b/js/src/dapps/registry/Container.js index 2de041126..3d5cf1dfe 100644 --- a/js/src/dapps/registry/Container.js +++ b/js/src/dapps/registry/Container.js @@ -37,6 +37,7 @@ class Container extends Component { componentDidMount () { Promise.all([ + this.props.actions.fetchIsTestnet(), this.props.actions.addresses.fetch(), this.props.actions.fetchContract() ]).then(() => { diff --git a/js/src/dapps/registry/Events/actions.js b/js/src/dapps/registry/Events/actions.js index aa48947e8..6303b1bc5 100644 --- a/js/src/dapps/registry/Events/actions.js +++ b/js/src/dapps/registry/Events/actions.js @@ -25,7 +25,11 @@ export const event = (name, event) => ({ type: 'events event', name, event }); export const subscribe = (name, from = 0, to = 'pending') => (dispatch, getState) => { const { contract } = getState(); - if (!contract) return; + + if (!contract) { + return; + } + const opt = { fromBlock: from, toBlock: to, limit: 50 }; dispatch(start(name, from, to)); @@ -38,8 +42,11 @@ export const subscribe = (name, from = 0, to = 'pending') => } events.forEach((e) => { - api.eth.getBlockByNumber(e.blockNumber) - .then((block) => { + Promise.all([ + api.eth.getBlockByNumber(e.blockNumber), + api.eth.getTransactionByHash(e.transactionHash) + ]) + .then(([block, tx]) => { const data = { type: name, key: '' + e.transactionHash + e.logIndex, @@ -47,6 +54,8 @@ export const subscribe = (name, from = 0, to = 'pending') => block: e.blockNumber, index: e.logIndex, transaction: e.transactionHash, + from: tx.from, + to: tx.to, parameters: e.params, timestamp: block.timestamp }; @@ -70,9 +79,16 @@ export const subscribe = (name, from = 0, to = 'pending') => export const unsubscribe = (name) => (dispatch, getState) => { const state = getState(); - if (!state.contract) return; + + if (!state.contract) { + return; + } + const subscriptions = state.events.subscriptions; - if (!(name in subscriptions) || subscriptions[name] === null) return; + + if (!(name in subscriptions) || subscriptions[name] === null) { + return; + } state.contract .unsubscribe(subscriptions[name]) diff --git a/js/src/dapps/registry/Events/events.css b/js/src/dapps/registry/Events/events.css index 66277dae3..15a23acf8 100644 --- a/js/src/dapps/registry/Events/events.css +++ b/js/src/dapps/registry/Events/events.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -49,3 +49,9 @@ .eventsList td { padding: 0.25em 0.5em; } + +.inline { + display: inline-block; + width: auto; + margin-right: 1em; +} diff --git a/js/src/dapps/registry/Events/events.js b/js/src/dapps/registry/Events/events.js index 77a985def..d204822d1 100644 --- a/js/src/dapps/registry/Events/events.js +++ b/js/src/dapps/registry/Events/events.js @@ -15,13 +15,17 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Card, CardHeader, CardActions, CardText } from 'material-ui/Card'; import Toggle from 'material-ui/Toggle'; import moment from 'moment'; import { bytesToHex } from '../parity'; -import renderHash from '../ui/hash'; -import renderAddress from '../ui/address'; +import Hash from '../ui/hash'; +import Address from '../ui/address'; + +import { subscribe, unsubscribe } from './actions'; import styles from './events.css'; const inlineButton = { @@ -42,21 +46,31 @@ const renderStatus = (timestamp, isPending) => { ); }; -const renderEvent = (classNames, verb) => (e, accounts, contacts) => { +const renderEvent = (classNames, verb) => (e) => { const classes = e.state === 'pending' ? classNames + ' ' + styles.pending : classNames; return ( - { renderAddress(e.parameters.owner.value, accounts, contacts) } - { verb } - { renderHash(bytesToHex(e.parameters.name.value)) } - { renderStatus(e.timestamp, e.state === 'pending') } + +
+ + + { verb } + + + + + + + + { renderStatus(e.timestamp, e.state === 'pending') } + ); }; -const renderDataChanged = (e, accounts, contacts) => { +const renderDataChanged = (e) => { let classNames = styles.dataChanged; if (e.state === 'pending') { classNames += ' ' + styles.pending; @@ -64,12 +78,61 @@ const renderDataChanged = (e, accounts, contacts) => { return ( - { renderAddress(e.parameters.owner.value, accounts, contacts) } - updated - key { new Buffer(e.parameters.plainKey.value).toString('utf8') } of { renderHash(bytesToHex(e.parameters.name.value)) } +
+ + + updated + + + { 'key ' } + + { new Buffer(e.parameters.plainKey.value).toString('utf8') } + + { 'of ' } + + + + + + { renderStatus(e.timestamp, e.state === 'pending') } + + + ); +}; + +const renderReverse = (e) => { + const verb = ({ + ReverseProposed: 'proposed', + ReverseConfirmed: 'confirmed', + ReverseRemoved: 'removed' + })[e.type]; + + if (!verb) { + return null; + } + + const classes = [ styles.reverse ]; + if (e.state === 'pending') { + classes.push(styles.pending); + } + + // TODO: `name` is an indexed param, cannot display as plain text + return ( + + +
+ + { verb } + + { 'name ' } + { bytesToHex(e.parameters.name.value) } + { ' for ' } +
+ + + { renderStatus(e.timestamp, e.state === 'pending') } - { renderStatus(e.timestamp, e.state === 'pending') } ); }; @@ -77,22 +140,25 @@ const renderDataChanged = (e, accounts, contacts) => { const eventTypes = { Reserved: renderEvent(styles.reserved, 'reserved'), Dropped: renderEvent(styles.dropped, 'dropped'), - DataChanged: renderDataChanged + DataChanged: renderDataChanged, + ReverseProposed: renderReverse, + ReverseConfirmed: renderReverse, + ReverseRemoved: renderReverse }; -export default class Events extends Component { +class Events extends Component { static propTypes = { - actions: PropTypes.object.isRequired, - subscriptions: PropTypes.object.isRequired, - pending: PropTypes.object.isRequired, events: PropTypes.array.isRequired, - accounts: PropTypes.object.isRequired, - contacts: PropTypes.object.isRequired + pending: PropTypes.object.isRequired, + subscriptions: PropTypes.object.isRequired, + + subscribe: PropTypes.func.isRequired, + unsubscribe: PropTypes.func.isRequired } render () { - const { subscriptions, pending, accounts, contacts } = this.props; + const { subscriptions, pending } = this.props; const eventsObject = this.props.events .filter((e) => eventTypes[e.type]) @@ -122,7 +188,16 @@ export default class Events extends Component { return evB.timestamp - evA.timestamp; }) - .map((e) => eventTypes[e.type](e, accounts, contacts)); + .map((e) => eventTypes[e.type](e)); + + const reverseToggled = + subscriptions.ReverseProposed !== null && + subscriptions.ReverseConfirmed !== null && + subscriptions.ReverseRemoved !== null; + const reverseDisabled = + pending.ReverseProposed || + pending.ReverseConfirmed || + pending.ReverseRemoved; return ( @@ -149,6 +224,13 @@ export default class Events extends Component { onToggle={ this.onDataChangedToggle } style={ inlineButton } /> + @@ -162,33 +244,59 @@ export default class Events extends Component { } onReservedToggle = (e, isToggled) => { - const { pending, subscriptions, actions } = this.props; + const { pending, subscriptions, subscribe, unsubscribe } = this.props; + if (!pending.Reserved) { if (isToggled && subscriptions.Reserved === null) { - actions.subscribe('Reserved'); + subscribe('Reserved'); } else if (!isToggled && subscriptions.Reserved !== null) { - actions.unsubscribe('Reserved'); + unsubscribe('Reserved'); } } }; + onDroppedToggle = (e, isToggled) => { - const { pending, subscriptions, actions } = this.props; + const { pending, subscriptions, subscribe, unsubscribe } = this.props; + if (!pending.Dropped) { if (isToggled && subscriptions.Dropped === null) { - actions.subscribe('Dropped'); + subscribe('Dropped'); } else if (!isToggled && subscriptions.Dropped !== null) { - actions.unsubscribe('Dropped'); + unsubscribe('Dropped'); } } }; + onDataChangedToggle = (e, isToggled) => { - const { pending, subscriptions, actions } = this.props; + const { pending, subscriptions, subscribe, unsubscribe } = this.props; + if (!pending.DataChanged) { if (isToggled && subscriptions.DataChanged === null) { - actions.subscribe('DataChanged'); + subscribe('DataChanged'); } else if (!isToggled && subscriptions.DataChanged !== null) { - actions.unsubscribe('DataChanged'); + unsubscribe('DataChanged'); + } + } + }; + + onReverseToggle = (e, isToggled) => { + const { pending, subscriptions, subscribe, unsubscribe } = this.props; + + for (let e of ['ReverseProposed', 'ReverseConfirmed', 'ReverseRemoved']) { + if (pending[e]) { + continue; + } + + if (isToggled && subscriptions[e] === null) { + subscribe(e); + } else if (!isToggled && subscriptions[e] !== null) { + unsubscribe(e); } } }; } + +const mapStateToProps = (state) => state.events; +const mapDispatchToProps = (dispatch) => bindActionCreators({ subscribe, unsubscribe }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Events); diff --git a/js/src/dapps/registry/Events/reducers.js b/js/src/dapps/registry/Events/reducers.js index 8bca205ac..7be492bbb 100644 --- a/js/src/dapps/registry/Events/reducers.js +++ b/js/src/dapps/registry/Events/reducers.js @@ -18,21 +18,35 @@ const initialState = { subscriptions: { Reserved: null, Dropped: null, - DataChanged: null + DataChanged: null, + ReverseProposed: null, + ReverseConfirmed: null, + ReverseRemoved: null }, pending: { Reserved: false, Dropped: false, - DataChanged: false + DataChanged: false, + ReverseProposed: false, + ReverseConfirmed: false, + ReverseRemoved: false }, events: [] }; const sortEvents = (a, b) => { - if (a.state === 'pending' && b.state !== 'pending') return -1; - if (a.state !== 'pending' && b.state === 'pending') return 1; + if (a.state === 'pending' && b.state !== 'pending') { + return -1; + } else if (a.state !== 'pending' && b.state === 'pending') { + return 1; + } + const d = b.block.minus(a.block).toFixed(0); - if (d === 0) return b.index.minus(a.index).toFixed(0); + + if (d === 0) { + return b.index.minus(a.index).toFixed(0); + } + return d; }; diff --git a/js/src/dapps/registry/IdentityIcon/identityIcon.css b/js/src/dapps/registry/IdentityIcon/identityIcon.css index 3fa1df99e..6e708cac3 100644 --- a/js/src/dapps/registry/IdentityIcon/identityIcon.css +++ b/js/src/dapps/registry/IdentityIcon/identityIcon.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/registry/Lookup/actions.js b/js/src/dapps/registry/Lookup/actions.js index 7cd1ee57a..eb1c7db66 100644 --- a/js/src/dapps/registry/Lookup/actions.js +++ b/js/src/dapps/registry/Lookup/actions.js @@ -18,25 +18,54 @@ import { sha3 } from '../parity.js'; export const clear = () => ({ type: 'lookup clear' }); -export const start = (name, key) => ({ type: 'lookup start', name, key }); +export const lookupStart = (name, key) => ({ type: 'lookup start', name, key }); +export const reverseLookupStart = (address) => ({ type: 'reverseLookup start', address }); -export const success = (address) => ({ type: 'lookup success', result: address }); +export const success = (action, result) => ({ type: `${action} success`, result: result }); -export const fail = () => ({ type: 'lookup error' }); +export const fail = (action) => ({ type: `${action} error` }); export const lookup = (name, key) => (dispatch, getState) => { const { contract } = getState(); - if (!contract) return; + if (!contract) { + return; + } + const getAddress = contract.functions .find((f) => f.name === 'getAddress'); name = name.toLowerCase(); - dispatch(start(name, key)); - getAddress.call({}, [sha3(name), key]) - .then((address) => dispatch(success(address))) + dispatch(lookupStart(name, key)); + + getAddress.call({}, [ sha3(name), key ]) + .then((address) => dispatch(success('lookup', address))) .catch((err) => { console.error(`could not lookup ${key} for ${name}`); - if (err) console.error(err.stack); - dispatch(fail()); + if (err) { + console.error(err.stack); + } + dispatch(fail('lookup')); + }); +}; + +export const reverseLookup = (address) => (dispatch, getState) => { + const { contract } = getState(); + if (!contract) { + return; + } + + const reverse = contract.functions + .find((f) => f.name === 'reverse'); + + dispatch(reverseLookupStart(address)); + + reverse.call({}, [ address ]) + .then((address) => dispatch(success('reverseLookup', address))) + .catch((err) => { + console.error(`could not lookup reverse for ${address}`); + if (err) { + console.error(err.stack); + } + dispatch(fail('reverseLookup')); }); }; diff --git a/js/src/dapps/registry/Lookup/lookup.css b/js/src/dapps/registry/Lookup/lookup.css index 12ddd040c..e415ab5f3 100644 --- a/js/src/dapps/registry/Lookup/lookup.css +++ b/js/src/dapps/registry/Lookup/lookup.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -20,9 +20,7 @@ } .box { + display: flex; margin: 0 1em; -} - -.spacing { - margin-left: 1em; + align-items: baseline; } diff --git a/js/src/dapps/registry/Lookup/lookup.js b/js/src/dapps/registry/Lookup/lookup.js index a743219f2..bf01df115 100644 --- a/js/src/dapps/registry/Lookup/lookup.js +++ b/js/src/dapps/registry/Lookup/lookup.js @@ -15,50 +15,65 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Card, CardHeader, CardText } from 'material-ui/Card'; import TextField from 'material-ui/TextField'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import MenuItem from 'material-ui/MenuItem'; import RaisedButton from 'material-ui/RaisedButton'; import SearchIcon from 'material-ui/svg-icons/action/search'; import { nullableProptype } from '~/util/proptypes'; -import renderAddress from '../ui/address.js'; +import Address from '../ui/address.js'; import renderImage from '../ui/image.js'; -import recordTypeSelect from '../ui/record-type-select.js'; +import { clear, lookup, reverseLookup } from './actions'; import styles from './lookup.css'; -export default class Lookup extends Component { +class Lookup extends Component { static propTypes = { - actions: PropTypes.object.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, result: nullableProptype(PropTypes.string.isRequired), - accounts: PropTypes.object.isRequired, - contacts: PropTypes.object.isRequired + + clear: PropTypes.func.isRequired, + lookup: PropTypes.func.isRequired, + reverseLookup: PropTypes.func.isRequired } - state = { name: '', type: 'A' }; + state = { + input: '', type: 'A' + }; render () { - const name = this.state.name || this.props.name; - const type = this.state.type || this.props.type; - const { result, accounts, contacts } = this.props; + const { input, type } = this.state; + const { result } = this.props; let output = ''; if (result) { if (type === 'A') { - output = ({ renderAddress(result, accounts, contacts, false) }); + output = ( + +
+ + ); } else if (type === 'IMG') { output = renderImage(result); } else if (type === 'CONTENT') { - output = (
- { result } -

This is most likely just the hash of the content you are looking for

-
); + output = ( +
+ { result } +

Keep in mind that this is most likely the hash of the content you are looking for.

+
+ ); } else { - output = ({ result }); + output = ( + { result } + ); } } @@ -67,14 +82,20 @@ export default class Lookup extends Component {
- { recordTypeSelect(type, this.onTypeChange, styles.spacing) } + + + + + + } @@ -86,14 +107,30 @@ export default class Lookup extends Component { ); } - onNameChange = (e) => { - this.setState({ name: e.target.value }); + onInputChange = (e) => { + this.setState({ input: e.target.value }); }; + onTypeChange = (e, i, type) => { this.setState({ type }); - this.props.actions.clear(); + this.props.clear(); }; + onLookupClick = () => { - this.props.actions.lookup(this.state.name, this.state.type); + const { input, type } = this.state; + + if (type === 'reverse') { + this.props.reverseLookup(input); + } else { + this.props.lookup(input, type); + } }; } + +const mapStateToProps = (state) => state.lookup; +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + clear, lookup, reverseLookup + }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Lookup); diff --git a/js/src/dapps/registry/Lookup/reducers.js b/js/src/dapps/registry/Lookup/reducers.js index d6807c713..b675fc702 100644 --- a/js/src/dapps/registry/Lookup/reducers.js +++ b/js/src/dapps/registry/Lookup/reducers.js @@ -14,39 +14,34 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { isStage } from '../util/actions'; + const initialState = { pending: false, - name: '', type: '', result: null }; export default (state = initialState, action) => { - if (action.type === 'lookup clear') { - return { ...state, result: null }; + const { type } = action; + + if (type.slice(0, 7) !== 'lookup ' && type.slice(0, 14) !== 'reverseLookup ') { + return state; } - if (action.type === 'lookup start') { - return { - pending: true, - name: action.name, type: action.entry, - result: null - }; + if (isStage('clear', action)) { + return { pending: state.pending, result: null }; } - if (action.type === 'lookup error') { - return { - pending: false, - name: initialState.name, type: initialState.type, - result: null - }; + if (isStage('start', action)) { + return { pending: true, result: null }; } - if (action.type === 'lookup success') { - return { - pending: false, - name: initialState.name, type: initialState.type, - result: action.result - }; + if (isStage('error', action)) { + return { pending: false, result: null }; + } + + if (isStage('success', action)) { + return { pending: false, result: action.result }; } return state; diff --git a/js/src/dapps/registry/Names/actions.js b/js/src/dapps/registry/Names/actions.js index f6ffec729..67867ca8e 100644 --- a/js/src/dapps/registry/Names/actions.js +++ b/js/src/dapps/registry/Names/actions.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import { sha3, api } from '../parity.js'; +import postTx from '../util/post-tx'; const alreadyQueued = (queue, action, name) => !!queue.find((entry) => entry.action === action && entry.name === name); @@ -30,38 +31,38 @@ export const reserve = (name) => (dispatch, getState) => { const account = state.accounts.selected; const contract = state.contract; const fee = state.fee; - - if (!contract || !account) return; - if (alreadyQueued(state.names.queue, 'reserve', name)) return; - const reserve = contract.functions.find((f) => f.name === 'reserve'); + if (!contract || !account) { + return; + } name = name.toLowerCase(); + + if (alreadyQueued(state.names.queue, 'reserve', name)) { + return; + } + const reserve = contract.functions.find((f) => f.name === 'reserve'); + + dispatch(reserveStart(name)); + const options = { from: account.address, value: fee }; - const values = [ sha3(name) ]; + const values = [ + sha3(name) + ]; - dispatch(reserveStart(name)); - - reserve.estimateGas(options, values) - .then((gas) => { - options.gas = gas.mul(1.2).toFixed(0); - return reserve.postTransaction(options, values); - }) - .then((requestId) => { - return api.pollMethod('parity_checkRequest', requestId); - }) - .then((txhash) => { + postTx(api, reserve, options, values) + .then((txHash) => { dispatch(reserveSuccess(name)); }) .catch((err) => { - if (err && err.type === 'REQUEST_REJECTED') { - return dispatch(reserveFail(name)); + console.error(`could not reserve ${name}`); + + if (err) { + console.error(err.stack); } - console.error(`could not reserve ${name}`); - if (err) console.error(err.stack); dispatch(reserveFail(name)); }); }; @@ -76,33 +77,37 @@ export const drop = (name) => (dispatch, getState) => { const state = getState(); const account = state.accounts.selected; const contract = state.contract; - if (!contract || !account) return; - if (alreadyQueued(state.names.queue, 'drop', name)) return; - const drop = contract.functions.find((f) => f.name === 'drop'); + if (!contract || !account) { + return; + } name = name.toLowerCase(); - const options = { from: account.address }; - const values = [ sha3(name) ]; + if (alreadyQueued(state.names.queue, 'drop', name)) { + return; + } + + const drop = contract.functions.find((f) => f.name === 'drop'); dispatch(dropStart(name)); - drop.estimateGas(options, values) - .then((gas) => { - options.gas = gas.mul(1.2).toFixed(0); - return drop.postTransaction(options, values); - }) - .then((requestId) => { - return api.pollMethod('parity_checkRequest', requestId); - }) + + const options = { + from: account.address + }; + const values = [ + sha3(name) + ]; + + postTx(api, drop, options, values) .then((txhash) => { dispatch(dropSuccess(name)); }) .catch((err) => { - if (err && err.type === 'REQUEST_REJECTED') { - dispatch(reserveFail(name)); + console.error(`could not drop ${name}`); + + if (err) { + console.error(err.stack); } - console.error(`could not drop ${name}`); - if (err) console.error(err.stack); dispatch(reserveFail(name)); }); }; diff --git a/js/src/dapps/registry/Names/names.css b/js/src/dapps/registry/Names/names.css index a058d41ac..b56387909 100644 --- a/js/src/dapps/registry/Names/names.css +++ b/js/src/dapps/registry/Names/names.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -20,7 +20,8 @@ } .box { - margin: 0 1em 1em 1em; + display: flex; + align-items: baseline; } .spacing { diff --git a/js/src/dapps/registry/Names/names.js b/js/src/dapps/registry/Names/names.js index c0c4badf0..c3f0e79f6 100644 --- a/js/src/dapps/registry/Names/names.js +++ b/js/src/dapps/registry/Names/names.js @@ -15,6 +15,8 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Card, CardHeader, CardText } from 'material-ui/Card'; import TextField from 'material-ui/TextField'; import DropDownMenu from 'material-ui/DropDownMenu'; @@ -24,6 +26,7 @@ import CheckIcon from 'material-ui/svg-icons/navigation/check'; import { fromWei } from '../parity.js'; +import { reserve, drop } from './actions'; import styles from './names.css'; const useSignerText = (

Use the Signer to authenticate the following changes.

); @@ -72,13 +75,15 @@ const renderQueue = (queue) => { ); }; -export default class Names extends Component { +class Names extends Component { static propTypes = { - actions: PropTypes.object.isRequired, fee: PropTypes.object.isRequired, pending: PropTypes.bool.isRequired, - queue: PropTypes.array.isRequired + queue: PropTypes.array.isRequired, + + reserve: PropTypes.func.isRequired, + drop: PropTypes.func.isRequired } state = { @@ -117,27 +122,29 @@ export default class Names extends Component { : (

To drop a name, you have to be the owner.

) ) } - - - - - - } - onTouchTap={ this.onSubmitClick } - /> +
+ + + + + + } + onTouchTap={ this.onSubmitClick } + /> +
{ queue.length > 0 ? (
{ useSignerText }{ renderQueue(queue) }
) : null @@ -156,9 +163,14 @@ export default class Names extends Component { onSubmitClick = () => { const { action, name } = this.state; if (action === 'reserve') { - this.props.actions.reserve(name); + this.props.reserve(name); } else if (action === 'drop') { - this.props.actions.drop(name); + this.props.drop(name); } }; } + +const mapStateToProps = (state) => ({ ...state.names, fee: state.fee }); +const mapDispatchToProps = (dispatch) => bindActionCreators({ reserve, drop }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Names); diff --git a/js/src/dapps/registry/Names/reducers.js b/js/src/dapps/registry/Names/reducers.js index 2b43c8b3e..17230ad40 100644 --- a/js/src/dapps/registry/Names/reducers.js +++ b/js/src/dapps/registry/Names/reducers.js @@ -14,36 +14,38 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { isAction, isStage, addToQueue, removeFromQueue } from '../util/actions'; + const initialState = { pending: false, queue: [] }; export default (state = initialState, action) => { - if (action.type === 'names reserve start') { - return { ...state, pending: true }; - } - if (action.type === 'names reserve success') { - return { - ...state, pending: false, - queue: state.queue.concat({ action: 'reserve', name: action.name }) - }; - } - if (action.type === 'names reserve fail') { - return { ...state, pending: false }; - } - - if (action.type === 'names drop start') { - return { ...state, pending: true }; - } - if (action.type === 'names drop success') { - return { - ...state, pending: false, - queue: state.queue.concat({ action: 'drop', name: action.name }) - }; - } - if (action.type === 'names drop fail') { - return { ...state, pending: false }; + if (isAction('names', 'reserve', action)) { + if (isStage('start', action)) { + return { + ...state, pending: true, + queue: addToQueue(state.queue, 'reserve', action.name) + }; + } else if (isStage('success', action) || isStage('fail', action)) { + return { + ...state, pending: false, + queue: removeFromQueue(state.queue, 'reserve', action.name) + }; + } + } else if (isAction('names', 'drop', action)) { + if (isStage('start', action)) { + return { + ...state, pending: true, + queue: addToQueue(state.queue, 'drop', action.name) + }; + } else if (isStage('success', action) || isStage('fail', action)) { + return { + ...state, pending: false, + queue: removeFromQueue(state.queue, 'drop', action.name) + }; + } } return state; diff --git a/js/src/dapps/registry/Records/actions.js b/js/src/dapps/registry/Records/actions.js index 8b1407211..9afcb172c 100644 --- a/js/src/dapps/registry/Records/actions.js +++ b/js/src/dapps/registry/Records/actions.js @@ -1,4 +1,21 @@ -import { sha3 } from '../parity.js'; +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { sha3, api } from '../parity.js'; +import postTx from '../util/post-tx'; export const start = (name, key, value) => ({ type: 'records update start', name, key, value }); @@ -10,29 +27,36 @@ export const update = (name, key, value) => (dispatch, getState) => { const state = getState(); const account = state.accounts.selected; const contract = state.contract; - if (!contract || !account) { return; } - const fnName = key === 'A' ? 'setAddress' : 'set'; - const fn = contract.functions.find((f) => f.name === fnName); - name = name.toLowerCase(); - const options = { from: account.address }; - const values = [ sha3(name), key, value ]; + + const fnName = key === 'A' ? 'setAddress' : 'set'; + const setAddress = contract.functions.find((f) => f.name === fnName); dispatch(start(name, key, value)); - fn.estimateGas(options, values) - .then((gas) => { - options.gas = gas.mul(1.2).toFixed(0); - return fn.postTransaction(options, values); - }) - .then((data) => { + + const options = { + from: account.address + }; + const values = [ + sha3(name), + key, + value + ]; + + postTx(api, setAddress, options, values) + .then((txHash) => { dispatch(success()); }).catch((err) => { console.error(`could not update ${key} record of ${name}`); - if (err) console.error(err.stack); + + if (err) { + console.error(err.stack); + } + dispatch(fail()); }); }; diff --git a/js/src/dapps/registry/Records/records.css b/js/src/dapps/registry/Records/records.css index 72b62595d..e16ea4a15 100644 --- a/js/src/dapps/registry/Records/records.css +++ b/js/src/dapps/registry/Records/records.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -23,6 +23,16 @@ margin-top: 0; } +.box { + display: flex; + align-items: baseline; +} + .spacing { margin-left: 1em; } + +.button { + flex-grow: 0; + flex-shrink: 0; +} diff --git a/js/src/dapps/registry/Records/records.js b/js/src/dapps/registry/Records/records.js index 085a70152..f1d92cac8 100644 --- a/js/src/dapps/registry/Records/records.js +++ b/js/src/dapps/registry/Records/records.js @@ -15,22 +15,27 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Card, CardHeader, CardText } from 'material-ui/Card'; import TextField from 'material-ui/TextField'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import MenuItem from 'material-ui/MenuItem'; import RaisedButton from 'material-ui/RaisedButton'; import SaveIcon from 'material-ui/svg-icons/content/save'; -import recordTypeSelect from '../ui/record-type-select.js'; +import { update } from './actions'; import styles from './records.css'; -export default class Records extends Component { +class Records extends Component { static propTypes = { - actions: PropTypes.object.isRequired, pending: PropTypes.bool.isRequired, name: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - value: PropTypes.string.isRequired + value: PropTypes.string.isRequired, + + update: PropTypes.func.isRequired } state = { name: '', type: 'A', value: '' }; @@ -48,28 +53,36 @@ export default class Records extends Component {

You can only modify entries of names that you previously registered.

- - - { recordTypeSelect(type, this.onTypeChange, styles.spacing) } - - } - onTouchTap={ this.onSaveClick } - /> +
+ + + + + + + +
+ } + onTouchTap={ this.onSaveClick } + /> +
+
); @@ -86,6 +99,11 @@ export default class Records extends Component { }; onSaveClick = () => { const { name, type, value } = this.state; - this.props.actions.update(name, type, value); + this.props.update(name, type, value); }; } + +const mapStateToProps = (state) => state.records; +const mapDispatchToProps = (dispatch) => bindActionCreators({ update }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Records); diff --git a/js/src/dapps/registry/Records/reducers.js b/js/src/dapps/registry/Records/reducers.js index 7e8f06755..9629e8149 100644 --- a/js/src/dapps/registry/Records/reducers.js +++ b/js/src/dapps/registry/Records/reducers.js @@ -1,21 +1,39 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { isAction, isStage } from '../util/actions'; + const initialState = { pending: false, name: '', type: '', value: '' }; export default (state = initialState, action) => { - if (action.type === 'records update start') { - return { - ...state, - pending: true, - name: action.name, type: action.entry, value: action.value - }; + if (!isAction('records', 'update', action)) { + return state; } - if (action.type === 'records update error' || action.type === 'records update success') { + if (isStage('start', action)) { return { - ...state, - pending: false, + ...state, pending: true, + name: action.name, type: action.entry, value: action.value + }; + } else if (isStage('success', action) || isStage('fail', action)) { + return { + ...state, pending: false, name: initialState.name, type: initialState.type, value: initialState.value }; } diff --git a/js/src/dapps/registry/Reverse/actions.js b/js/src/dapps/registry/Reverse/actions.js new file mode 100644 index 000000000..07a1afade --- /dev/null +++ b/js/src/dapps/registry/Reverse/actions.js @@ -0,0 +1,92 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { api } from '../parity.js'; +import postTx from '../util/post-tx'; + +export const start = (action, name, address) => ({ type: `reverse ${action} start`, name, address }); + +export const success = (action) => ({ type: `reverse ${action} success` }); + +export const fail = (action) => ({ type: `reverse ${action} error` }); + +export const propose = (name, address) => (dispatch, getState) => { + const state = getState(); + const account = state.accounts.selected; + const contract = state.contract; + if (!contract || !account) { + return; + } + + name = name.toLowerCase(); + + const proposeReverse = contract.functions.find((f) => f.name === 'proposeReverse'); + + dispatch(start('propose', name, address)); + + const options = { + from: account.address + }; + const values = [ + name, + address + ]; + + postTx(api, proposeReverse, options, values) + .then((txHash) => { + dispatch(success('propose')); + }) + .catch((err) => { + console.error(`could not propose reverse ${name} for address ${address}`); + if (err) { + console.error(err.stack); + } + dispatch(fail('propose')); + }); +}; + +export const confirm = (name) => (dispatch, getState) => { + const state = getState(); + const account = state.accounts.selected; + const contract = state.contract; + if (!contract || !account) { + return; + } + name = name.toLowerCase(); + + const confirmReverse = contract.functions.find((f) => f.name === 'confirmReverse'); + + dispatch(start('confirm', name)); + + const options = { + from: account.address + }; + const values = [ + name + ]; + + postTx(api, confirmReverse, options, values) + .then((txHash) => { + dispatch(success('confirm')); + }) + .catch((err) => { + console.error(`could not confirm reverse ${name}`); + if (err) { + console.error(err.stack); + } + dispatch(fail('confirm')); + }); +}; diff --git a/js/src/dapps/registry/Reverse/index.js b/js/src/dapps/registry/Reverse/index.js new file mode 100644 index 000000000..ecaf9c3db --- /dev/null +++ b/js/src/dapps/registry/Reverse/index.js @@ -0,0 +1 @@ +export default from './reverse'; diff --git a/js/src/dapps/registry/Reverse/reducers.js b/js/src/dapps/registry/Reverse/reducers.js new file mode 100644 index 000000000..53a242c3b --- /dev/null +++ b/js/src/dapps/registry/Reverse/reducers.js @@ -0,0 +1,68 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { isAction, isStage } from '../util/actions'; + +const initialState = { + pending: false, + queue: [] +}; + +export default (state = initialState, action) => { + if (isAction('reverse', 'propose', action)) { + if (isStage('start', action)) { + return { + ...state, pending: true, + queue: state.queue.concat({ + action: 'propose', + name: action.name, + address: action.address + }) + }; + } else if (isStage('success', action) || isStage('fail', action)) { + return { + ...state, pending: false, + queue: state.queue.filter((e) => + e.action === 'propose' && + e.name === action.name && + e.address === action.address + ) + }; + } + } + + if (isAction('reverse', 'confirm', action)) { + if (isStage('start', action)) { + return { + ...state, pending: true, + queue: state.queue.concat({ + action: 'confirm', + name: action.name + }) + }; + } else if (isStage('success', action) || isStage('fail', action)) { + return { + ...state, pending: false, + queue: state.queue.filter((e) => + e.action === 'confirm' && + e.name === action.name + ) + }; + } + } + + return state; +}; diff --git a/js/src/dapps/registry/Reverse/reverse.css b/js/src/dapps/registry/Reverse/reverse.css new file mode 100644 index 000000000..0b75bfaf4 --- /dev/null +++ b/js/src/dapps/registry/Reverse/reverse.css @@ -0,0 +1,39 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.reverse { + margin: 1em; +} + +.noSpacing { + margin-top: 0; +} + +.box { + display: flex; + align-items: baseline; +} + +.spacing { + margin-right: 1em; +} + +.button { + flex-grow: 0; + flex-shrink: 0; +} + diff --git a/js/src/dapps/registry/Reverse/reverse.js b/js/src/dapps/registry/Reverse/reverse.js new file mode 100644 index 000000000..24af0a7a4 --- /dev/null +++ b/js/src/dapps/registry/Reverse/reverse.js @@ -0,0 +1,137 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { + Card, CardHeader, CardText, TextField, DropDownMenu, MenuItem, RaisedButton +} from 'material-ui'; + +import { AddIcon, CheckIcon } from '~/ui/Icons'; +import { propose, confirm } from './actions'; +import styles from './reverse.css'; + +class Reverse extends Component { + static propTypes = { + pending: PropTypes.bool.isRequired, + queue: PropTypes.array.isRequired, + + propose: PropTypes.func.isRequired, + confirm: PropTypes.func.isRequired + } + + state = { + action: 'propose', + name: '', + address: '' + }; + + render () { + const { pending } = this.props; + const { action, address, name } = this.state; + + const explanation = action === 'propose' + ? ( +

+ To propose a reverse entry for foo, you have to be the owner of it. +

+ ) : ( +

+ To confirm a proposal, send the transaction from the account that the name has been proposed for. +

+ ); + + let addressInput = null; + if (action === 'propose') { + addressInput = ( + + ); + } + + return ( + + + +

+ + To make others to find the name of an address using the registry, you can propose & confirm reverse entries. + +

+ { explanation } +
+ + + + + { addressInput } + +
+ : } + onTouchTap={ this.onSubmitClick } + /> +
+
+
+
+ ); + } + + onNameChange = (e) => { + this.setState({ name: e.target.value }); + }; + + onAddressChange = (e) => { + this.setState({ address: e.target.value }); + }; + + onActionChange = (e, i, action) => { + this.setState({ action }); + }; + + onSubmitClick = () => { + const { action, name, address } = this.state; + + if (action === 'propose') { + this.props.propose(name, address); + } else if (action === 'confirm') { + this.props.confirm(name); + } + }; +} + +const mapStateToProps = (state) => state.reverse; +const mapDispatchToProps = (dispatch) => bindActionCreators({ propose, confirm }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Reverse); diff --git a/js/src/dapps/registry/actions.js b/js/src/dapps/registry/actions.js index 0526802b9..6f4cef8e9 100644 --- a/js/src/dapps/registry/actions.js +++ b/js/src/dapps/registry/actions.js @@ -23,46 +23,76 @@ import * as lookup from './Lookup/actions.js'; import * as events from './Events/actions.js'; import * as names from './Names/actions.js'; import * as records from './Records/actions.js'; +import * as reverse from './Reverse/actions.js'; -export { addresses, accounts, lookup, events, names, records }; +export { addresses, accounts, lookup, events, names, records, reverse }; + +export const setIsTestnet = (isTestnet) => ({ type: 'set isTestnet', isTestnet }); + +export const fetchIsTestnet = () => (dispatch) => + api.net.version() + .then((netVersion) => { + dispatch(setIsTestnet( + netVersion === '2' || // morden + netVersion === '3' // ropsten + )); + }) + .catch((err) => { + console.error('could not check if testnet'); + if (err) { + console.error(err.stack); + } + }); export const setContract = (contract) => ({ type: 'set contract', contract }); export const fetchContract = () => (dispatch) => api.parity.registryAddress() - .then((address) => { - const contract = api.newContract(registryAbi, address); - dispatch(setContract(contract)); - dispatch(fetchFee()); - dispatch(fetchOwner()); - }) - .catch((err) => { - console.error('could not fetch contract'); - if (err) console.error(err.stack); - }); + .then((address) => { + const contract = api.newContract(registryAbi, address); + dispatch(setContract(contract)); + dispatch(fetchFee()); + dispatch(fetchOwner()); + }) + .catch((err) => { + console.error('could not fetch contract'); + if (err) { + console.error(err.stack); + } + }); export const setFee = (fee) => ({ type: 'set fee', fee }); const fetchFee = () => (dispatch, getState) => { const { contract } = getState(); - if (!contract) return; + if (!contract) { + return; + } + contract.instance.fee.call() - .then((fee) => dispatch(setFee(fee))) - .catch((err) => { - console.error('could not fetch fee'); - if (err) console.error(err.stack); - }); + .then((fee) => dispatch(setFee(fee))) + .catch((err) => { + console.error('could not fetch fee'); + if (err) { + console.error(err.stack); + } + }); }; export const setOwner = (owner) => ({ type: 'set owner', owner }); export const fetchOwner = () => (dispatch, getState) => { const { contract } = getState(); - if (!contract) return; + if (!contract) { + return; + } + contract.instance.owner.call() - .then((owner) => dispatch(setOwner(owner))) - .catch((err) => { - console.error('could not fetch owner'); - if (err) console.error(err.stack); - }); + .then((owner) => dispatch(setOwner(owner))) + .catch((err) => { + console.error('could not fetch owner'); + if (err) { + console.error(err.stack); + } + }); }; diff --git a/js/src/dapps/registry/addresses/actions.js b/js/src/dapps/registry/addresses/actions.js index d53df1c7f..9f4de0817 100644 --- a/js/src/dapps/registry/addresses/actions.js +++ b/js/src/dapps/registry/addresses/actions.js @@ -20,14 +20,14 @@ export const set = (addresses) => ({ type: 'addresses set', addresses }); export const fetch = () => (dispatch) => { return api.parity - .accounts() + .accountsInfo() .then((accountsInfo) => { const addresses = Object .keys(accountsInfo) .map((address) => ({ ...accountsInfo[address], address, - isAccount: !!accountsInfo[address].uuid + isAccount: true })); dispatch(set(addresses)); }) diff --git a/js/src/dapps/registry/reducers.js b/js/src/dapps/registry/reducers.js index 06b8f024b..45ca9642e 100644 --- a/js/src/dapps/registry/reducers.js +++ b/js/src/dapps/registry/reducers.js @@ -20,6 +20,10 @@ import lookupReducer from './Lookup/reducers.js'; import eventsReducer from './Events/reducers.js'; import namesReducer from './Names/reducers.js'; import recordsReducer from './Records/reducers.js'; +import reverseReducer from './Reverse/reducers.js'; + +const isTestnetReducer = (state = null, action) => + action.type === 'set isTestnet' ? action.isTestnet : state; const contractReducer = (state = null, action) => action.type === 'set contract' ? action.contract : state; @@ -31,6 +35,7 @@ const ownerReducer = (state = null, action) => action.type === 'set owner' ? action.owner : state; const initialState = { + isTestnet: isTestnetReducer(undefined, { type: '' }), accounts: accountsReducer(undefined, { type: '' }), contacts: contactsReducer(undefined, { type: '' }), contract: contractReducer(undefined, { type: '' }), @@ -39,10 +44,12 @@ const initialState = { lookup: lookupReducer(undefined, { type: '' }), events: eventsReducer(undefined, { type: '' }), names: namesReducer(undefined, { type: '' }), - records: recordsReducer(undefined, { type: '' }) + records: recordsReducer(undefined, { type: '' }), + reverse: reverseReducer(undefined, { type: '' }) }; export default (state = initialState, action) => ({ + isTestnet: isTestnetReducer(state.isTestnet, action), accounts: accountsReducer(state.accounts, action), contacts: contactsReducer(state.contacts, action), contract: contractReducer(state.contract, action), @@ -51,5 +58,6 @@ export default (state = initialState, action) => ({ lookup: lookupReducer(state.lookup, action), events: eventsReducer(state.events, action), names: namesReducer(state.names, action), - records: recordsReducer(state.records, action) + records: recordsReducer(state.records, action), + reverse: reverseReducer(state.reverse, action) }); diff --git a/js/src/dapps/registry/ui/address.css b/js/src/dapps/registry/ui/address.css new file mode 100644 index 000000000..69cd27b9e --- /dev/null +++ b/js/src/dapps/registry/ui/address.css @@ -0,0 +1,41 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.container { + display: inline-block; + vertical-align: middle; + line-height: 24px; +} + +.align { + display: inline-block; + vertical-align: top; + line-height: 24px; +} + +.link { + text-decoration: none; + color: inherit; + + &:hover { + text-decoration: underline; + } + + & abbr { + text-decoration: inherit; + } +} diff --git a/js/src/dapps/registry/ui/address.js b/js/src/dapps/registry/ui/address.js index f19894b48..e3eac2c97 100644 --- a/js/src/dapps/registry/ui/address.js +++ b/js/src/dapps/registry/ui/address.js @@ -14,34 +14,85 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React from 'react'; -import renderHash from './hash'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; + +import Hash from './hash'; +import etherscanUrl from '../util/etherscan-url'; import IdentityIcon from '../IdentityIcon'; -const container = { - display: 'inline-block', - verticalAlign: 'middle', - height: '24px' -}; -const align = { - display: 'inline-block', - verticalAlign: 'top', - lineHeight: '24px' -}; +import styles from './address.css'; -export default (address, accounts, contacts, shortenHash = true) => { - let caption; - if (accounts[address]) { - caption = ({ accounts[address].name || address }); - } else if (contacts[address]) { - caption = ({ contacts[address].name || address }); - } else { - caption = ({ shortenHash ? renderHash(address) : address }); +class Address extends Component { + static propTypes = { + address: PropTypes.string.isRequired, + accounts: PropTypes.object.isRequired, + contacts: PropTypes.object.isRequired, + isTestnet: PropTypes.bool.isRequired, + key: PropTypes.string, + shortenHash: PropTypes.bool } - return ( -
- - { caption } -
- ); -}; + + static defaultProps = { + key: 'address', + shortenHash: true + } + + render () { + const { address, accounts, contacts, isTestnet, key, shortenHash } = this.props; + + let caption; + if (accounts[address] || contacts[address]) { + const name = (accounts[address] || contacts[address] || {}).name; + caption = ( + + + { name || address } + + + ); + } else { + caption = ( + + { shortenHash ? ( + + ) : address } + + ); + } + + return ( +
+ + { caption } +
+ ); + } +} + +export default connect( + // mapStateToProps + (state) => ({ + accounts: state.accounts.all, + contacts: state.contacts, + isTestnet: state.isTestnet + }), + // mapDispatchToProps + null +)(Address); diff --git a/js/src/dapps/registry/ui/hash.css b/js/src/dapps/registry/ui/hash.css new file mode 100644 index 000000000..a16d340a1 --- /dev/null +++ b/js/src/dapps/registry/ui/hash.css @@ -0,0 +1,25 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.link { + text-decoration: none; + color: inherit; + + &:hover { + text-decoration: underline; + } +} diff --git a/js/src/dapps/registry/ui/hash.js b/js/src/dapps/registry/ui/hash.js index 7a586b256..6eeaab7b2 100644 --- a/js/src/dapps/registry/ui/hash.js +++ b/js/src/dapps/registry/ui/hash.js @@ -14,11 +14,53 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React from 'react'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; -export default (hash) => { - const shortened = hash.length > (2 + 9 + 9) - ? hash.substr(2, 9) + '...' + hash.slice(-9) - : hash.slice(2); - return ({ shortened }); -}; +import etherscanUrl from '../util/etherscan-url'; + +import styles from './hash.css'; + +const leading0x = /^0x/; + +class Hash extends Component { + static propTypes = { + hash: PropTypes.string.isRequired, + isTestnet: PropTypes.bool.isRequired, + linked: PropTypes.bool + } + + static defaultProps = { + linked: false + } + + render () { + const { hash, isTestnet, linked } = this.props; + + let shortened = hash.toLowerCase().replace(leading0x, ''); + shortened = shortened.length > (6 + 6) + ? shortened.substr(0, 6) + '...' + shortened.slice(-6) + : shortened; + + if (linked) { + return ( + + { shortened } + + ); + } + + return ({ shortened }); + } +} + +export default connect( + (state) => ({ // mapStateToProps + isTestnet: state.isTestnet + }), + null // mapDispatchToProps +)(Hash); diff --git a/js/src/dapps/registry/ui/record-type-select.js b/js/src/dapps/registry/util/actions.js similarity index 57% rename from js/src/dapps/registry/ui/record-type-select.js rename to js/src/dapps/registry/util/actions.js index e146dbf7d..0f4f350fc 100644 --- a/js/src/dapps/registry/ui/record-type-select.js +++ b/js/src/dapps/registry/util/actions.js @@ -14,14 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React from 'react'; -import DropDownMenu from 'material-ui/DropDownMenu'; -import MenuItem from 'material-ui/MenuItem'; +export const isAction = (ns, type, action) => { + return action.type.slice(0, ns.length + 1 + type.length) === `${ns} ${type}`; +}; -export default (value, onSelect, className = '') => ( - - - - - -); +export const isStage = (stage, action) => { + return action.type.slice(-1 - stage.length) === ` ${stage}`; +}; + +export const addToQueue = (queue, action, name) => { + return queue.concat({ action, name }); +}; + +export const removeFromQueue = (queue, action, name) => { + return queue.filter((e) => e.action === action && e.name === name); +}; diff --git a/js/src/dapps/registry/util/etherscan-url.js b/js/src/dapps/registry/util/etherscan-url.js new file mode 100644 index 000000000..26fb9a84f --- /dev/null +++ b/js/src/dapps/registry/util/etherscan-url.js @@ -0,0 +1,26 @@ +// Copyright 2015, 2016 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 . + +const leading0x = /^0x/; + +const etherscanUrl = (hash, isTestnet) => { + hash = hash.toLowerCase().replace(leading0x, ''); + const type = hash.length === 40 ? 'address' : 'tx'; + + return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/${type}/0x${hash}`; +}; + +export default etherscanUrl; diff --git a/js/src/dapps/registry/util/post-tx.js b/js/src/dapps/registry/util/post-tx.js new file mode 100644 index 000000000..84326dcab --- /dev/null +++ b/js/src/dapps/registry/util/post-tx.js @@ -0,0 +1,36 @@ +// Copyright 2015, 2016 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 . + +const postTx = (api, method, opt = {}, values = []) => { + opt = Object.assign({}, opt); + + return method.estimateGas(opt, values) + .then((gas) => { + opt.gas = gas.mul(1.2).toFixed(0); + return method.postTransaction(opt, values); + }) + .then((reqId) => { + return api.pollMethod('parity_checkRequest', reqId); + }) + .catch((err) => { + if (err && err.type === 'REQUEST_REJECTED') { + throw new Error('The request has been rejected.'); + } + throw err; + }); +}; + +export default postTx; diff --git a/js/src/dapps/signaturereg/Application/application.css b/js/src/dapps/signaturereg/Application/application.css index 4675b064e..c29493deb 100644 --- a/js/src/dapps/signaturereg/Application/application.css +++ b/js/src/dapps/signaturereg/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/Button/button.css b/js/src/dapps/signaturereg/Button/button.css index 444359c79..d0b91accf 100644 --- a/js/src/dapps/signaturereg/Button/button.css +++ b/js/src/dapps/signaturereg/Button/button.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/Events/events.css b/js/src/dapps/signaturereg/Events/events.css index 5e9960e0e..ebdcfbe09 100644 --- a/js/src/dapps/signaturereg/Events/events.css +++ b/js/src/dapps/signaturereg/Events/events.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/Header/header.css b/js/src/dapps/signaturereg/Header/header.css index 8d95b959c..7d9d57b39 100644 --- a/js/src/dapps/signaturereg/Header/header.css +++ b/js/src/dapps/signaturereg/Header/header.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/IdentityIcon/identityIcon.css b/js/src/dapps/signaturereg/IdentityIcon/identityIcon.css index 3fa1df99e..6e708cac3 100644 --- a/js/src/dapps/signaturereg/IdentityIcon/identityIcon.css +++ b/js/src/dapps/signaturereg/IdentityIcon/identityIcon.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/Import/import.css b/js/src/dapps/signaturereg/Import/import.css index 8f9788c34..46b7c378e 100644 --- a/js/src/dapps/signaturereg/Import/import.css +++ b/js/src/dapps/signaturereg/Import/import.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/Loading/loading.css b/js/src/dapps/signaturereg/Loading/loading.css index b77d1a237..4885f1af2 100644 --- a/js/src/dapps/signaturereg/Loading/loading.css +++ b/js/src/dapps/signaturereg/Loading/loading.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index cd0fd59c7..c615399ae 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -49,7 +49,7 @@ export function attachInterface (callback) { return Promise .all([ registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), - api.parity.accounts() + api.parity.accountsInfo() ]); }) .then(([address, accountsInfo]) => { @@ -58,7 +58,6 @@ export function attachInterface (callback) { const contract = api.newContract(abis.signaturereg, address); const accounts = Object .keys(accountsInfo) - .filter((address) => accountsInfo[address].uuid) .reduce((obj, address) => { const info = accountsInfo[address] || {}; diff --git a/js/src/dapps/style.css b/js/src/dapps/style.css index b177a15f9..9139d3e73 100644 --- a/js/src/dapps/style.css +++ b/js/src/dapps/style.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.css b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.css index f6f8476bb..bf3e2809e 100644 --- a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.css +++ b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Accounts/actions.js b/js/src/dapps/tokenreg/Accounts/actions.js index 85e4aaceb..97684cb81 100644 --- a/js/src/dapps/tokenreg/Accounts/actions.js +++ b/js/src/dapps/tokenreg/Accounts/actions.js @@ -36,11 +36,10 @@ export const setSelectedAccount = (address) => ({ export const loadAccounts = () => (dispatch) => { api.parity - .accounts() + .accountsInfo() .then((accountsInfo) => { const accountsList = Object .keys(accountsInfo) - .filter((address) => accountsInfo[address].uuid) .map((address) => ({ ...accountsInfo[address], address diff --git a/js/src/dapps/tokenreg/Actions/Query/query.js b/js/src/dapps/tokenreg/Actions/Query/query.js index fd680769c..452b387a5 100644 --- a/js/src/dapps/tokenreg/Actions/Query/query.js +++ b/js/src/dapps/tokenreg/Actions/Query/query.js @@ -173,20 +173,26 @@ export default class QueryAction extends Component { onQueryKeyChange = (event, index, queryKey) => { this.setState({ queryKey, - form: { valid: false, value: '' } + form: { + valid: false, + value: '' + } }); } onChange = (valid, value) => { this.setState({ form: { - valid, value + valid, + value } }); } onQuery = () => { - if (!this.state.form.valid) return; + if (!this.state.form.valid) { + return; + } const { queryKey, form } = this.state; diff --git a/js/src/dapps/tokenreg/Actions/Register/register.js b/js/src/dapps/tokenreg/Actions/Register/register.js index 54df411c7..ec1199f3d 100644 --- a/js/src/dapps/tokenreg/Actions/Register/register.js +++ b/js/src/dapps/tokenreg/Actions/Register/register.js @@ -128,8 +128,12 @@ export default class RegisterAction extends Component { renderContent () { const { error, complete } = this.props; - if (error) return this.renderError(); - if (complete) return this.renderComplete(); + if (error) { + return this.renderError(); + } else if (complete) { + return this.renderComplete(); + } + return this.renderForm(); } diff --git a/js/src/dapps/tokenreg/Actions/actions.css b/js/src/dapps/tokenreg/Actions/actions.css index d8eba57f1..187877e7c 100644 --- a/js/src/dapps/tokenreg/Actions/actions.css +++ b/js/src/dapps/tokenreg/Actions/actions.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Application/application.css b/js/src/dapps/tokenreg/Application/application.css index 94ca6302e..e01bde6e1 100644 --- a/js/src/dapps/tokenreg/Application/application.css +++ b/js/src/dapps/tokenreg/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Chip/chip.css b/js/src/dapps/tokenreg/Chip/chip.css index 5c28d56ce..4ed334135 100644 --- a/js/src/dapps/tokenreg/Chip/chip.css +++ b/js/src/dapps/tokenreg/Chip/chip.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Chip/chip.js b/js/src/dapps/tokenreg/Chip/chip.js index f26f9a0c6..b5407af11 100644 --- a/js/src/dapps/tokenreg/Chip/chip.js +++ b/js/src/dapps/tokenreg/Chip/chip.js @@ -57,7 +57,9 @@ export default class CustomChip extends Component { } renderIcon (isAddress, address) { - if (!isAddress) return; + if (!isAddress) { + return; + } return ( @@ -83,7 +85,9 @@ export default class InputText extends Component { } renderIsValid () { - if (this.state.loading || !this.state.valid) return; + if (this.state.loading || !this.state.valid) { + return; + } return (
@@ -120,8 +124,13 @@ export default class InputText extends Component { } onKeyDown = (event) => { - if (!this.props.onEnter) return; - if (event.keyCode !== 13) return; + if (!this.props.onEnter) { + return; + } + + if (event.keyCode !== 13) { + return; + } this.props.onEnter(); } diff --git a/js/src/dapps/tokenreg/Inputs/inputs.css b/js/src/dapps/tokenreg/Inputs/inputs.css index f8bea1148..0e5e406f0 100644 --- a/js/src/dapps/tokenreg/Inputs/inputs.css +++ b/js/src/dapps/tokenreg/Inputs/inputs.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Inputs/validation.js b/js/src/dapps/tokenreg/Inputs/validation.js index 3b14bd0a4..cbc4e2a89 100644 --- a/js/src/dapps/tokenreg/Inputs/validation.js +++ b/js/src/dapps/tokenreg/Inputs/validation.js @@ -71,9 +71,14 @@ const validateAddress = (address) => { const validateTokenAddress = (address, contract, simple) => { const addressValidation = validateAddress(address); - if (!addressValidation.valid) return addressValidation; - if (simple) return addressValidation; + if (!addressValidation.valid) { + return addressValidation; + } + + if (simple) { + return addressValidation; + } return getTokenTotalSupply(address) .then(balance => { @@ -96,7 +101,10 @@ const validateTokenAddress = (address, contract, simple) => { }); }) .then((result) => { - if (result) return result; + if (result) { + return result; + } + return addressValidation; }); }; @@ -130,7 +138,10 @@ const validateTLA = (tla, contract, simple) => { } }) .then((result) => { - if (result) return result; + if (result) { + return result; + } + return { value: fTLA, error: null, @@ -215,16 +226,28 @@ const validateURL = (string) => { }; export const validate = (value, type, contract) => { - if (type === ADDRESS_TYPE) return validateAddress(value); - if (type === TOKEN_ADDRESS_TYPE) return validateTokenAddress(value, contract); - if (type === SIMPLE_TOKEN_ADDRESS_TYPE) return validateTokenAddress(value, contract, true); - if (type === TLA_TYPE) return validateTLA(value, contract); - if (type === SIMPLE_TLA_TYPE) return validateTLA(value, contract, true); - if (type === UINT_TYPE) return validateUint(value); - if (type === DECIMAL_TYPE) return validateDecimal(value); - if (type === STRING_TYPE) return validateString(value); - if (type === HEX_TYPE) return validateHex(value); - if (type === URL_TYPE) return validateURL(value); - - return { valid: true, error: null }; + switch (type) { + case ADDRESS_TYPE: + return validateAddress(value); + case TOKEN_ADDRESS_TYPE: + return validateTokenAddress(value, contract); + case SIMPLE_TOKEN_ADDRESS_TYPE: + return validateTokenAddress(value, contract, true); + case TLA_TYPE: + return validateTLA(value, contract); + case SIMPLE_TLA_TYPE: + return validateTLA(value, contract, true); + case UINT_TYPE: + return validateUint(value); + case DECIMAL_TYPE: + return validateDecimal(value); + case STRING_TYPE: + return validateString(value); + case HEX_TYPE: + return validateHex(value); + case URL_TYPE: + return validateURL(value); + default: + return { valid: true, error: null }; + } }; diff --git a/js/src/dapps/tokenreg/Loading/loading.css b/js/src/dapps/tokenreg/Loading/loading.css index dbdb98e42..97d16ad9b 100644 --- a/js/src/dapps/tokenreg/Loading/loading.css +++ b/js/src/dapps/tokenreg/Loading/loading.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Status/actions.js b/js/src/dapps/tokenreg/Status/actions.js index 057a44e65..e23afbbc1 100644 --- a/js/src/dapps/tokenreg/Status/actions.js +++ b/js/src/dapps/tokenreg/Status/actions.js @@ -119,7 +119,9 @@ export const subscribeEvents = () => (dispatch, getState) => { return; } - if (!logs || logs.length === 0) return; + if (!logs || logs.length === 0) { + return; + } logs.forEach(log => { const event = log.event; diff --git a/js/src/dapps/tokenreg/Status/status.css b/js/src/dapps/tokenreg/Status/status.css index 7333194b7..d465ba1df 100644 --- a/js/src/dapps/tokenreg/Status/status.css +++ b/js/src/dapps/tokenreg/Status/status.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/dapps/tokenreg/Tokens/Token/add-meta.js b/js/src/dapps/tokenreg/Tokens/Token/add-meta.js index 60be410e9..3dc4bdbe1 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/add-meta.js +++ b/js/src/dapps/tokenreg/Tokens/Token/add-meta.js @@ -46,7 +46,9 @@ export default class AddMeta extends Component { state = initState; render () { - if (!this.props.isTokenOwner) return null; + if (!this.props.isTokenOwner) { + return null; + } return (
- -
); + return ( +
+ +
+ ); } - if (!meta) return; + if (!meta) { + return null; + } const metaData = metaDataKeys.find(m => m.value === meta.query); if (!meta.value) { - return (
-

- No - { metaData.label.toLowerCase() } - meta-data... -

-
); + return ( +
+

+ No + { metaData.label.toLowerCase() } + meta-data... +

+
+ ); } if (meta.query === 'IMG') { const imageHash = meta.value.replace(/^0x/, ''); - return (
-

- - { metaData.label } - meta-data: -

-
- + return ( +
+

+ + { metaData.label } + meta-data: +

+
+ +
-
); + ); } if (meta.query === 'A') { const address = meta.value.slice(0, 42); - return (
+ return ( +
+

+ + { metaData.label } + meta-data: +

+

+ { api.util.toChecksumAddress(address) } +

+
+ ); + } + + return ( +

{ metaData.label } meta-data:

-

- { api.util.toChecksumAddress(address) } -

-
); - } - - return (
-

- - { metaData.label } - meta-data: -

-

{ meta.value }

-
); +

{ meta.value }

+
+ ); } renderMetaPending () { const isMetaPending = this.props.metaPending; - if (!isMetaPending) return; - return (
-

- Meta-Data pending... -

-
); + if (!isMetaPending) { + return null; + } + + return ( +
+

+ Meta-Data pending... +

+
+ ); } renderMetaMined () { const isMetaMined = this.props.metaMined; - if (!isMetaMined) return; - return (
-

- Meta-Data saved on the blockchain! -

-
); + if (!isMetaMined) { + return null; + } + + return ( +
+

+ Meta-Data saved on the blockchain! +

+
+ ); } onUnregister = () => { diff --git a/js/src/dapps/tokenreg/Tokens/tokens.css b/js/src/dapps/tokenreg/Tokens/tokens.css index 20aaecef4..7bb105659 100644 --- a/js/src/dapps/tokenreg/Tokens/tokens.css +++ b/js/src/dapps/tokenreg/Tokens/tokens.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/error_pages.css b/js/src/error_pages.css index 0b098d29a..eca8a506a 100644 --- a/js/src/error_pages.css +++ b/js/src/error_pages.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/index.js b/js/src/index.js index b90f9c61a..72f26e12f 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -36,6 +36,7 @@ import ContextProvider from '~/ui/ContextProvider'; import muiTheme from '~/ui/Theme'; import MainApplication from './main'; +import { patchApi } from '~/util/tx'; import { setApi } from '~/redux/providers/apiActions'; import './environment'; @@ -52,12 +53,7 @@ if (process.env.NODE_ENV === 'development') { } const AUTH_HASH = '#/auth?'; -const parityUrl = process.env.PARITY_URL || - ( - process.env.NODE_ENV === 'production' - ? window.location.host - : '127.0.0.1:8180' - ); +const parityUrl = process.env.PARITY_URL || window.location.host; let token = null; if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { @@ -65,6 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { } const api = new SecureApi(`ws://${parityUrl}`, token); +patchApi(api); ContractInstances.create(api); const store = initStore(api, hashHistory); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 3f36a93b3..1f6caca4a 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -26,7 +26,7 @@ export default { } }, - accounts: { + accountsInfo: { desc: 'returns a map of accounts as an object', params: [], returns: { @@ -36,20 +36,12 @@ export default { name: { type: String, desc: 'Account name' - }, - meta: { - type: String, - desc: 'Encoded JSON string the defines additional account metadata' - }, - uuid: { - type: String, - desc: 'The account Uuid, or null if not available/unknown/not applicable.' } } } }, - accountsInfo: { + allAccountsInfo: { desc: 'returns a map of accounts as an object', params: [], returns: { @@ -86,6 +78,22 @@ export default { } }, + chainStatus: { + desc: 'Returns the information on warp sync blocks', + params: [], + returns: { + type: Object, + desc: 'The status object', + details: { + blockGap: { + type: Array, + desc: 'Describes the gap in the blockchain, if there is one: (first, last)', + optional: true + } + } + } + }, + checkRequest: { desc: 'Returns the transactionhash of the requestId (received from parity_postTransaction) if the request was confirmed', params: [ @@ -228,6 +236,29 @@ export default { } }, + getDappsAddresses: { + desc: 'Returns the list of accounts available to a specific dapp', + params: [ + { + type: String, + desc: 'Dapp Id' + } + ], + returns: { + type: Array, + desc: 'The list of available accounts' + } + }, + + getNewDappsWhitelist: { + desc: 'Returns the list of accounts available to a new dapps', + params: [], + returns: { + type: Array, + desc: 'The list of available accounts' + } + }, + hashContent: { desc: 'Creates a hash of the file as retrieved', params: [ @@ -274,6 +305,15 @@ export default { } }, + listRecentDapps: { + desc: 'Returns a list of the most recent active dapps', + params: [], + returns: { + type: Array, + desc: 'Array of Dapp Ids' + } + }, + removeAddress: { desc: 'Removes an address from the addressbook', params: [ @@ -578,6 +618,24 @@ export default { } }, + setDappsAddresses: { + desc: 'Sets the available addresses for a dapp', + params: [ + { + type: String, + desc: 'Dapp Id' + }, + { + type: Array, + desc: 'Array of available accounts available to the dapp' + } + ], + returns: { + type: Boolean, + desc: 'True if the call succeeded' + } + }, + setExtraData: { desc: 'Changes extra data for newly mined blocks', params: [ @@ -637,6 +695,20 @@ export default { } }, + setNewDappsWhitelist: { + desc: 'Sets the list of accounts available to new dapps', + params: [ + { + type: Array, + desc: 'List of accounts available by default' + } + ], + returns: { + type: Boolean, + desc: 'True if the call succeeded' + } + }, + setTransactionsLimit: { desc: 'Changes limit for transactions in queue.', params: [ diff --git a/js/src/jsonrpc/interfaces/signer.js b/js/src/jsonrpc/interfaces/signer.js index e4ffc1e03..2e0559898 100644 --- a/js/src/jsonrpc/interfaces/signer.js +++ b/js/src/jsonrpc/interfaces/signer.js @@ -26,6 +26,15 @@ export default { } }, + generateWebProxyAccessToken: { + desc: 'Generates a new web proxy access token', + params: [], + returns: { + type: String, + desc: 'The new web proxy access token' + } + }, + requestsToConfirm: { desc: 'Returns a list of the transactions requiring authorization', params: [], diff --git a/js/src/modals/AddContract/addContract.css b/js/src/modals/AddContract/addContract.css index 0821a180e..53d47c1a4 100644 --- a/js/src/modals/AddContract/addContract.css +++ b/js/src/modals/AddContract/addContract.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 0d04438c3..c5e7aac61 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -14,38 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; -import ContentAdd from 'material-ui/svg-icons/content/add'; -import ContentClear from 'material-ui/svg-icons/content/clear'; -import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; -import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; +import { FormattedMessage } from 'react-intl'; +import { newError } from '~/redux/actions'; import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; -import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation'; +import { AddIcon, CancelIcon, NextIcon, PrevIcon } from '~/ui/Icons'; -import { eip20, wallet } from '~/contracts/abi'; - -const ABI_TYPES = [ - { - label: 'Token', readOnly: true, value: JSON.stringify(eip20), - type: 'token', - description: (A standard ERC 20 token) - }, - { - label: 'Multisig Wallet', readOnly: true, - type: 'multisig', - value: JSON.stringify(wallet), - description: (Official Multisig contract: see contract code) - }, - { - label: 'Custom Contract', value: '', - type: 'custom', - description: 'Contract created from custom ABI' - } -]; - -const STEPS = [ 'choose a contract type', 'enter contract details' ]; +import Store from './store'; +@observer export default class AddContract extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -56,219 +35,217 @@ export default class AddContract extends Component { onClose: PropTypes.func }; - state = { - abi: '', - abiError: ERRORS.invalidAbi, - abiType: ABI_TYPES[2], - abiTypeIndex: 2, - abiParsed: null, - address: '', - addressError: ERRORS.invalidAddress, - name: '', - nameError: ERRORS.invalidName, - description: '', - step: 0 - }; - - componentDidMount () { - this.onChangeABIType(null, this.state.abiTypeIndex); - } + store = new Store(this.context.api, this.props.contracts); render () { - const { step } = this.state; + const { step } = this.store; return ( - { this.renderStep(step) } + steps={ [ + , + + ] } + visible> + { this.renderStep() } ); } - renderStep (step) { + renderStep () { + const { step } = this.store; + switch (step) { case 0: return this.renderContractTypeSelector(); + default: return this.renderFields(); } } renderContractTypeSelector () { - const { abiTypeIndex } = this.state; + const { abiTypeIndex, abiTypes } = this.store; return ( ); } renderDialogActions () { - const { addressError, nameError, step } = this.state; - const hasError = !!(addressError || nameError); + const { step } = this.store; const cancelBtn = (
@@ -129,9 +148,17 @@ export default class DetailsStep extends Component { return ( + } + label={ + + } + onChange={ this.onEditTestPassword } + onSubmit={ this.testPassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleTestPassword } - onChange={ this.onEditCurrent } /> + type='password' />
-
+ label={ + + } + onActive={ this.onActivateChangeTab }> +
+ } + label={ + + } + onChange={ this.onEditCurrentPassword } + onSubmit={ this.changePassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditCurrent } /> + type='password' /> + } + label={ + + } + onChange={ this.onEditNewPasswordHint } + onSubmit={ this.changePassword } submitOnBlur={ false } - value={ passwordHint } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditHint } /> + value={ passwordHint } />
+ } + label={ + + } + onChange={ this.onEditNewPassword } + onSubmit={ this.changePassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditNew } /> + type='password' />
+ } + hint={ + + } + label={ + + } + onChange={ this.onEditNewPasswordRepeat } + onSubmit={ this.changePassword } submitOnBlur={ false } - error={ repeatError } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditRepeatNew } /> + type='password' />
@@ -225,176 +244,118 @@ class PasswordManager extends Component { } renderDialogActions () { + const { actionTab, busy, isRepeatValid } = this.store; const { onClose } = this.props; - const { action, waiting, repeatValid } = this.state; const cancelBtn = (
@@ -243,7 +243,7 @@ class WalletSettings extends Component { @@ -329,7 +329,7 @@ function mapStateToProps (initState, initProps) { const senders = pick(accounts, owners); return () => { - return { accounts: accountsInfo, senders }; + return { accountsInfo, senders }; }; } diff --git a/js/src/modals/WalletSettings/walletSettingsStore.js b/js/src/modals/WalletSettings/walletSettingsStore.js index 6db4469c3..6d70b8de3 100644 --- a/js/src/modals/WalletSettings/walletSettingsStore.js +++ b/js/src/modals/WalletSettings/walletSettingsStore.js @@ -28,6 +28,8 @@ const STEPS = { }; export default class WalletSettingsStore { + accounts = {}; + @observable step = null; @observable requests = []; @observable deployState = ''; diff --git a/js/src/modals/index.js b/js/src/modals/index.js index 4534c8aba..702f4d414 100644 --- a/js/src/modals/index.js +++ b/js/src/modals/index.js @@ -16,8 +16,10 @@ import AddAddress from './AddAddress'; import AddContract from './AddContract'; +import AddDapps from './AddDapps'; import CreateAccount from './CreateAccount'; import CreateWallet from './CreateWallet'; +import DappPermissions from './DappPermissions'; import DeleteAccount from './DeleteAccount'; import DeployContract from './DeployContract'; import EditMeta from './EditMeta'; @@ -35,8 +37,10 @@ import WalletSettings from './WalletSettings'; export { AddAddress, AddContract, + AddDapps, CreateAccount, CreateWallet, + DappPermissions, DeleteAccount, DeployContract, EditMeta, diff --git a/js/src/redux/actions.js b/js/src/redux/actions.js index 0be511bd3..0618adfd3 100644 --- a/js/src/redux/actions.js +++ b/js/src/redux/actions.js @@ -16,6 +16,7 @@ import { newError } from '~/ui/Errors/actions'; import { setAddressImage } from './providers/imagesActions'; +import { showSnackbar } from './providers/snackbarActions'; import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions'; import { toggleView } from '~/views/Settings/actions'; @@ -23,6 +24,7 @@ export { newError, clearStatusLogs, setAddressImage, + showSnackbar, toggleStatusLogs, toggleStatusRefresh, toggleView diff --git a/js/src/redux/providers/balances.js b/js/src/redux/providers/balances.js index 2f08c7752..0a73ef4f9 100644 --- a/js/src/redux/providers/balances.js +++ b/js/src/redux/providers/balances.js @@ -60,7 +60,7 @@ export default class Balances { subscribeAccountsInfo () { this._api - .subscribe('parity_accountsInfo', (error, accountsInfo) => { + .subscribe('parity_allAccountsInfo', (error, accountsInfo) => { if (error) { return; } diff --git a/js/src/redux/providers/balancesActions.js b/js/src/redux/providers/balancesActions.js index 36e3fbb7c..b99c44fcb 100644 --- a/js/src/redux/providers/balancesActions.js +++ b/js/src/redux/providers/balancesActions.js @@ -48,7 +48,7 @@ function setBalances (_balances) { const balance = Object.assign({}, balances[address]); const { tokens, txCount = balance.txCount } = nextBalances[address]; - const nextTokens = [].concat(balance.tokens); + const nextTokens = balance.tokens.slice(); tokens.forEach((t) => { const { token, value } = t; @@ -175,7 +175,7 @@ export function fetchBalances (_addresses) { const { api, personal } = getState(); const { visibleAccounts, accounts } = personal; - const addresses = uniq(_addresses || visibleAccounts || []); + const addresses = uniq((_addresses || visibleAccounts || []).concat(Object.keys(accounts))); if (addresses.length === 0) { return Promise.resolve(); @@ -183,7 +183,7 @@ export function fetchBalances (_addresses) { const fullFetch = addresses.length === 1; - const addressesToFetch = uniq(addresses.concat(Object.keys(accounts))); + const addressesToFetch = uniq(addresses); return Promise .all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch))) diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 0bd2e5157..c81aa7e67 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -14,10 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { uniq } from 'lodash'; -import debounce from 'debounce'; +import { uniq, range, debounce } from 'lodash'; -import ABI from '~/contracts/abi/certifier.json'; +import CertifierABI from '~/contracts/abi/certifier.json'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification, removeCertification } from './actions'; @@ -32,6 +31,7 @@ const updatableFilter = (api, onFilter) => { api.eth.uninstallFilter(filterId); }); } + filter = (filter || Promise.resolve()) .then(() => api.eth.newFilter({ fromBlock: 0, @@ -46,7 +46,10 @@ const updatableFilter = (api, onFilter) => { .catch((err) => { console.error('Failed to create certifications filter:', err); }); + + return filter; }; + return update; }; @@ -54,12 +57,52 @@ export default class CertificationsMiddleware { toMiddleware () { const api = Contracts.get()._api; const badgeReg = Contracts.get().badgeReg; - const contract = new Contract(api, ABI); + + const contract = new Contract(api, CertifierABI); const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); const Revoked = contract.events.find((e) => e.name === 'Revoked'); return (store) => { - const onLogs = (logs) => { + let certifiers = []; + let addresses = []; + let filterChanged = false; + let filter = null; + let badgeRegFilter = null; + let fetchCertifiersPromise = null; + + const updateFilter = updatableFilter(api, (filterId) => { + filterChanged = true; + filter = filterId; + }); + + const badgeRegUpdateFilter = updatableFilter(api, (filterId) => { + filterChanged = true; + badgeRegFilter = filterId; + }); + + badgeReg + .getContract() + .then((badgeRegContract) => { + return badgeRegUpdateFilter(badgeRegContract.address, [ + badgeRegContract.instance.Registered.signature, + badgeRegContract.instance.Unregistered.signature, + badgeRegContract.instance.MetaChanged.signature, + badgeRegContract.instance.AddressChanged.signature + ]); + }) + .then(() => { + shortFetchChanges(); + + api.subscribe('eth_blockNumber', (err) => { + if (err) { + return; + } + + fetchChanges(); + }); + }); + + function onLogs (logs) { logs = contract.parseEventLogs(logs); logs.forEach((log) => { const certifier = certifiers.find((c) => c.address === log.address); @@ -74,73 +117,106 @@ export default class CertificationsMiddleware { store.dispatch(addCertification(log.params.who.value, id, name, title, icon)); } }); - }; + } - let filter = null; + function onBadgeRegLogs (logs) { + const ids = logs.map((log) => log.params.id.value.toNumber()); + return fetchCertifiers(uniq(ids)); + } - const onFilter = (filterId) => { - filter = filterId; - api.eth.getFilterLogs(filterId) - .then(onLogs) + function _fetchChanges () { + const method = filterChanged + ? 'getFilterLogs' + : 'getFilterChanges'; + + filterChanged = false; + + api.eth[method](badgeRegFilter) + .then(onBadgeRegLogs) .catch((err) => { - console.error('Failed to fetch certifier events:', err); - }); - }; - - const fetchChanges = debounce(() => { - api.eth.getFilterChanges(filter) + console.error('Failed to fetch badge reg events:', err); + }) + .then(() => api.eth[method](filter)) .then(onLogs) .catch((err) => { console.error('Failed to fetch new certifier events:', err); }); - }, 10 * 1000, true); - api.subscribe('eth_blockNumber', (err) => { - if (err) return; - fetchChanges(); - }); + } - const updateFilter = updatableFilter(api, onFilter); - let certifiers = []; - let accounts = []; // these are addresses + const shortFetchChanges = debounce(_fetchChanges, 0.5 * 1000, { leading: true }); + const fetchChanges = debounce(shortFetchChanges, 10 * 1000, { leading: true }); - const fetchConfirmedEvents = () => { - updateFilter(certifiers.map((c) => c.address), [ + function fetchConfirmedEvents () { + return updateFilter(certifiers.map((c) => c.address), [ [ Confirmed.signature, Revoked.signature ], - accounts - ]); - }; + addresses + ]).then(() => shortFetchChanges()); + } + + function fetchCertifiers (ids = []) { + if (fetchCertifiersPromise) { + return fetchCertifiersPromise; + } + + let fetchEvents = false; + + const idsPromise = (certifiers.length === 0) + ? badgeReg.certifierCount().then((count) => { + return range(count); + }) + : Promise.resolve(ids); + + fetchCertifiersPromise = idsPromise + .then((ids) => { + const promises = ids.map((id) => { + return badgeReg.fetchCertifier(id) + .then((cert) => { + if (!certifiers.some((c) => c.id === cert.id)) { + certifiers = certifiers.concat(cert); + fetchEvents = true; + } + }) + .catch((err) => { + if (/does not exist/.test(err.toString())) { + return console.warn(err.toString()); + } + + console.warn(`Could not fetch certifier ${id}:`, err); + }); + }); + + return Promise + .all(promises) + .then(() => { + fetchCertifiersPromise = null; + + if (fetchEvents) { + return fetchConfirmedEvents(); + } + }); + }); + + return fetchCertifiersPromise; + } return (next) => (action) => { switch (action.type) { case 'fetchCertifiers': - badgeReg.certifierCount().then((count) => { - new Array(+count).fill(null).forEach((_, id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - if (!certifiers.some((c) => c.id === cert.id)) { - certifiers = certifiers.concat(cert); - fetchConfirmedEvents(); - } - }) - .catch((err) => { - console.warn(`Could not fetch certifier ${id}:`, err); - }); - }); - }); + fetchConfirmedEvents(); break; case 'fetchCertifications': const { address } = action; - if (!accounts.includes(address)) { - accounts = accounts.concat(address); + if (!addresses.includes(address)) { + addresses = addresses.concat(address); fetchConfirmedEvents(); } break; case 'setVisibleAccounts': - const { addresses } = action; - accounts = uniq(accounts.concat(addresses)); + const _addresses = action.addresses || []; + addresses = uniq(addresses.concat(_addresses)); fetchConfirmedEvents(); break; diff --git a/js/src/redux/providers/personal.js b/js/src/redux/providers/personal.js index 5a19b1c7f..a507ffd2c 100644 --- a/js/src/redux/providers/personal.js +++ b/js/src/redux/providers/personal.js @@ -29,9 +29,9 @@ export default class Personal { _subscribeAccountsInfo () { this._api - .subscribe('parity_accountsInfo', (error, accountsInfo) => { + .subscribe('parity_allAccountsInfo', (error, accountsInfo) => { if (error) { - console.error('parity_accountsInfo', error); + console.error('parity_allAccountsInfo', error); return; } @@ -41,7 +41,7 @@ export default class Personal { _removeDeleted () { this._api.parity - .accountsInfo() + .allAccountsInfo() .then((accountsInfo) => { return Promise.all( Object diff --git a/js/src/redux/providers/personalActions.js b/js/src/redux/providers/personalActions.js index 7ca7c3374..1ed39c05a 100644 --- a/js/src/redux/providers/personalActions.js +++ b/js/src/redux/providers/personalActions.js @@ -14,14 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { isEqual } from 'lodash'; +import { isEqual, intersection } from 'lodash'; import { fetchBalances } from './balancesActions'; import { attachWallets } from './walletActions'; +import Contract from '~/api/contract'; import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore'; +import WalletsUtils from '~/util/wallets'; +import { wallet as WalletAbi } from '~/contracts/abi'; export function personalAccountsInfo (accountsInfo) { + const addresses = []; const accounts = {}; const contacts = {}; const contracts = {}; @@ -32,6 +36,7 @@ export function personalAccountsInfo (accountsInfo) { .filter((account) => account.uuid || !account.meta.deleted) .forEach((account) => { if (account.uuid) { + addresses.push(account.address); accounts[account.address] = account; } else if (account.meta.wallet) { account.wallet = true; @@ -46,14 +51,52 @@ export function personalAccountsInfo (accountsInfo) { // Load user contracts for Method Decoding MethodDecodingStore.loadContracts(contracts); - return (dispatch) => { - const data = { - accountsInfo, - accounts, contacts, contracts, wallets - }; + return (dispatch, getState) => { + const { api } = getState(); - dispatch(_personalAccountsInfo(data)); - dispatch(attachWallets(wallets)); + const _fetchOwners = Object + .values(wallets) + .map((wallet) => { + const walletContract = new Contract(api, WalletAbi); + return WalletsUtils.fetchOwners(walletContract.at(wallet.address)); + }); + + Promise + .all(_fetchOwners) + .then((walletsOwners) => { + return Object + .values(wallets) + .map((wallet, index) => { + wallet.owners = walletsOwners[index].map((owner) => ({ + address: owner, + name: accountsInfo[owner] && accountsInfo[owner].name || owner + })); + + return wallet; + }); + }) + .then((_wallets) => { + _wallets.forEach((wallet) => { + const owners = wallet.owners.map((o) => o.address); + + // Owners ∩ Addresses not null : Wallet is owned + // by one of the accounts + if (intersection(owners, addresses).length > 0) { + accounts[wallet.address] = wallet; + } else { + contacts[wallet.address] = wallet; + } + }); + + const data = { + accountsInfo, + accounts, contacts, contracts + }; + + dispatch(_personalAccountsInfo(data)); + dispatch(attachWallets(wallets)); + dispatch(fetchBalances()); + }); }; } diff --git a/js/src/redux/providers/personalReducer.js b/js/src/redux/providers/personalReducer.js index 19ee421f0..daadd54b3 100644 --- a/js/src/redux/providers/personalReducer.js +++ b/js/src/redux/providers/personalReducer.js @@ -25,14 +25,13 @@ const initialState = { hasContacts: false, contracts: {}, hasContracts: false, - wallet: {}, - hasWallets: false, visibleAccounts: [] }; export default handleActions({ personalAccountsInfo (state, action) { - const { accountsInfo, accounts, contacts, contracts, wallets } = action; + const accountsInfo = action.accountsInfo || state.accountsInfo; + const { accounts, contacts, contracts } = action; return Object.assign({}, state, { accountsInfo, @@ -41,9 +40,7 @@ export default handleActions({ contacts, hasContacts: Object.keys(contacts).length !== 0, contracts, - hasContracts: Object.keys(contracts).length !== 0, - wallets, - hasWallets: Object.keys(wallets).length !== 0 + hasContracts: Object.keys(contracts).length !== 0 }); }, diff --git a/js/src/redux/providers/signerReducer.js b/js/src/redux/providers/signerReducer.js index c6d55f140..835fe8a70 100644 --- a/js/src/redux/providers/signerReducer.js +++ b/js/src/redux/providers/signerReducer.js @@ -90,7 +90,7 @@ export default handleActions({ signerSuccessRejectRequest (state, action) { const { id } = action.payload; const rejected = Object.assign( - state.pending.find(p => p.id === id), + state.pending.find(p => p.id === id) || { id }, { status: 'rejected' } ); return { diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index d7b360b14..ef4c09224 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -22,13 +22,11 @@ export default class Status { this._api = api; this._store = store; - this._pingable = false; this._apiStatus = {}; this._status = {}; this._longStatus = {}; this._minerSettings = {}; - this._pollPingTimeoutId = null; this._longStatusTimeoutId = null; this._timestamp = Date.now(); @@ -36,7 +34,6 @@ export default class Status { start () { this._subscribeBlockNumber(); - this._pollPing(); this._pollStatus(); this._pollLongStatus(); this._pollLogs(); @@ -65,50 +62,6 @@ export default class Status { }); } - /** - * Pinging should be smart. It should only - * be used when the UI is connecting or the - * Node is deconnected. - * - * @see src/views/Connection/connection.js - */ - _shouldPing = () => { - const { isConnected } = this._apiStatus; - return !isConnected; - } - - _stopPollPing = () => { - if (!this._pollPingTimeoutId) { - return; - } - - clearTimeout(this._pollPingTimeoutId); - this._pollPingTimeoutId = null; - } - - _pollPing = () => { - // Already pinging, don't try again - if (this._pollPingTimeoutId) { - return; - } - - const dispatch = (pingable, timeout = 1000) => { - if (pingable !== this._pingable) { - this._pingable = pingable; - this._store.dispatch(statusCollection({ isPingable: pingable })); - } - - this._pollPingTimeoutId = setTimeout(() => { - this._stopPollPing(); - this._pollPing(); - }, timeout); - }; - - fetch('/', { method: 'HEAD' }) - .then((response) => dispatch(!!response.ok)) - .catch(() => dispatch(false)); - } - _pollTraceMode = () => { return this._api.trace.block() .then(blockTraces => { @@ -137,7 +90,6 @@ export default class Status { if (gotConnected) { this._pollLongStatus(); - this._store.dispatch(statusCollection({ isPingable: true })); } if (!isEqual(apiStatus, this._apiStatus)) { @@ -145,13 +97,6 @@ export default class Status { this._apiStatus = apiStatus; } - // Ping if necessary, otherwise stop pinging - if (this._shouldPing()) { - this._pollPing(); - } else { - this._stopPollPing(); - } - if (!isConnected) { return nextTimeout(250); } diff --git a/js/src/redux/providers/statusReducer.js b/js/src/redux/providers/statusReducer.js index 413ff3319..17186b012 100644 --- a/js/src/redux/providers/statusReducer.js +++ b/js/src/redux/providers/statusReducer.js @@ -43,7 +43,6 @@ const initialState = { syncing: true, isConnected: false, isConnecting: false, - isPingable: false, isTest: undefined, refreshStatus: false, traceMode: undefined diff --git a/js/src/reset.css b/js/src/reset.css index b1ee22d03..c9e94e627 100644 --- a/js/src/reset.css +++ b/js/src/reset.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/routes.js b/js/src/routes.js index 82296852b..6aa1304c5 100644 --- a/js/src/routes.js +++ b/js/src/routes.js @@ -16,9 +16,10 @@ import { Accounts, Account, Addresses, Address, Application, - Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, + Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, - SettingsViews, Signer, Status + SettingsViews, Signer, Status, + Wallet, Web, WriteContract } from '~/views'; function handleDeprecatedRoute (nextState, replace) { @@ -111,6 +112,7 @@ const routes = [ { path: 'apps', component: Dapps }, { path: 'app/:id', component: Dapp }, + { path: 'web', component: Web }, { path: 'signer', component: Signer } ] } diff --git a/js/src/secureApi.js b/js/src/secureApi.js index d91a974d7..445151c69 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -14,35 +14,45 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { uniq } from 'lodash'; + import Api from './api'; const sysuiToken = window.localStorage.getItem('sysuiToken'); export default class SecureApi extends Api { constructor (url, nextToken) { - super(new Api.Transport.Ws(url, sysuiToken)); + super(new Api.Transport.Ws(url, sysuiToken, false)); + this._url = url; this._isConnecting = true; - this._connectState = sysuiToken === 'initial' ? 1 : 0; this._needsToken = false; + this._dappsPort = 8080; this._dappsInterface = null; this._signerPort = 8180; - this._followConnectionTimeoutId = null; // Try tokens from localstorage, then from hash - this._tokensToTry = [ sysuiToken, nextToken ].filter((t) => t && t.length); + this._tokens = uniq([sysuiToken, nextToken, 'initial']) + .filter((token) => token) + .map((token) => ({ value: token, tried: false })); - this._followConnection(); + this._tryNextToken(); } - setToken = () => { + saveToken = () => { window.localStorage.setItem('sysuiToken', this._transport.token); - // DEBUG: console.log('SecureApi:setToken', this._transport.token); + // DEBUG: console.log('SecureApi:saveToken', this._transport.token); } + /** + * Returns a Promise that gets resolved with + * a boolean: `true` if the node is up, `false` + * otherwise + */ _checkNodeUp () { - return fetch('/', { method: 'HEAD' }) + const url = this._url.replace(/wss?/, 'http'); + return fetch(url, { method: 'HEAD' }) .then( (r) => r.status === 200, () => false @@ -50,92 +60,70 @@ export default class SecureApi extends Api { .catch(() => false); } - _followConnection = () => { - const nextTick = () => { - if (this._followConnectionTimeoutId) { - clearTimeout(this._followConnectionTimeoutId); - } + _setManual () { + this._needsToken = true; + this._isConnecting = false; + } - this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250); - }; + _tryNextToken () { + const nextTokenIndex = this._tokens.findIndex((t) => !t.tried); - const setManual = () => { - this._connectState = 100; - this._needsToken = true; - this._isConnecting = false; - }; - - const lastError = this._transport.lastError; - const isConnected = this._transport.isConnected; - - switch (this._connectState) { - // token = - case 0: - if (isConnected) { - return this.connectSuccess(); - } else if (lastError) { - return this - ._checkNodeUp() - .then((isNodeUp) => { - const { timestamp } = lastError; - - if ((Date.now() - timestamp) > 250) { - return nextTick(); - } - - const nextToken = this._tokensToTry[0] || 'initial'; - const nextState = nextToken !== 'initial' ? 0 : 1; - - // If previous token was wrong (error while node up), delete it - if (isNodeUp) { - this._tokensToTry = this._tokensToTry.slice(1); - } - - if (nextToken !== this._transport.token) { - this.updateToken(nextToken, nextState); - } - - return nextTick(); - }); - } - break; - - // token = 'initial' - case 1: - if (isConnected) { - this.signer - .generateAuthorizationToken() - .then((token) => { - this.updateToken(token, 2); - }) - .catch((error) => { - console.error('SecureApi:generateAuthorizationToken', error); - setManual(); - }); - return; - } else if (lastError) { - return setManual(); - } - break; - - // token = - case 2: - if (isConnected) { - return this.connectSuccess(); - } else if (lastError) { - return setManual(); - } - break; + if (nextTokenIndex < 0) { + return this._setManual(); } - nextTick(); + const nextToken = this._tokens[nextTokenIndex]; + nextToken.tried = true; + + this.updateToken(nextToken.value); + } + + _followConnection = () => { + const token = this.transport.token; + + return this + .transport + .connect() + .then(() => { + if (token === 'initial') { + return this.signer + .generateAuthorizationToken() + .then((token) => { + return this.updateToken(token); + }) + .catch((e) => console.error(e)); + } + + this.connectSuccess(); + return true; + }) + .catch((e) => { + this + ._checkNodeUp() + .then((isNodeUp) => { + // Try again in a few... + if (!isNodeUp) { + this._isConnecting = false; + const timeout = this.transport.retryTimeout; + + window.setTimeout(() => { + this._followConnection(); + }, timeout); + + return; + } + + this._tryNextToken(); + return false; + }); + }); } connectSuccess () { this._isConnecting = false; this._needsToken = false; - this.setToken(); + this.saveToken(); Promise .all([ @@ -152,10 +140,9 @@ export default class SecureApi extends Api { // DEBUG: console.log('SecureApi:connectSuccess', this._transport.token); } - updateToken (token, connectState = 0) { - this._connectState = connectState; - this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, '')); - this._followConnection(); + updateToken (token) { + this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, ''), false); + return this._followConnection(); // DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState); } diff --git a/js/src/serviceWorker.js b/js/src/serviceWorker.js index c558a57cf..136e6a6b7 100644 --- a/js/src/serviceWorker.js +++ b/js/src/serviceWorker.js @@ -140,5 +140,5 @@ function getCompiler (build) { }); } - return self.solc[longVersion]; + return Promise.resolve(self.solc[longVersion]); } diff --git a/js/src/ui/AccountCard/accountCard.css b/js/src/ui/AccountCard/accountCard.css new file mode 100644 index 000000000..5820ddf2f --- /dev/null +++ b/js/src/ui/AccountCard/accountCard.css @@ -0,0 +1,110 @@ +/* Copyright 2015, 2016 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 . +*/ + +.account { + padding: 1em; + margin: 0.5em 0; + + display: flex; + flex-direction: row; + align-items: center; + + background-color: rgba(0, 0, 0, 0.8); + + transition: transform ease-out 0.1s; + transform: scale(1); + + &.copied { + animation-duration: 0.25s; + animation-name: copied; + } + + &:focus { + transform: scale(0.99); + background-color: rgba(0, 0, 0, 0.6); + } + + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + + &:hover { + cursor: pointer; + background-color: rgba(0, 0, 0, 0.4); + } +} + +.description { + font-size: 0.75em; + color: rgba(255, 255, 255, 0.5); +} + +.accountInfo { + flex: 1; + + display: flex; + flex-direction: column; + justify-content: center; + min-width: 0; + + > * { + padding: 0.25em 0; + } + + .addressContainer { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.9em; + + .address { + &:hover { + cursor: text; + } + } + } + + .accountName { + font-weight: 700 !important; + } + +} + +.balance { + .tag { + margin-left: 0.5em; + font-size: 0.85em; + } +} + +@keyframes copied { + from { + transform: scale(0.99); + } + + 50% { + transform: scale(0.97); + } + + to { + transform: scale(0.99); + } +} diff --git a/js/src/ui/AccountCard/accountCard.js b/js/src/ui/AccountCard/accountCard.js new file mode 100644 index 000000000..518dfaaa6 --- /dev/null +++ b/js/src/ui/AccountCard/accountCard.js @@ -0,0 +1,203 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import ReactDOM from 'react-dom'; +import keycode from 'keycode'; + +import IdentityIcon from '~/ui/IdentityIcon'; +import Tags from '~/ui/Tags'; + +import { fromWei } from '~/api/util/wei'; + +import styles from './accountCard.css'; + +export default class AccountCard extends Component { + + static propTypes = { + account: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + + balance: PropTypes.object + }; + + state = { + copied: false + }; + + render () { + const { account } = this.props; + const { copied } = this.state; + + const { address, name, description, meta = {} } = account; + + const displayName = (name && name.toUpperCase()) || address; + const { tags = [] } = meta; + + const classes = [ styles.account ]; + + if (copied) { + classes.push(styles.copied); + } + + return ( +
+ +
+
+ { displayName } +
+ + { this.renderTags(tags, address) } + { this.renderDescription(description) } + { this.renderAddress(displayName, address) } + { this.renderBalance(address) } +
+
+ ); + } + + renderDescription (description) { + if (!description) { + return null; + } + + return ( +
+ { description } +
+ ); + } + + renderAddress (name, address) { + if (name === address) { + return null; + } + + return ( +
+ + { address } + +
+ ); + } + + renderTags (tags = [], address) { + if (tags.length === 0) { + return null; + } + + return ( + + ); + } + + renderBalance (address) { + const { balance = {} } = this.props; + + if (!balance.tokens) { + return null; + } + + const ethToken = balance.tokens + .find((tok) => tok.token && (tok.token.tag || '').toLowerCase() === 'eth'); + + if (!ethToken) { + return null; + } + + const value = fromWei(ethToken.value).toFormat(3); + + return ( +
+ { value } + ETH +
+ ); + } + + handleKeyDown = (event) => { + const codeName = keycode(event); + + if (event.ctrlKey) { + // Copy the selected address if nothing selected and there is + // a focused item + const isSelection = !window.getSelection || window.getSelection().type === 'Range'; + + if (codeName === 'c' && !isSelection) { + const element = ReactDOM.findDOMNode(this.refs.address); + + // Copy the address from the right element + // @see https://developers.google.com/web/updates/2015/04/cut-and-copy-commands + try { + const range = document.createRange(); + range.selectNode(element); + window.getSelection().addRange(range); + document.execCommand('copy'); + + try { + window.getSelection().removeRange(range); + } catch (e) { + window.getSelection().removeAllRanges(); + } + + this.setState({ copied: true }, () => { + window.setTimeout(() => { + this.setState({ copied: false }); + }, 250); + }); + } catch (e) { + console.warn('could not copy'); + } + } + + return event; + } + } + + onClick = () => { + const { account, onClick } = this.props; + onClick(account.address); + } + + onFocus = () => { + const { account, onFocus } = this.props; + onFocus(account.index); + } + + preventEvent = (e) => { + e.preventDefault(); + e.stopPropagation(); + } + + setTagRef = (tagRef) => { + this.tagRefs.push(tagRef); + } +} diff --git a/js/src/ui/AccountCard/index.js b/js/src/ui/AccountCard/index.js new file mode 100644 index 000000000..4adfc46d2 --- /dev/null +++ b/js/src/ui/AccountCard/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './accountCard'; diff --git a/js/src/ui/Actionbar/Import/import.css b/js/src/ui/Actionbar/Import/import.css index 90686ee52..5efa7d868 100644 --- a/js/src/ui/Actionbar/Import/import.css +++ b/js/src/ui/Actionbar/Import/import.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Actionbar/Search/search.css b/js/src/ui/Actionbar/Search/search.css index 956a9e2e3..c36c89a29 100644 --- a/js/src/ui/Actionbar/Search/search.css +++ b/js/src/ui/Actionbar/Search/search.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Actionbar/Sort/sort.css b/js/src/ui/Actionbar/Sort/sort.css index ff592ca2f..adc38ee71 100644 --- a/js/src/ui/Actionbar/Sort/sort.css +++ b/js/src/ui/Actionbar/Sort/sort.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Actionbar/actionbar.css b/js/src/ui/Actionbar/actionbar.css index 73c5687f4..aacc3fc39 100644 --- a/js/src/ui/Actionbar/actionbar.css +++ b/js/src/ui/Actionbar/actionbar.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Badge/badge.css b/js/src/ui/Badge/badge.css index 3653bdae8..4a5eb2c13 100644 --- a/js/src/ui/Badge/badge.css +++ b/js/src/ui/Badge/badge.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index f36133fe6..a27904832 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/BlockStatus/blockStatus.css b/js/src/ui/BlockStatus/blockStatus.css index ed86646f0..342354b38 100644 --- a/js/src/ui/BlockStatus/blockStatus.css +++ b/js/src/ui/BlockStatus/blockStatus.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/BlockStatus/blockStatus.js b/js/src/ui/BlockStatus/blockStatus.js index df6f44a8c..f50c7a685 100644 --- a/js/src/ui/BlockStatus/blockStatus.js +++ b/js/src/ui/BlockStatus/blockStatus.js @@ -62,9 +62,17 @@ class BlockStatus extends Component { ); } + let syncStatus = null; + + if (syncing && syncing.currentBlock && syncing.highestBlock) { + syncStatus = ( + { syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing + ); + } + return (
- { syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing + { syncStatus } { warpStatus }
); diff --git a/js/src/ui/Certifications/certifications.css b/js/src/ui/Certifications/certifications.css index bff45ce5f..6ce6ac2ae 100644 --- a/js/src/ui/Certifications/certifications.css +++ b/js/src/ui/Certifications/certifications.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/ConfirmDialog/confirmDialog.css b/js/src/ui/ConfirmDialog/confirmDialog.css index 6bb860bd3..50c44c554 100644 --- a/js/src/ui/ConfirmDialog/confirmDialog.css +++ b/js/src/ui/ConfirmDialog/confirmDialog.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Container/Title/title.css b/js/src/ui/Container/Title/title.css index e277b7b82..341c25a7f 100644 --- a/js/src/ui/Container/Title/title.css +++ b/js/src/ui/Container/Title/title.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index d5c2ef336..b8e050b38 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/CopyToClipboard/copyToClipboard.css b/js/src/ui/CopyToClipboard/copyToClipboard.css index 6cc8d8030..8fd1c7b39 100644 --- a/js/src/ui/CopyToClipboard/copyToClipboard.css +++ b/js/src/ui/CopyToClipboard/copyToClipboard.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Errors/errors.css b/js/src/ui/Errors/errors.css index 216993e4f..46813160e 100644 --- a/js/src/ui/Errors/errors.css +++ b/js/src/ui/Errors/errors.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/AddressSelect/addressSelect.css b/js/src/ui/Form/AddressSelect/addressSelect.css index 01bc8901d..25fb67f1c 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.css +++ b/js/src/ui/Form/AddressSelect/addressSelect.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -14,53 +14,110 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ -.account { - padding: 0.25em 0; - display: flex; - align-items: center; -} -.name { - height: 32px; - line-height: 32px; - display: inline-block; - vertical-align: top; - text-transform: uppercase; - padding: 0 0 0 1em; -} +.input { + box-sizing: border-box; + appearance: textfield; + width: 100%; + padding: 0; + border: none; + background: transparent; -.balance { - color: #aaa; - padding-left: 1em; -} + transition-property: font-size, padding; + transition-duration: 0.5s; + transition-timing-function: cubic-bezier(0.7,0,0.3,1); -.image { - display: inline-block; - height: 32px; - width: 32px; - margin: 0; - z-index: 10; -} + color: white; + font-family: inherit; + font-size: 2em; -.icon { - position: absolute; - left: 0; - top: 35px; + &:focus { + outline: none; + } - &.noLabel { - top: 11px; + &::placeholder { + color: #a2a2a2; } } -.paddedInput input { - padding-left: 46px !important; -} - -.container { +.inputAddress { position: relative; + + &:hover, *:hover { + cursor: text !important; + } } -.menuItem { - min-height: 0 !important; - line-height: inherit !important; +.main { + position: relative; + left: 0; + + &:focus { + outline: none; + } +} + +.label { + margin: 1rem 2.5rem 0.25em; + color: rgba(255, 255, 255, 0.498039); +} + +.underline { + position: relative; + margin: 0 9rem 0 2.5rem; +} + +.empty { + font-size: 1.5em; +} + +.inputContainer { + display: flex; + flex-direction: column; + flex: 1; + + .input { + font-size: 1.5em; + padding: 0 9rem 0.5em 2.5rem; + display: block; + + padding-right: 6rem; + } +} + +.categories { + flex: 1; + + display: flex; + flex-direction: row; + justify-content: flex-start; + + margin: 2rem 2rem 0; + + > * { + flex: 1; + } +} + +.category { + display: flex; + flex-direction: column; + margin: 0 0.5em; + max-width: 35em; + + .title { + text-transform: uppercase; + font-size: 1.5em; + font-color: white; + } + + .cards { + flex: 1; + overflow: auto; + + display: flex; + flex-direction: column; + + margin: 1em 0; + } } diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index c443ecaa2..31e4d2207 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -15,262 +15,567 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import { MenuItem } from 'material-ui'; -import { isEqual, pick } from 'lodash'; +import ReactDOM from 'react-dom'; +import { connect } from 'react-redux'; +import keycode, { codes } from 'keycode'; +import { FormattedMessage } from 'react-intl'; +import { observer } from 'mobx-react'; -import AutoComplete from '../AutoComplete'; -import IdentityIcon from '../../IdentityIcon'; -import IdentityName from '../../IdentityName'; +import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline'; -import { fromWei } from '~/api/util/wei'; +import AccountCard from '~/ui/AccountCard'; +import InputAddress from '~/ui/Form/InputAddress'; +import Portal from '~/ui/Portal'; +import { nodeOrStringProptype } from '~/util/proptypes'; +import { validateAddress } from '~/util/validation'; +import AddressSelectStore from './addressSelectStore'; import styles from './addressSelect.css'; -export default class AddressSelect extends Component { +const BOTTOM_BORDER_STYLE = { borderBottom: 'solid 3px' }; + +// Current Form ID +let currentId = 1; + +@observer +class AddressSelect extends Component { static contextTypes = { - api: PropTypes.object.isRequired - } + intl: React.PropTypes.object.isRequired, + api: PropTypes.object.isRequired, + muiTheme: PropTypes.object.isRequired + }; static propTypes = { + // Required props onChange: PropTypes.func.isRequired, + // Redux props + accountsInfo: PropTypes.object, accounts: PropTypes.object, - allowInput: PropTypes.bool, balances: PropTypes.object, contacts: PropTypes.object, contracts: PropTypes.object, - disabled: PropTypes.bool, - error: PropTypes.string, - hint: PropTypes.string, - label: PropTypes.string, tokens: PropTypes.object, - value: PropTypes.string, - wallets: PropTypes.object - } + + // Optional props + allowInput: PropTypes.bool, + disabled: PropTypes.bool, + error: nodeOrStringProptype(), + hint: nodeOrStringProptype(), + label: nodeOrStringProptype(), + value: nodeOrStringProptype() + }; + + static defaultProps = { + value: '' + }; + + store = new AddressSelectStore(this.context.api); state = { - autocompleteEntries: [], - entries: {}, - addresses: [], - value: '' - } - - // Cache autocomplete items - items = {} - - entriesFromProps (props = this.props) { - const { accounts = {}, contacts = {}, contracts = {}, wallets = {} } = props; - - const autocompleteEntries = [].concat( - Object.values(wallets), - 'divider', - Object.values(accounts), - 'divider', - Object.values(contacts), - 'divider', - Object.values(contracts) - ); - - const entries = { - ...wallets, - ...accounts, - ...contacts, - ...contracts - }; - - return { autocompleteEntries, entries }; - } - - shouldComponentUpdate (nextProps, nextState) { - const keys = [ 'error', 'value' ]; - - const prevValues = pick(this.props, keys); - const nextValues = pick(nextProps, keys); - - return !isEqual(prevValues, nextValues); - } + expanded: false, + focused: false, + focusedCat: null, + focusedItem: null, + inputFocused: false, + inputValue: '' + }; componentWillMount () { - const { value } = this.props; - const { entries, autocompleteEntries } = this.entriesFromProps(); - const addresses = Object.keys(entries).sort(); - - this.setState({ autocompleteEntries, entries, addresses, value }); + this.setValues(); } - componentWillReceiveProps (newProps) { - if (newProps.value !== this.props.value) { - this.setState({ value: newProps.value }); + componentWillReceiveProps (nextProps) { + if (this.store.values && this.store.values.length > 0) { + return; } + + this.setValues(nextProps); + } + + setValues (props = this.props) { + this.store.setValues(props); } render () { - const { allowInput, disabled, error, hint, label } = this.props; - const { autocompleteEntries, value } = this.state; + const input = this.renderInput(); + const content = this.renderContent(); - const searchText = this.getSearchText(); - const icon = this.renderIdentityIcon(value); + const classes = [ styles.main ]; return ( -
- - { icon } -
- ); - } - - renderIdentityIcon (inputValue) { - const { error, value, label } = this.props; - - if (error || !inputValue || value.length !== 42) { - return null; - } - - const classes = [ styles.icon ]; - - if (!label) { - classes.push(styles.noLabel); - } - - return ( - + onBlur={ this.handleMainBlur } + onClick={ this.handleFocus } + onFocus={ this.handleMainFocus } + onKeyDown={ this.handleInputAddresKeydown } + ref='inputAddress' + tabIndex={ 0 } + > + { input } + { content } + ); } - renderItem = (entry) => { - const { address, name } = entry; + renderInput () { + const { focused } = this.state; + const { accountsInfo, disabled, error, hint, label, value } = this.props; - const _balance = this.getBalance(address); - const balance = _balance ? _balance.toNumber() : _balance; + const input = ( + + ); - if (!this.items[address] || this.items[address].balance !== balance) { - this.items[address] = { - text: name && name.toUpperCase() || address, - value: this.renderMenuItem(address), - address, balance - }; + if (disabled) { + return input; } - return this.items[address]; + return ( +
+ { input } +
+ ); } - getBalance (address) { - const { balances = {} } = this.props; + renderContent () { + const { muiTheme } = this.context; + const { hint, disabled, label } = this.props; + const { expanded, inputFocused } = this.state; + + if (disabled) { + return null; + } + + const id = `addressSelect_${++currentId}`; + const ilHint = typeof hint === 'string' || !(hint && hint.props) + ? (hint || '') + : this.context.intl.formatMessage( + hint.props, + hint.props.values || {} + ); + + return ( + + + + +
+ +
+ + { this.renderCurrentInput() } + { this.renderRegistryValues() } + { this.renderAccounts() } +
+ ); + } + + renderCurrentInput () { + const { inputValue } = this.state; + + if (!this.props.allowInput || !inputValue) { + return null; + } + + const { address, addressError } = validateAddress(inputValue); + + if (addressError) { + return null; + } + + return ( +
+ { this.renderAccountCard({ address }) } +
+ ); + } + + renderRegistryValues () { + const { registryValues } = this.store; + + if (registryValues.length === 0) { + return null; + } + + const accounts = registryValues + .map((registryValue, index) => { + const account = { ...registryValue, index: `${registryValue.address}_${index}` }; + return this.renderAccountCard(account); + }); + + return ( +
+ { accounts } +
+ ); + } + + renderAccounts () { + const { values } = this.store; + + if (values.length === 0) { + return ( +
+
+ +
+
+ ); + } + + const categories = values.map((category, index) => { + return this.renderCategory(category, index); + }); + + return ( +
+ { categories } +
+ ); + } + + renderCategory (category, index) { + const { label, key, values = [] } = category; + let content; + + if (values.length === 0) { + content = ( +

+ +

+ ); + } else { + const cards = values + .map((account) => this.renderAccountCard(account)); + + content = ( +
+
{ cards }
+
+ ); + } + + return ( +
+
{ label }
+ { content } +
+ ); + } + + renderAccountCard (_account) { + const { balances, accountsInfo } = this.props; + const { address, index = null } = _account; + const balance = balances[address]; - - if (!balance) { - return null; - } - - const ethToken = balance.tokens.find((tok) => tok.token && tok.token.tag && tok.token.tag.toLowerCase() === 'eth'); - - if (!ethToken) { - return null; - } - - return ethToken.value; - } - - renderBalance (address) { - const balance = this.getBalance(address); - const value = fromWei(balance); + const account = { + ...accountsInfo[address], + ..._account + }; return ( -
- { value.toFormat(3) } { 'ETH' } -
+ ); } - renderMenuItem (address) { - const balance = this.props.balances - ? this.renderBalance(address) - : null; - - const item = ( -
- - - { balance } -
- ); - - return ( - - { item } - - ); + setInputRef = (refId) => { + this.inputRef = refId; } - getSearchText () { - const entry = this.getEntry(); - - return entry && entry.name - ? entry.name.toUpperCase() - : this.state.value; - } - - getEntry () { - const { entries, value } = this.state; - return value ? entries[value] : null; - } - - handleFilter = (searchText, name, item) => { - const { address } = item; - const entry = this.state.entries[address]; - const lowCaseSearch = (searchText || '').toLowerCase(); - - return [entry.name, entry.address] - .some(text => text.toLowerCase().indexOf(lowCaseSearch) !== -1); - } - - onChange = (entry, empty) => { + validateCustomInput = () => { const { allowInput } = this.props; - const { value } = this.state; + const { inputValue } = this.store; + const { values } = this.store; - const address = entry && entry.address - ? entry.address - : ((empty && !allowInput) ? '' : value); + // If input is HEX and allowInput === true, send it + if (allowInput && inputValue && /^(0x)?([0-9a-f])+$/i.test(inputValue)) { + return this.handleClick(inputValue); + } - this.props.onChange(null, address); + // If only one value, select it + if (values.reduce((cur, cat) => cur + cat.values.length, 0) === 1) { + const value = values.find((cat) => cat.values.length > 0).values[0]; + return this.handleClick(value.address); + } } - onUpdateInput = (query, choices) => { - const { api } = this.context; + handleInputAddresKeydown = (event) => { + const code = keycode(event); - const address = query.trim(); + // Simulate click on input address if enter is pressed + if (code === 'enter') { + return this.handleDOMAction('inputAddress', 'click'); + } + } - if (!/^0x/.test(address) && api.util.isAddressValid(`0x${address}`)) { - const checksumed = api.util.toChecksumAddress(`0x${address}`); - return this.props.onChange(null, checksumed); + handleKeyDown = (event) => { + const codeName = keycode(event); + + if (event.ctrlKey) { + return event; + } + + switch (codeName) { + case 'enter': + const index = this.state.focusedItem; + if (!index) { + return this.validateCustomInput(); + } + + return this.handleDOMAction(`account_${index}`, 'click'); + + case 'left': + case 'right': + case 'up': + case 'down': + return this.handleNavigation(codeName, event); + + default: + const code = codes[codeName]; + + // @see https://github.com/timoxley/keycode/blob/master/index.js + // lower case chars + if (code >= (97 - 32) && code <= (122 - 32)) { + return this.handleDOMAction(this.inputRef, 'focus'); + } + + // numbers + if (code >= 48 && code <= 57) { + return this.handleDOMAction(this.inputRef, 'focus'); + } + + return event; + } + } + + handleDOMAction = (ref, method) => { + const refItem = typeof ref === 'string' ? this.refs[ref] : ref; + const element = ReactDOM.findDOMNode(refItem); + + if (!element || typeof element[method] !== 'function') { + console.warn('could not find', ref, 'or method', method); + return; + } + + return element[method](); + } + + focusItem = (index) => { + this.setState({ focusedItem: index }); + return this.handleDOMAction(`account_${index}`, 'focus'); + } + + handleNavigation = (direction, event) => { + const { focusedItem, focusedCat } = this.state; + const { values } = this.store; + + // Don't do anything if no values + if (values.reduce((cur, cat) => cur + cat.values.length, 0) === 0) { + return event; + } + + // Focus on the first element if none selected yet if going down + if (!focusedItem) { + if (direction !== 'down') { + return event; + } + + event.preventDefault(); + + const firstCat = values.findIndex((cat) => cat.values.length > 0); + const nextCat = focusedCat && values[focusedCat].values.length > 0 + ? focusedCat + : firstCat; + + const nextValues = values[nextCat]; + const nextFocus = nextValues ? nextValues.values[0] : null; + return this.focusItem(nextFocus && nextFocus.index || 1); + } + + event.preventDefault(); + + // Find the previous focused category + const prevCategoryIndex = values.findIndex((category) => { + return category.values.find((value) => value.index === focusedItem); + }); + const prevFocusIndex = values[prevCategoryIndex].values.findIndex((a) => a.index === focusedItem); + + let nextCategory = prevCategoryIndex; + let nextFocusIndex; + + // If down: increase index if possible + if (direction === 'down') { + const prevN = values[prevCategoryIndex].values.length; + nextFocusIndex = Math.min(prevFocusIndex + 1, prevN - 1); + } + + // If up: decrease index if possible + if (direction === 'up') { + // Focus on search if at the top + if (prevFocusIndex === 0) { + return this.handleDOMAction(this.inputRef, 'focus'); + } + + nextFocusIndex = prevFocusIndex - 1; + } + + // If right: next category + if (direction === 'right') { + const categoryShift = values + .slice(prevCategoryIndex + 1, values.length) + .findIndex((cat) => cat.values.length > 0) + 1; + + nextCategory = Math.min(prevCategoryIndex + categoryShift, values.length - 1); + } + + // If right: previous category + if (direction === 'left') { + const categoryShift = values + .slice(0, prevCategoryIndex) + .reverse() + .findIndex((cat) => cat.values.length > 0) + 1; + + nextCategory = Math.max(prevCategoryIndex - categoryShift, 0); + } + + // If left or right: try to keep the horizontal index + if (direction === 'left' || direction === 'right') { + this.setState({ focusedCat: nextCategory }); + nextFocusIndex = Math.min(prevFocusIndex, values[nextCategory].values.length - 1); + } + + const nextFocus = values[nextCategory].values[nextFocusIndex].index; + return this.focusItem(nextFocus); + } + + handleClick = (address) => { + // Don't do anything if it's only text-selection + if (window.getSelection && window.getSelection().type === 'Range') { + return; } this.props.onChange(null, address); + this.handleClose(); + } + + handleMainBlur = () => { + if (window.document.hasFocus() && !this.state.expanded) { + this.closing = false; + this.setState({ focused: false }); + } + } + + handleMainFocus = () => { + if (this.state.focused) { + return; + } + + this.setState({ focused: true }, () => { + if (this.closing) { + this.closing = false; + return; + } + + this.handleFocus(); + }); + } + + handleFocus = () => { + this.setState({ expanded: true, focusedItem: null, focusedCat: null }, () => { + window.setTimeout(() => { + this.handleDOMAction(this.inputRef, 'focus'); + }); + }); + } + + handleClose = () => { + this.closing = true; + + if (this.refs.inputAddress) { + this.handleDOMAction('inputAddress', 'focus'); + } + + this.setState({ expanded: false }); + } + + handleInputBlur = () => { + this.setState({ inputFocused: false }); + } + + handleInputFocus = () => { + this.setState({ focusedItem: null, inputFocused: true }); + } + + handleChange = (event = { target: {} }) => { + const { value = '' } = event.target; + + this.store.handleChange(value); + + this.setState({ + focusedItem: null, + inputValue: value + }); + } +} + +function mapStateToProps (state) { + const { accountsInfo } = state.personal; + const { balances } = state.balances; + + return { + accountsInfo, + balances }; } + +export default connect( + mapStateToProps +)(AddressSelect); diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js new file mode 100644 index 000000000..b6827b9cc --- /dev/null +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -0,0 +1,218 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React from 'react'; +import { observable, action } from 'mobx'; +import { flatMap } from 'lodash'; +import { FormattedMessage } from 'react-intl'; + +import Contracts from '~/contracts'; +import { sha3 } from '~/api/util/sha3'; + +export default class AddressSelectStore { + + @observable values = []; + @observable registryValues = []; + + initValues = []; + regLookups = []; + + constructor (api) { + this.api = api; + + const { registry } = Contracts.create(api); + + registry + .getContract('emailverification') + .then((emailVerification) => { + this.regLookups.push({ + lookup: (value) => { + return emailVerification + .instance + .reverse.call({}, [ sha3(value) ]); + }, + describe: (value) => ( + + ) + }); + }); + + registry + .getInstance() + .then((registryInstance) => { + this.regLookups.push({ + lookup: (value) => { + return registryInstance + .getAddress.call({}, [ sha3(value), 'A' ]); + }, + describe: (value) => ( + + ) + }); + }); + } + + @action setValues (props) { + const { accounts = {}, contracts = {}, contacts = {} } = props; + + const accountsN = Object.keys(accounts).length; + const contractsN = Object.keys(contracts).length; + const contactsN = Object.keys(contacts).length; + + if (accountsN + contractsN + contactsN === 0) { + return; + } + + this.initValues = [ + { + key: 'accounts', + label: ( + + ), + values: Object.values(accounts) + }, + { + key: 'contacts', + label: ( + + ), + values: Object.values(contacts) + }, + { + key: 'contracts', + label: ( + + ), + values: Object.values(contracts) + } + ].filter((cat) => cat.values.length > 0); + + this.handleChange(); + } + + @action handleChange = (value = '') => { + let index = 0; + + this.values = this.initValues + .map((category) => { + const filteredValues = this + .filterValues(category.values, value) + .map((value) => { + index++; + + return { + index: parseInt(index), + ...value + }; + }); + + return { + label: category.label, + values: filteredValues + }; + }); + + // Registries Lookup + this.registryValues = []; + + const lookups = this.regLookups.map((regLookup) => regLookup.lookup(value)); + + Promise + .all(lookups) + .then((results) => { + return results + .map((result, index) => { + if (/^(0x)?0*$/.test(result)) { + return; + } + + const lowercaseResult = result.toLowerCase(); + + const account = flatMap(this.initValues, (cat) => cat.values) + .find((account) => account.address.toLowerCase() === lowercaseResult); + + return { + description: this.regLookups[index].describe(value), + address: result, + name: account && account.name || value + }; + }) + .filter((data) => data); + }) + .then((registryValues) => { + this.registryValues = registryValues; + }); + } + + /** + * Filter the given values based on the given + * filter + */ + filterValues = (values = [], _filter = '') => { + const filter = _filter.toLowerCase(); + + return values + // Remove empty accounts + .filter((a) => a) + .filter((account) => { + const address = account.address.toLowerCase(); + const inAddress = address.includes(filter); + + if (!account.name || inAddress) { + return inAddress; + } + + const name = account.name.toLowerCase(); + const inName = name.includes(filter); + const { meta = {} } = account; + + if (!meta.tags || inName) { + return inName; + } + + const tags = (meta.tags || []).join(''); + return tags.includes(filter); + }) + .sort((accA, accB) => { + const nameA = accA.name || accA.address; + const nameB = accB.name || accB.address; + + return nameA.localeCompare(nameB); + }); + } + +} diff --git a/js/src/ui/Form/AutoComplete/autocomplete.css b/js/src/ui/Form/AutoComplete/autocomplete.css index 9fad53edb..e8bbabb30 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.css +++ b/js/src/ui/Form/AutoComplete/autocomplete.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index 3ebd59772..aaab0b55c 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -14,12 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; +import { isEqual } from 'lodash'; import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui'; import { PopoverAnimationVertical } from 'material-ui/Popover'; +import React, { Component, PropTypes } from 'react'; -import { isEqual } from 'lodash'; +import { nodeOrStringProptype } from '~/util/proptypes'; import styles from './autocomplete.css'; @@ -41,21 +42,21 @@ class Divider extends Component { export default class AutoComplete extends Component { static propTypes = { - onChange: PropTypes.func.isRequired, - onUpdateInput: PropTypes.func, - disabled: PropTypes.bool, - label: PropTypes.string, - hint: PropTypes.string, - error: PropTypes.string, - value: PropTypes.string, className: PropTypes.string, - filter: PropTypes.func, - renderItem: PropTypes.func, + disabled: PropTypes.bool, entry: PropTypes.object, entries: PropTypes.oneOfType([ PropTypes.array, PropTypes.object - ]) + ]), + error: nodeOrStringProptype(), + filter: PropTypes.func, + hint: nodeOrStringProptype(), + label: nodeOrStringProptype(), + onChange: PropTypes.func.isRequired, + onUpdateInput: PropTypes.func, + renderItem: PropTypes.func, + value: PropTypes.string }; state = { diff --git a/js/src/ui/Form/FormWrap/formWrap.css b/js/src/ui/Form/FormWrap/formWrap.css index 31a73e6ee..96c594971 100644 --- a/js/src/ui/Form/FormWrap/formWrap.css +++ b/js/src/ui/Form/FormWrap/formWrap.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/Input/input.css b/js/src/ui/Form/Input/input.css index b4d535eb9..8721047e5 100644 --- a/js/src/ui/Form/Input/input.css +++ b/js/src/ui/Form/Input/input.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js index 92d313b5a..d82ed8cf4 100644 --- a/js/src/ui/Form/Input/input.js +++ b/js/src/ui/Form/Input/input.js @@ -39,6 +39,10 @@ const UNDERLINE_NORMAL = { borderBottom: 'solid 2px' }; +const UNDERLINE_FOCUSED = { + transform: 'scaleX(1.0)' +}; + const NAME_ID = ' '; export default class Input extends Component { @@ -47,10 +51,12 @@ export default class Input extends Component { PropTypes.string, PropTypes.bool ]), + autoFocus: PropTypes.bool, children: PropTypes.node, className: PropTypes.string, disabled: PropTypes.bool, error: nodeOrStringProptype(), + focused: PropTypes.bool, readOnly: PropTypes.bool, floatCopy: PropTypes.bool, hint: nodeOrStringProptype(), @@ -61,9 +67,12 @@ export default class Input extends Component { multiLine: PropTypes.bool, onBlur: PropTypes.func, onChange: PropTypes.func, + onClick: PropTypes.func, + onFocus: PropTypes.func, onKeyDown: PropTypes.func, onSubmit: PropTypes.func, rows: PropTypes.number, + tabIndex: PropTypes.number, type: PropTypes.string, submitOnBlur: PropTypes.bool, style: PropTypes.object, @@ -92,11 +101,20 @@ export default class Input extends Component { if ((newProps.value !== this.props.value) && (newProps.value !== this.state.value)) { this.setValue(newProps.value); } + + if (newProps.focused && !this.props.focused) { + this.refs.input.setState({ isFocused: true }); + } + + if (!newProps.focused && this.props.focused) { + this.refs.input.setState({ isFocused: false }); + } } render () { const { value } = this.state; - const { children, className, disabled, error, hideUnderline, hint, label, max, min, multiLine, rows, style, type } = this.props; + const { autoFocus, children, className, hideUnderline, disabled, error, focused, label } = this.props; + const { hint, onClick, onFocus, multiLine, rows, type, min, max, style, tabIndex } = this.props; const readOnly = this.props.readOnly || disabled; @@ -111,11 +129,17 @@ export default class Input extends Component { textFieldStyle.height = 'initial'; } + const underlineStyle = readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL; + const underlineFocusStyle = focused + ? UNDERLINE_FOCUSED + : readOnly && typeof focused !== 'boolean' ? { display: 'none' } : null; + return (
{ this.renderCopyButton() } { children } diff --git a/js/src/ui/Form/InputAddress/inputAddress.css b/js/src/ui/Form/InputAddress/inputAddress.css index 37598a759..604725ebc 100644 --- a/js/src/ui/Form/InputAddress/inputAddress.css +++ b/js/src/ui/Form/InputAddress/inputAddress.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/InputAddress/inputAddress.js b/js/src/ui/Form/InputAddress/inputAddress.js index e2a0707d5..ec70502c0 100644 --- a/js/src/ui/Form/InputAddress/inputAddress.js +++ b/js/src/ui/Form/InputAddress/inputAddress.js @@ -20,6 +20,7 @@ import { bindActionCreators } from 'redux'; import util from '~/api/util'; import { nodeOrStringProptype } from '~/util/proptypes'; +import { isNullAddress } from '~/util/validation'; import IdentityIcon from '../../IdentityIcon'; import Input from '../Input'; @@ -33,12 +34,17 @@ class InputAddress extends Component { className: PropTypes.string, disabled: PropTypes.bool, error: PropTypes.string, + focused: PropTypes.bool, hideUnderline: PropTypes.bool, hint: nodeOrStringProptype(), label: nodeOrStringProptype(), onChange: PropTypes.func, + onClick: PropTypes.func, + onFocus: PropTypes.func, onSubmit: PropTypes.func, + readOnly: PropTypes.bool, small: PropTypes.bool, + tabIndex: PropTypes.number, text: PropTypes.bool, tokens: PropTypes.object, value: PropTypes.string @@ -51,10 +57,11 @@ class InputAddress extends Component { }; render () { - const { className, disabled, error, hint, label, text, value } = this.props; - const { accountsInfo, allowCopy, hideUnderline, onSubmit, small, tokens } = this.props; + const { accountsInfo, allowCopy, className, disabled, error, focused, hint } = this.props; + const { hideUnderline, label, onClick, onFocus, onSubmit, readOnly, small } = this.props; + const { tabIndex, text, tokens, value } = this.props; - const account = accountsInfo[value] || tokens[value]; + const account = value && (accountsInfo[value] || tokens[value]); const icon = this.renderIcon(); @@ -62,6 +69,7 @@ class InputAddress extends Component { classes.push(!icon ? styles.inputEmpty : styles.input); const containerClasses = [ styles.container ]; + const nullName = (disabled || readOnly) && isNullAddress(value) ? 'null' : null; if (small) { containerClasses.push(styles.small); @@ -74,15 +82,20 @@ class InputAddress extends Component { className={ classes.join(' ') } disabled={ disabled } error={ error } + focused={ focused } hideUnderline={ hideUnderline } hint={ hint } label={ label } onChange={ this.handleInputChange } + onClick={ onClick } + onFocus={ onFocus } onSubmit={ onSubmit } + readOnly={ readOnly } + tabIndex={ tabIndex } value={ text && account ? account.name - : value + : (nullName || value) } /> { icon }
diff --git a/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js b/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js index 59fb81458..5655550fd 100644 --- a/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js +++ b/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js @@ -25,7 +25,6 @@ class InputAddressSelect extends Component { accounts: PropTypes.object.isRequired, contacts: PropTypes.object.isRequired, contracts: PropTypes.object.isRequired, - wallets: PropTypes.object.isRequired, error: PropTypes.string, label: PropTypes.string, hint: PropTypes.string, @@ -34,7 +33,7 @@ class InputAddressSelect extends Component { }; render () { - const { accounts, contacts, contracts, wallets, label, hint, error, value, onChange } = this.props; + const { accounts, contacts, contracts, label, hint, error, value, onChange } = this.props; return ( . +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import React, { Component, PropTypes } from 'react'; -import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; - +import { arrayOrObjectProptype } from '~/util/proptypes'; import styles from './radioButtons.css'; export default class RadioButtons extends Component { static propTypes = { + name: PropTypes.string, onChange: PropTypes.func.isRequired, - values: PropTypes.array.isRequired, - value: PropTypes.any, - name: PropTypes.string + values: arrayOrObjectProptype().isRequired }; static defaultProps = { @@ -40,16 +39,16 @@ export default class RadioButtons extends Component { const index = Number.isNaN(parseInt(value)) ? values.findIndex((val) => val.key === value) : parseInt(value); - - const selectedValue = typeof value !== 'object' ? values[index] : value; + const selectedValue = typeof value !== 'object' + ? values[index] + : value; const key = this.getKey(selectedValue, index); return ( + valueSelected={ key } > { this.renderContent() } ); @@ -59,7 +58,9 @@ export default class RadioButtons extends Component { const { values } = this.props; return values.map((value, index) => { - const label = typeof value === 'string' ? value : value.label || ''; + const label = typeof value === 'string' + ? value + : value.label || ''; const description = (typeof value !== 'string' && value.description) || null; const key = this.getKey(value, index); @@ -67,28 +68,26 @@ export default class RadioButtons extends Component { { label } { description - ? ( - { description } - ) + ? { description } : null } - ) } - /> + } + value={ key } /> ); }); } getKey (value, index) { if (typeof value !== 'string') { - return typeof value.key === 'undefined' ? index : value.key; + return typeof value.key === 'undefined' + ? index + : value.key; } return index; @@ -96,8 +95,8 @@ export default class RadioButtons extends Component { onChange = (event, index) => { const { onChange, values } = this.props; - const value = values[index] || values.find((v) => v.key === index); + onChange(value, index); } } diff --git a/js/src/ui/Form/TypedInput/typedInput.css b/js/src/ui/Form/TypedInput/typedInput.css index ab911edac..6a4410f3d 100644 --- a/js/src/ui/Form/TypedInput/typedInput.css +++ b/js/src/ui/Form/TypedInput/typedInput.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js index 230b59c70..b0a8be98d 100644 --- a/js/src/ui/Form/TypedInput/typedInput.js +++ b/js/src/ui/Form/TypedInput/typedInput.js @@ -67,15 +67,7 @@ export default class TypedInput extends Component { } render () { - const { param } = this.props; - - if (typeof param === 'string') { - const parsedParam = parseAbiType(param); - - if (parsedParam) { - return this.renderParam(parsedParam); - } - } + const param = this.getParam(); if (param) { return this.renderParam(param); @@ -234,7 +226,8 @@ export default class TypedInput extends Component { } renderInteger (value = this.props.value, onChange = this.onChange) { - const { label, error, param, hint, min, max } = this.props; + const { label, error, hint, min, max } = this.props; + const param = this.getParam(); const realValue = value && typeof value.toNumber === 'function' ? value.toNumber() @@ -263,7 +256,8 @@ export default class TypedInput extends Component { * @see https://github.com/facebook/react/issues/1549 */ renderFloat (value = this.props.value, onChange = this.onChange) { - const { label, error, param, hint, min, max } = this.props; + const { label, error, hint, min, max } = this.props; + const param = this.getParam(); const realValue = value && typeof value.toNumber === 'function' ? value.toNumber() @@ -379,7 +373,9 @@ export default class TypedInput extends Component { } onAddField = () => { - const { value, onChange, param } = this.props; + const { value, onChange } = this.props; + const param = this.getParam(); + const newValues = [].concat(value, param.subtype.default); onChange(newValues); @@ -392,4 +388,14 @@ export default class TypedInput extends Component { onChange(newValues); } + getParam = () => { + const { param } = this.props; + + if (typeof param === 'string') { + return parseAbiType(param); + } + + return param; + } + } diff --git a/js/src/ui/Form/form.css b/js/src/ui/Form/form.css index 4a4be1002..f4a8b8f7c 100644 --- a/js/src/ui/Form/form.css +++ b/js/src/ui/Form/form.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.css b/js/src/ui/GasPriceEditor/gasPriceEditor.css index bcb99a63b..80b7dd313 100644 --- a/js/src/ui/GasPriceEditor/gasPriceEditor.css +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/GasPriceSelector/gasPriceSelector.css b/js/src/ui/GasPriceSelector/gasPriceSelector.css index ce5bd55e8..1917eeb28 100644 --- a/js/src/ui/GasPriceSelector/gasPriceSelector.css +++ b/js/src/ui/GasPriceSelector/gasPriceSelector.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index b9cf70ba0..8901d7dc7 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -16,16 +16,30 @@ import AddIcon from 'material-ui/svg-icons/content/add'; import CancelIcon from 'material-ui/svg-icons/content/clear'; +import CheckIcon from 'material-ui/svg-icons/navigation/check'; +import CloseIcon from 'material-ui/svg-icons/navigation/close'; +import ContractIcon from 'material-ui/svg-icons/action/code'; import DoneIcon from 'material-ui/svg-icons/action/done-all'; -import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; +import LockedIcon from 'material-ui/svg-icons/action/lock-outline'; import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; +import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; +import SaveIcon from 'material-ui/svg-icons/content/save'; +import SendIcon from 'material-ui/svg-icons/content/send'; import SnoozeIcon from 'material-ui/svg-icons/av/snooze'; +import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; export { AddIcon, CancelIcon, + CheckIcon, + CloseIcon, + ContractIcon, DoneIcon, - PrevIcon, + LockedIcon, NextIcon, - SnoozeIcon + PrevIcon, + SaveIcon, + SendIcon, + SnoozeIcon, + VisibleIcon }; diff --git a/js/src/ui/IdentityIcon/identityIcon.css b/js/src/ui/IdentityIcon/identityIcon.css index 34da4ebeb..042cbdde9 100644 --- a/js/src/ui/IdentityIcon/identityIcon.css +++ b/js/src/ui/IdentityIcon/identityIcon.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/IdentityIcon/identityIcon.js b/js/src/ui/IdentityIcon/identityIcon.js index 55fc34afa..5ab1651ec 100644 --- a/js/src/ui/IdentityIcon/identityIcon.js +++ b/js/src/ui/IdentityIcon/identityIcon.js @@ -17,9 +17,10 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import ContractIcon from 'material-ui/svg-icons/action/code'; import { createIdentityImg } from '~/api/util/identity'; +import { isNullAddress } from '~/util/validation'; +import { CancelIcon, ContractIcon } from '../Icons'; import styles from './identityIcon.css'; @@ -107,10 +108,22 @@ class IdentityIcon extends Component { return ( + ); + } else if (isNullAddress(address)) { + return ( + ); } @@ -118,6 +131,7 @@ class IdentityIcon extends Component { return ( diff --git a/js/src/ui/IdentityName/identityName.js b/js/src/ui/IdentityName/identityName.js index 0ad9075d6..45e864a75 100644 --- a/js/src/ui/IdentityName/identityName.js +++ b/js/src/ui/IdentityName/identityName.js @@ -18,41 +18,48 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { isNullAddress } from '~/util/validation'; import ShortenedHash from '../ShortenedHash'; const defaultName = 'UNNAMED'; class IdentityName extends Component { static propTypes = { - className: PropTypes.string, - address: PropTypes.string, accountsInfo: PropTypes.object, - tokens: PropTypes.object, + address: PropTypes.string, + className: PropTypes.string, empty: PropTypes.bool, + name: PropTypes.string, shorten: PropTypes.bool, - unknown: PropTypes.bool, - name: PropTypes.string + tokens: PropTypes.object, + unknown: PropTypes.bool } render () { - const { address, accountsInfo, tokens, empty, name, shorten, unknown, className } = this.props; + const { address, accountsInfo, className, empty, name, shorten, tokens, unknown } = this.props; const account = accountsInfo[address] || tokens[address]; if (!account && empty) { return null; } - const addressFallback = shorten ? () : address; + const nullName = isNullAddress(address) ? 'null' : null; + const addressFallback = nullName || (shorten ? () : address); const fallback = unknown ? defaultName : addressFallback; const isUuid = account && account.name === account.uuid; const displayName = (name && name.toUpperCase().trim()) || (account && !isUuid - ? account.name.toUpperCase().trim() - : fallback); + ? account.name.toUpperCase().trim() + : fallback + ); return ( - { displayName && displayName.length ? displayName : fallback } + { + displayName && displayName.length + ? displayName + : fallback + } ); } diff --git a/js/src/ui/IdentityName/identityName.spec.js b/js/src/ui/IdentityName/identityName.spec.js index 31bf3ee5f..bb0b55e46 100644 --- a/js/src/ui/IdentityName/identityName.spec.js +++ b/js/src/ui/IdentityName/identityName.spec.js @@ -23,6 +23,7 @@ import IdentityName from './identityName'; const ADDR_A = '0x123456789abcdef0123456789A'; const ADDR_B = '0x123456789abcdef0123456789B'; const ADDR_C = '0x123456789abcdef0123456789C'; +const ADDR_NULL = '0x0000000000000000000000000000000000000000'; const STORE = { dispatch: sinon.stub(), subscribe: sinon.stub(), @@ -52,7 +53,7 @@ function render (props) { describe('ui/IdentityName', () => { describe('rendering', () => { it('renders defaults', () => { - expect(render()).to.be.ok; + expect(render({ address: ADDR_A })).to.be.ok; }); describe('account not found', () => { @@ -71,6 +72,10 @@ describe('ui/IdentityName', () => { it('renders unknown with flag', () => { expect(render({ address: ADDR_C, unknown: true }).text()).to.equal('UNNAMED'); }); + + it('renders 0x000...000 as null', () => { + expect(render({ address: ADDR_NULL }).text()).to.equal('null'); + }); }); }); }); diff --git a/js/src/ui/Loading/loading.css b/js/src/ui/Loading/loading.css index dbdb98e42..97d16ad9b 100644 --- a/js/src/ui/Loading/loading.css +++ b/js/src/ui/Loading/loading.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/MethodDecoding/methodDecoding.css b/js/src/ui/MethodDecoding/methodDecoding.css index 9ef8a2779..adb899e1c 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.css +++ b/js/src/ui/MethodDecoding/methodDecoding.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -75,7 +75,7 @@ font-family: inherit !important; } -.inputs img { +.inputs [data-address-img] { position: absolute; top: -16px; } diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index fcf7f7513..ad676da0a 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -122,10 +122,24 @@ class MethodDecoding extends Component { for a total transaction value of { this.renderEtherValue(gasValue) } + { this.renderMinBlock() } ); } + renderMinBlock () { + const { historic, transaction } = this.props; + const { minBlock } = transaction; + + if (!minBlock || minBlock.eq(0)) { + return null; + } + + return ( + , { historic ? 'Submitted' : 'Submission' } at block #{ minBlock.toFormat(0) } + ); + } + renderAction () { const { token } = this.props; const { methodName, methodInputs, methodSignature, isDeploy, isReceived, isContract } = this.state; diff --git a/js/src/ui/MethodDecoding/methodDecodingStore.js b/js/src/ui/MethodDecoding/methodDecodingStore.js index 24433e541..5d518d3a9 100644 --- a/js/src/ui/MethodDecoding/methodDecodingStore.js +++ b/js/src/ui/MethodDecoding/methodDecodingStore.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify @@ -118,14 +118,14 @@ export default class MethodDecodingStore { return Promise.resolve(result); } - const { signature, paramdata } = this.api.util.decodeCallData(input); - result.signature = signature; - result.params = paramdata; + try { + const { signature } = this.api.util.decodeCallData(input); - // Contract deployment - if (!signature || signature === CONTRACT_CREATE || transaction.creates) { - return Promise.resolve({ ...result, deploy: true }); - } + if (signature === CONTRACT_CREATE || transaction.creates) { + result.contract = true; + return Promise.resolve({ ...result, deploy: true }); + } + } catch (e) {} return this .isContract(contractAddress || transaction.creates) @@ -136,6 +136,15 @@ export default class MethodDecodingStore { return result; } + const { signature, paramdata } = this.api.util.decodeCallData(input); + result.signature = signature; + result.params = paramdata; + + // Contract deployment + if (!signature) { + return Promise.resolve({ ...result, deploy: true }); + } + return this .fetchMethodAbi(signature) .then((abi) => { @@ -192,7 +201,7 @@ export default class MethodDecodingStore { */ isContract (contractAddress) { // If zero address, it isn't a contract - if (/^(0x)?0*$/.test(contractAddress)) { + if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) { return Promise.resolve(false); } diff --git a/js/src/ui/Modal/Busy/busy.css b/js/src/ui/Modal/Busy/busy.css index ea2cb9485..cc0465906 100644 --- a/js/src/ui/Modal/Busy/busy.css +++ b/js/src/ui/Modal/Busy/busy.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Modal/Completed/completed.css b/js/src/ui/Modal/Completed/completed.css index e843e7c66..b5cff6322 100644 --- a/js/src/ui/Modal/Completed/completed.css +++ b/js/src/ui/Modal/Completed/completed.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Modal/modal.css b/js/src/ui/Modal/modal.css index 723ddda69..1e79cb75b 100644 --- a/js/src/ui/Modal/modal.css +++ b/js/src/ui/Modal/modal.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Page/page.css b/js/src/ui/Page/page.css index 06d4f7274..185a335c0 100644 --- a/js/src/ui/Page/page.css +++ b/js/src/ui/Page/page.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/ParityBackground/parityBackground.js b/js/src/ui/ParityBackground/parityBackground.js index 4bbfd4772..6e554b846 100644 --- a/js/src/ui/ParityBackground/parityBackground.js +++ b/js/src/ui/ParityBackground/parityBackground.js @@ -18,48 +18,70 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; class ParityBackground extends Component { + static contextTypes = { + muiTheme: PropTypes.object.isRequired + }; + static propTypes = { - style: PropTypes.object.isRequired, + backgroundSeed: PropTypes.string, children: PropTypes.node, className: PropTypes.string, onClick: PropTypes.func }; + state = { + style: {} + }; + + _seed = null; + + componentWillMount () { + this.setStyle(); + } + + componentWillReceiveProps (nextProps) { + this.setStyle(nextProps); + } + + shouldComponentUpdate (_, nextState) { + return nextState.style !== this.state.style; + } + + setStyle (props = this.props) { + const { seed, gradient, backgroundSeed } = props; + + const _seed = seed || backgroundSeed; + + // Don't update if it's the same seed... + if (this._seed === _seed) { + return; + } + + const { muiTheme } = this.context; + + const style = muiTheme.parity.getBackgroundStyle(gradient, _seed); + this.setState({ style }); + } + render () { - const { children, className, style, onClick } = this.props; + const { children, className, onClick } = this.props; + const { style } = this.state; return (
+ onTouchTap={ onClick } + > { children }
); } } -function mapStateToProps (_, initProps) { - const { gradient, seed, muiTheme } = initProps; - - let _seed = seed; - let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) }; - - return (state, props) => { - const { backgroundSeed } = state.settings; - const { seed } = props; - - const newSeed = seed || backgroundSeed; - - if (newSeed === _seed) { - return _props; - } - - _seed = newSeed; - _props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) }; - - return _props; - }; +function mapStateToProps (state) { + const { backgroundSeed } = state.settings; + return { backgroundSeed }; } export default connect( diff --git a/js/src/ui/Portal/index.js b/js/src/ui/Portal/index.js new file mode 100644 index 000000000..36bca9caa --- /dev/null +++ b/js/src/ui/Portal/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './portal'; diff --git a/js/src/ui/Portal/portal.css b/js/src/ui/Portal/portal.css new file mode 100644 index 000000000..65c3b0103 --- /dev/null +++ b/js/src/ui/Portal/portal.css @@ -0,0 +1,100 @@ +/* Copyright 2015, 2016 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 . +*/ + +$left: 1.5em; +$right: $left; +$bottom: $left; +$top: 20vh; + +.backOverlay { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(255, 255, 255, 0.25); + z-index: -10; + opacity: 0; + + transform-origin: 100% 0; + transition-property: opacity, z-index; + transition-duration: 0.25s; + transition-timing-function: ease-out; + + &.expanded { + opacity: 1; + z-index: 2500; + } +} + +.parityBackground { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 0.25; + z-index: -1; +} + +.overlay { + display: flex; + position: fixed; + top: $top; + left: $left; + width: calc(100vw - $left - $right); + height: calc(100vh - $top - $bottom); + + transform-origin: 100% 0; + transition-property: opacity, z-index; + transition-duration: 0.25s; + transition-timing-function: ease-out; + + background-color: rgba(0, 0, 0, 1); + opacity: 0; + z-index: -10; + + * { + min-width: 0; + } + + &.expanded { + opacity: 1; + z-index: 3500; + } +} + +.closeIcon { + position: absolute; + top: 0.5rem; + right: 1rem; + font-size: 4em; + + transition-property: opacity; + transition-duration: 0.25s; + transition-timing-function: ease-out; + + &, * { + height: 48px !important; + width: 48px !important; + } + + &:hover { + cursor: pointer; + opacity: 0.5; + } +} diff --git a/js/src/ui/Portal/portal.js b/js/src/ui/Portal/portal.js new file mode 100644 index 000000000..91be44309 --- /dev/null +++ b/js/src/ui/Portal/portal.js @@ -0,0 +1,135 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import ReactDOM from 'react-dom'; +import ReactPortal from 'react-portal'; +import keycode from 'keycode'; + +import { CloseIcon } from '~/ui/Icons'; +import ParityBackground from '~/ui/ParityBackground'; + +import styles from './portal.css'; + +export default class Portal extends Component { + + static propTypes = { + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + + children: PropTypes.node, + className: PropTypes.string, + onKeyDown: PropTypes.func + }; + + state = { + expanded: false + } + + componentWillReceiveProps (nextProps) { + if (this.props.open !== nextProps.open) { + const opening = nextProps.open; + const closing = !opening; + + if (opening) { + return this.setState({ expanded: true }); + } + + if (closing) { + return this.setState({ expanded: false }); + } + } + } + + render () { + const { expanded } = this.state; + const { children, className } = this.props; + + const classes = [ styles.overlay, className ]; + const backClasses = [ styles.backOverlay ]; + + if (expanded) { + classes.push(styles.expanded); + backClasses.push(styles.expanded); + } + + return ( + +
+
+ + + { this.renderCloseIcon() } + { children } +
+
+
+ ); + } + + renderCloseIcon () { + const { expanded } = this.state; + + if (!expanded) { + return null; + } + + return ( +
+ +
+ ); + } + + stopEvent = (event) => { + event.preventDefault(); + event.stopPropagation(); + } + + handleClose = () => { + this.props.onClose(); + } + + handleKeyDown = (event) => { + const codeName = keycode(event); + + switch (codeName) { + case 'esc': + event.preventDefault(); + return this.handleClose(); + + default: + event.persist(); + return this.props.onKeyDown(event); + } + } + + handleDOMAction = (ref, method) => { + const refItem = typeof ref === 'string' ? this.refs[ref] : ref; + const element = ReactDOM.findDOMNode(refItem); + + if (!element || typeof element[method] !== 'function') { + console.warn('could not find', ref, 'or method', method); + return; + } + + return element[method](); + } +} diff --git a/js/src/ui/ShortenedHash/shortenedHash.css b/js/src/ui/ShortenedHash/shortenedHash.css index 7184a5fec..6743add0c 100644 --- a/js/src/ui/ShortenedHash/shortenedHash.css +++ b/js/src/ui/ShortenedHash/shortenedHash.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/Tags/tags.css b/js/src/ui/Tags/tags.css index f17fd1064..b5a9881a4 100644 --- a/js/src/ui/Tags/tags.css +++ b/js/src/ui/Tags/tags.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify @@ -29,6 +29,8 @@ border-radius: 16px; margin: 0.75em 0.5em 0 0; padding: 0.25em 1em; + opacity: 1; + transition: opacity 0.2s ease-out; } .tagClickable:hover { diff --git a/js/src/ui/Tags/tags.js b/js/src/ui/Tags/tags.js index a0db6fc6a..bc86e6f3b 100644 --- a/js/src/ui/Tags/tags.js +++ b/js/src/ui/Tags/tags.js @@ -16,12 +16,15 @@ import React, { Component, PropTypes } from 'react'; +import { arrayOrObjectProptype } from '~/util/proptypes'; + import styles from './tags.css'; export default class Tags extends Component { static propTypes = { - tags: PropTypes.array, - handleAddSearchToken: PropTypes.func + handleAddSearchToken: PropTypes.func, + setRefs: PropTypes.func, + tags: arrayOrObjectProptype() } render () { @@ -31,13 +34,17 @@ export default class Tags extends Component { } renderTags () { - const { handleAddSearchToken } = this.props; + const { handleAddSearchToken, setRefs } = this.props; const tags = this.props.tags || []; const tagClasses = handleAddSearchToken ? [ styles.tag, styles.tagClickable ] : [ styles.tag ]; + const setRef = setRefs + ? (ref) => { setRefs(ref); } + : () => {}; + return tags .sort() .map((tag, idx) => { @@ -49,7 +56,9 @@ export default class Tags extends Component {
+ onClick={ onClick } + ref={ setRef } + > { tag }
); diff --git a/js/src/ui/Tooltips/tooltips.css b/js/src/ui/Tooltips/tooltips.css index 9b63487e6..3e49b6d0a 100644 --- a/js/src/ui/Tooltips/tooltips.css +++ b/js/src/ui/Tooltips/tooltips.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/TxHash/txHash.css b/js/src/ui/TxHash/txHash.css index c5b54e351..8c73f01e7 100644 --- a/js/src/ui/TxHash/txHash.css +++ b/js/src/ui/TxHash/txHash.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js index fcbe374e1..09905d594 100644 --- a/js/src/ui/TxHash/txHash.js +++ b/js/src/ui/TxHash/txHash.js @@ -134,7 +134,7 @@ class TxHash extends Component { const { api } = this.context; const { hash } = this.props; - if (error) { + if (error || !hash || /^(0x)?0*$/.test(hash)) { return; } diff --git a/js/src/ui/TxList/txList.css b/js/src/ui/TxList/txList.css index ebe672bf8..eadf0e2ee 100644 --- a/js/src/ui/TxList/txList.css +++ b/js/src/ui/TxList/txList.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/util/constants.js b/js/src/util/constants.js index 4e2a073f1..109c060dd 100644 --- a/js/src/util/constants.js +++ b/js/src/util/constants.js @@ -19,8 +19,11 @@ const DEFAULT_GASPRICE = '20000000000'; const MAX_GAS_ESTIMATION = '50000000'; +const NULL_ADDRESS = '0000000000000000000000000000000000000000'; + export { DEFAULT_GAS, DEFAULT_GASPRICE, - MAX_GAS_ESTIMATION + MAX_GAS_ESTIMATION, + NULL_ADDRESS }; diff --git a/js/src/util/proptypes.js b/js/src/util/proptypes.js index 1f993768e..58b54f775 100644 --- a/js/src/util/proptypes.js +++ b/js/src/util/proptypes.js @@ -16,6 +16,13 @@ import { PropTypes } from 'react'; +export function arrayOrObjectProptype () { + return PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]); +} + export function nullableProptype (type) { return PropTypes.oneOfType([ PropTypes.oneOf([ null ]), diff --git a/js/src/util/tx.js b/js/src/util/tx.js index 591931ff3..90c78a42d 100644 --- a/js/src/util/tx.js +++ b/js/src/util/tx.js @@ -14,10 +14,93 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import WalletsUtils from '~/util/wallets'; + const isValidReceipt = (receipt) => { return receipt && receipt.blockNumber && receipt.blockNumber.gt(0); }; +function getTxArgs (func, options, values = []) { + const { contract } = func; + const { api } = contract; + const address = options.from; + + if (!address) { + return Promise.resolve({ func, options, values }); + } + + return WalletsUtils + .isWallet(api, address) + .then((isWallet) => { + if (!isWallet) { + return { func, options, values }; + } + + options.data = contract.getCallData(func, options, values); + options.to = options.to || contract.address; + + if (!options.to) { + return { func, options, values }; + } + + return WalletsUtils + .getCallArgs(api, options, values) + .then((callArgs) => { + if (!callArgs) { + return { func, options, values }; + } + + return callArgs; + }); + }); +} + +export function estimateGas (_func, _options, _values = []) { + return getTxArgs(_func, _options, _values) + .then((callArgs) => { + const { func, options, values } = callArgs; + return func._estimateGas(options, values); + }) + .then((gas) => { + return WalletsUtils + .isWallet(_func.contract.api, _options.from) + .then((isWallet) => { + if (isWallet) { + return gas.mul(1.5); + } + + return gas; + }); + }); +} + +export function postTransaction (_func, _options, _values = []) { + return getTxArgs(_func, _options, _values) + .then((callArgs) => { + const { func, options, values } = callArgs; + return func._postTransaction(options, values); + }); +} + +export function patchApi (api) { + api.patch = { + ...api.patch, + contract: patchContract + }; +} + +export function patchContract (contract) { + contract._functions.forEach((func) => { + if (!func.constant) { + func._postTransaction = func.postTransaction; + func._estimateGas = func.estimateGas; + + func.postTransaction = postTransaction.bind(contract, func); + func.estimateGas = estimateGas.bind(contract, func); + } + }); +} + export function checkIfTxFailed (api, tx, gasSent) { return api.pollMethod('eth_getTransactionReceipt', tx) .then((receipt) => { diff --git a/js/src/util/validation.js b/js/src/util/validation.js index 75802ed5f..a789300b4 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -18,6 +18,8 @@ import BigNumber from 'bignumber.js'; import util from '~/api/util'; +import { NULL_ADDRESS } from './constants'; + export const ERRORS = { invalidAddress: 'address is an invalid network address', invalidAmount: 'the supplied amount should be a valid positive number', @@ -33,21 +35,21 @@ export const ERRORS = { gasBlockLimit: 'the transaction execution will exceed the block gas limit' }; -export function validateAbi (abi, api) { +export function validateAbi (abi) { let abiError = null; let abiParsed = null; try { abiParsed = JSON.parse(abi); - if (!api.util.isArray(abiParsed)) { + if (!util.isArray(abiParsed)) { abiError = ERRORS.invalidAbi; return { abi, abiError, abiParsed }; } // Validate each elements of the Array const invalidIndex = abiParsed - .map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api) || isAbiFallback(o)) + .map((o) => isValidAbiEvent(o) || isValidAbiFunction(o) || isAbiFallback(o)) .findIndex((valid) => !valid); if (invalidIndex !== -1) { @@ -68,13 +70,13 @@ export function validateAbi (abi, api) { }; } -function isValidAbiFunction (object, api) { +function isValidAbiFunction (object) { if (!object) { return false; } return ((object.type === 'function' && object.name) || object.type === 'constructor') && - (object.inputs && api.util.isArray(object.inputs)); + (object.inputs && util.isArray(object.inputs)); } function isAbiFallback (object) { @@ -85,14 +87,14 @@ function isAbiFallback (object) { return object.type === 'fallback'; } -function isValidAbiEvent (object, api) { +function isValidAbiEvent (object) { if (!object) { return false; } return (object.type === 'event') && (object.name) && - (object.inputs && api.util.isArray(object.inputs)); + (object.inputs && util.isArray(object.inputs)); } export function validateAddress (address) { @@ -174,3 +176,11 @@ export function validateUint (value) { valueError }; } + +export function isNullAddress (address) { + if (address && address.substr(0, 2) === '0x') { + return isNullAddress(address.substr(2)); + } + + return address === NULL_ADDRESS; +} diff --git a/js/src/util/wallets.js b/js/src/util/wallets.js index 3732840d8..739a1e3c2 100644 --- a/js/src/util/wallets.js +++ b/js/src/util/wallets.js @@ -14,13 +14,92 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { range, uniq } from 'lodash'; +import BigNumber from 'bignumber.js'; +import { intersection, range, uniq } from 'lodash'; +import Contract from '~/api/contract'; import { bytesToHex, toHex } from '~/api/util/format'; import { validateAddress } from '~/util/validation'; +import WalletAbi from '~/contracts/abi/wallet.json'; + +const _cachedWalletLookup = {}; export default class WalletsUtils { + static getCallArgs (api, options, values = []) { + const walletContract = new Contract(api, WalletAbi); + + const promises = [ + api.parity.accountsInfo(), + WalletsUtils.fetchOwners(walletContract.at(options.from)) + ]; + + return Promise + .all(promises) + .then(([ accounts, owners ]) => { + const addresses = Object.keys(accounts); + const owner = intersection(addresses, owners).pop(); + + if (!owner) { + return false; + } + + return owner; + }) + .then((owner) => { + if (!owner) { + return false; + } + + const _options = Object.assign({}, options); + const { from, to, value = new BigNumber(0), data } = options; + + delete _options.data; + + const nextValues = [ to, value, data ]; + const nextOptions = { + ..._options, + from: owner, + to: from, + value: new BigNumber(0) + }; + + const execFunc = walletContract.instance.execute; + + return { func: execFunc, options: nextOptions, values: nextValues }; + }); + } + + /** + * Check whether the given address could be + * a Wallet. The result is cached in order not + * to make unnecessary calls on non-wallet accounts + */ + static isWallet (api, address) { + if (!_cachedWalletLookup[address]) { + const walletContract = new Contract(api, WalletAbi); + + _cachedWalletLookup[address] = walletContract + .at(address) + .instance + .m_numOwners + .call() + .then((result) => { + if (!result || result.equals(0)) { + return false; + } + + return true; + }) + .then((bool) => { + _cachedWalletLookup[address] = Promise.resolve(bool); + return bool; + }); + } + + return _cachedWalletLookup[address]; + } + static fetchRequire (walletContract) { return walletContract.instance.m_required.call(); } diff --git a/js/src/views/Account/Header/header.css b/js/src/views/Account/Header/header.css index 26c2a5b22..6c1527129 100644 --- a/js/src/views/Account/Header/header.css +++ b/js/src/views/Account/Header/header.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index 058c20db3..6e508d05e 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -42,7 +42,6 @@ export default class Header extends Component { render () { const { account, balance, className, children, hideName } = this.props; const { address, meta, uuid } = account; - if (!account) { return null; } diff --git a/js/src/views/Account/Transactions/transactions.css b/js/src/views/Account/Transactions/transactions.css index 6b44d298f..de8543bb1 100644 --- a/js/src/views/Account/Transactions/transactions.css +++ b/js/src/views/Account/Transactions/transactions.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Account/account.css b/js/src/views/Account/account.css index a9a039c83..e415a3d8a 100644 --- a/js/src/views/Account/account.css +++ b/js/src/views/Account/account.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 82b2e6b71..f274d8fbe 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -33,16 +33,9 @@ import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; -import SMSVerificationStore from '~/modals/Verification/sms-store'; -import EmailVerificationStore from '~/modals/Verification/email-store'; - import styles from './account.css'; class Account extends Component { - static contextTypes = { - api: PropTypes.object.isRequired - } - static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, fetchCertifiers: PropTypes.func.isRequired, @@ -51,7 +44,6 @@ class Account extends Component { params: PropTypes.object, accounts: PropTypes.object, - isTestnet: PropTypes.bool, balances: PropTypes.object } @@ -60,7 +52,6 @@ class Account extends Component { showEditDialog: false, showFundDialog: false, showVerificationDialog: false, - verificationStore: null, showTransferDialog: false, showPasswordDialog: false } @@ -195,7 +186,6 @@ class Account extends Component { return ( ); } @@ -221,13 +211,11 @@ class Account extends Component { return null; } - const store = this.state.verificationStore; const { address } = this.props.params; return ( ); @@ -301,22 +289,6 @@ class Account extends Component { this.setState({ showVerificationDialog: true }); } - selectVerificationMethod = (name) => { - const { isTestnet } = this.props; - if (typeof isTestnet !== 'boolean' || this.state.verificationStore) return; - - const { api } = this.context; - const { address } = this.props.params; - - let verificationStore = null; - if (name === 'sms') { - verificationStore = new SMSVerificationStore(api, address, isTestnet); - } else if (name === 'email') { - verificationStore = new EmailVerificationStore(api, address, isTestnet); - } - this.setState({ verificationStore }); - } - onVerificationClose = () => { this.setState({ showVerificationDialog: false }); } @@ -344,13 +316,11 @@ class Account extends Component { function mapStateToProps (state) { const { accounts } = state.personal; - const { isTest } = state.nodeStatus; const { balances } = state.balances; const { images } = state; return { accounts, - isTestnet: isTest, balances, images }; diff --git a/js/src/views/Accounts/List/list.css b/js/src/views/Accounts/List/list.css index 6c23d32e1..4caa7a69a 100644 --- a/js/src/views/Accounts/List/list.css +++ b/js/src/views/Accounts/List/list.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index d5bdd9662..9cebdda6e 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -34,7 +34,6 @@ class List extends Component { order: PropTypes.string, orderFallback: PropTypes.string, search: PropTypes.array, - walletsOwners: PropTypes.object, fetchCertifiers: PropTypes.func.isRequired, fetchCertifications: PropTypes.func.isRequired, @@ -58,7 +57,7 @@ class List extends Component { } renderAccounts () { - const { accounts, balances, empty, link, walletsOwners, handleAddSearchToken } = this.props; + const { accounts, balances, empty, link, handleAddSearchToken } = this.props; if (empty) { return ( @@ -76,7 +75,7 @@ class List extends Component { const account = accounts[address] || {}; const balance = balances[address] || {}; - const owners = walletsOwners && walletsOwners[address] || null; + const owners = account.owners || null; return (
token.token.tag.toLowerCase() === 'eth'); const ethB = balanceB.tokens.find(token => token.token.tag.toLowerCase() === 'eth'); - if (!ethA && !ethB) return 0; - if (ethA && !ethB) return -1; - if (!ethA && ethB) return 1; + if (!ethA && !ethB) { + return 0; + } else if (ethA && !ethB) { + return -1; + } else if (!ethA && ethB) { + return 1; + } return -1 * ethA.value.comparedTo(ethB.value); } @@ -159,9 +166,13 @@ class List extends Component { .sort() .join(''); - if (!tagsA && !tagsB) return 0; - if (tagsA && !tagsB) return -1; - if (!tagsA && tagsB) return 1; + if (!tagsA && !tagsB) { + return 0; + } else if (tagsA && !tagsB) { + return -1; + } else if (!tagsA && tagsB) { + return 1; + } return tagsA.localeCompare(tagsB); } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 3b1d64d18..55e868c08 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -157,7 +157,11 @@ export default class Summary extends Component { const { link, noLink, account, name } = this.props; const { address } = account; - const viewLink = `/${link || 'accounts'}/${address}`; + const baseLink = account.wallet + ? 'wallet' + : link || 'accounts'; + + const viewLink = `/${baseLink}/${address}`; const content = ( diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css index 317905fd5..2a7cdcec9 100644 --- a/js/src/views/Accounts/accounts.css +++ b/js/src/views/Accounts/accounts.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index 70b6f770a..06322e436 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ContentAdd from 'material-ui/svg-icons/content/add'; -import { uniq, isEqual } from 'lodash'; +import { uniq, isEqual, pickBy, omitBy } from 'lodash'; import List from './List'; import { CreateAccount, CreateWallet } from '~/modals'; @@ -36,9 +36,6 @@ class Accounts extends Component { setVisibleAccounts: PropTypes.func.isRequired, accounts: PropTypes.object.isRequired, hasAccounts: PropTypes.bool.isRequired, - wallets: PropTypes.object.isRequired, - walletsOwners: PropTypes.object.isRequired, - hasWallets: PropTypes.bool.isRequired, balances: PropTypes.object } @@ -62,8 +59,8 @@ class Accounts extends Component { } componentWillReceiveProps (nextProps) { - const prevAddresses = Object.keys({ ...this.props.accounts, ...this.props.wallets }); - const nextAddresses = Object.keys({ ...nextProps.accounts, ...nextProps.wallets }); + const prevAddresses = Object.keys(this.props.accounts); + const nextAddresses = Object.keys(nextProps.accounts); if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) { this.setVisibleAccounts(nextProps); @@ -75,8 +72,8 @@ class Accounts extends Component { } setVisibleAccounts (props = this.props) { - const { accounts, wallets, setVisibleAccounts } = props; - const addresses = Object.keys({ ...accounts, ...wallets }); + const { accounts, setVisibleAccounts } = props; + const addresses = Object.keys(accounts); setVisibleAccounts(addresses); } @@ -115,30 +112,38 @@ class Accounts extends Component { } renderAccounts () { + const { accounts, balances } = this.props; + + const _accounts = omitBy(accounts, (a) => a.wallet); + const _hasAccounts = Object.keys(_accounts).length > 0; + if (!this.state.show) { - return this.renderLoading(this.props.accounts); + return this.renderLoading(_accounts); } - const { accounts, hasAccounts, balances } = this.props; const { searchValues, sortOrder } = this.state; return ( ); } renderWallets () { + const { accounts, balances } = this.props; + + const wallets = pickBy(accounts, (a) => a.wallet); + const hasWallets = Object.keys(wallets).length > 0; + if (!this.state.show) { - return this.renderLoading(this.props.wallets); + return this.renderLoading(wallets); } - const { wallets, hasWallets, balances, walletsOwners } = this.props; const { searchValues, sortOrder } = this.state; if (!wallets || Object.keys(wallets).length === 0) { @@ -154,7 +159,6 @@ class Accounts extends Component { empty={ !hasWallets } order={ sortOrder } handleAddSearchToken={ this.onAddSearchToken } - walletsOwners={ walletsOwners } /> ); } @@ -287,34 +291,12 @@ class Accounts extends Component { } function mapStateToProps (state) { - const { accounts, hasAccounts, wallets, hasWallets, accountsInfo } = state.personal; + const { accounts, hasAccounts } = state.personal; const { balances } = state.balances; - const walletsInfo = state.wallet.wallets; - - const walletsOwners = Object - .keys(walletsInfo) - .map((wallet) => { - const owners = walletsInfo[wallet].owners || []; - - return { - owners: owners.map((owner) => ({ - address: owner, - name: accountsInfo[owner] && accountsInfo[owner].name || owner - })), - address: wallet - }; - }) - .reduce((walletsOwners, wallet) => { - walletsOwners[wallet.address] = wallet.owners; - return walletsOwners; - }, {}); return { - accounts, - hasAccounts, - wallets, - walletsOwners, - hasWallets, + accounts: accounts, + hasAccounts: hasAccounts, balances }; } diff --git a/js/src/views/Address/address.css b/js/src/views/Address/address.css index cd44688bb..1dc3dd6af 100644 --- a/js/src/views/Address/address.css +++ b/js/src/views/Address/address.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Address/address.js b/js/src/views/Address/address.js index 8798e8262..c0927bedf 100644 --- a/js/src/views/Address/address.js +++ b/js/src/views/Address/address.js @@ -182,7 +182,6 @@ class Address extends Component { return ( ); } diff --git a/js/src/views/Addresses/addresses.css b/js/src/views/Addresses/addresses.css index 5bbbf8a7e..a5d27548b 100644 --- a/js/src/views/Addresses/addresses.css +++ b/js/src/views/Addresses/addresses.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Application/Container/container.js b/js/src/views/Application/Container/container.js index 421483e54..80e3571c1 100644 --- a/js/src/views/Application/Container/container.js +++ b/js/src/views/Application/Container/container.js @@ -22,9 +22,6 @@ import { Errors, ParityBackground, Tooltips } from '~/ui'; import styles from '../application.css'; export default class Container extends Component { - static contextTypes = { - muiTheme: PropTypes.object.isRequired - }; static propTypes = { children: PropTypes.node.isRequired, @@ -34,13 +31,10 @@ export default class Container extends Component { }; render () { - const { muiTheme } = this.context; const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props; return ( - + diff --git a/js/src/views/Application/FrameError/frameError.css b/js/src/views/Application/FrameError/frameError.css index 0cec767ba..d8330a486 100644 --- a/js/src/views/Application/FrameError/frameError.css +++ b/js/src/views/Application/FrameError/frameError.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Application/Status/status.css b/js/src/views/Application/Status/status.css index 4eeb4b918..518411a94 100644 --- a/js/src/views/Application/Status/status.css +++ b/js/src/views/Application/Status/status.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index 172750c8f..8df18a01f 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Application/application.css b/js/src/views/Application/application.css index 9d957d6c9..f30f82ce3 100644 --- a/js/src/views/Application/application.css +++ b/js/src/views/Application/application.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index 49d299661..03b85ca38 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -56,7 +56,7 @@ class Application extends Component { render () { const [root] = (window.location.hash || '').replace('#/', '').split('/'); - const isDapp = root === 'app'; + const isMinimized = root === 'app' || root === 'web'; if (inFrame) { return ( @@ -66,9 +66,9 @@ class Application extends Component { return (
- { isDapp ? this.renderDapp() : this.renderApp() } + { isMinimized ? this.renderMinimized() : this.renderApp() } - +
); } @@ -98,7 +98,7 @@ class Application extends Component { ); } - renderDapp () { + renderMinimized () { const { children } = this.props; return ( diff --git a/js/src/views/Application/store.js b/js/src/views/Application/store.js index b9f960eff..535d0942b 100644 --- a/js/src/views/Application/store.js +++ b/js/src/views/Application/store.js @@ -41,7 +41,7 @@ export default class Store { _checkAccounts () { this._api.parity - .accountsInfo() + .allAccountsInfo() .then((info) => { const accounts = Object.keys(info).filter((address) => info[address].uuid); diff --git a/js/src/views/Connection/connection.css b/js/src/views/Connection/connection.css index 968902bfa..7e497bf44 100644 --- a/js/src/views/Connection/connection.css +++ b/js/src/views/Connection/connection.css @@ -1,4 +1,4 @@ -/* Copyright 2015; 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Connection/connection.js b/js/src/views/Connection/connection.js index 8cc2378d9..4f2ae7b1d 100644 --- a/js/src/views/Connection/connection.js +++ b/js/src/views/Connection/connection.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ActionCompareArrows from 'material-ui/svg-icons/action/compare-arrows'; @@ -34,27 +35,26 @@ class Connection extends Component { static propTypes = { isConnected: PropTypes.bool, isConnecting: PropTypes.bool, - isPingable: PropTypes.bool, needsToken: PropTypes.bool } state = { + loading: false, token: '', validToken: false } render () { - const { isConnected, isConnecting, isPingable } = this.props; - const isOk = !isConnecting && isConnected && isPingable; + const { isConnected, needsToken } = this.props; - if (isOk) { + if (isConnected) { return null; } - const typeIcon = isPingable + const typeIcon = needsToken ? : ; - const description = isPingable + const description = needsToken ? this.renderSigner() : this.renderPing(); @@ -68,7 +68,7 @@ class Connection extends Component {
- +
{ typeIcon } @@ -82,20 +82,44 @@ class Connection extends Component { } renderSigner () { - const { token, validToken } = this.state; + const { loading, token, validToken } = this.state; const { isConnecting, needsToken } = this.props; if (needsToken && !isConnecting) { return (
-
Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run parity signer new-token and supply the token below
+
+ parity signer new-token + } } /> +
+ disabled={ loading } + error={ + validToken || (!token || !token.length) + ? null + : ( + + ) + } + hint={ + + } + label={ + + } + onChange={ this.onChangeToken } + value={ token } />
); @@ -103,7 +127,9 @@ class Connection extends Component { return (
- Connecting to the Parity Secure API. +
); } @@ -111,13 +137,17 @@ class Connection extends Component { renderPing () { return (
- Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network. +
); } - onChangeToken = (event, token) => { - const validToken = /[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}/.test(token); + onChangeToken = (event, _token) => { + const token = _token.trim(); + const validToken = /^[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}$/.test(token); + this.setState({ token, validToken }, () => { validToken && this.setToken(); }); @@ -127,15 +157,23 @@ class Connection extends Component { const { api } = this.context; const { token } = this.state; - api.updateToken(token, 0); - this.setState({ token: '', validToken: false }); + this.setState({ loading: true }); + + api + .updateToken(token, 0) + .then((isValid) => { + this.setState({ + loading: isValid || false, + validToken: isValid + }); + }); } } function mapStateToProps (state) { - const { isConnected, isConnecting, isPingable, needsToken } = state.nodeStatus; + const { isConnected, isConnecting, needsToken } = state.nodeStatus; - return { isConnected, isConnecting, isPingable, needsToken }; + return { isConnected, isConnecting, needsToken }; } function mapDispatchToProps (dispatch) { diff --git a/js/src/views/Contract/Queries/inputQuery.js b/js/src/views/Contract/Queries/inputQuery.js index 9d3cf279d..df6b24642 100644 --- a/js/src/views/Contract/Queries/inputQuery.js +++ b/js/src/views/Contract/Queries/inputQuery.js @@ -95,7 +95,10 @@ export default class InputQuery extends Component { return (); } - if (!results || results.length < 1) return null; + if (!results || results.length < 1) { + return null; + } + return outputs .map((out, index) => ({ name: out.name, @@ -181,7 +184,9 @@ export default class InputQuery extends Component { } renderValue (value) { - if (!value) return 'no data'; + if (value === null || value === undefined) { + return 'no data'; + } const { api } = this.context; diff --git a/js/src/views/Contract/Queries/queries.css b/js/src/views/Contract/Queries/queries.css index 41bf00265..eabbb77ea 100644 --- a/js/src/views/Contract/Queries/queries.css +++ b/js/src/views/Contract/Queries/queries.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Contract/contract.css b/js/src/views/Contract/contract.css index f49da0831..4752fd04a 100644 --- a/js/src/views/Contract/contract.css +++ b/js/src/views/Contract/contract.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index 38447911f..b7ab705bb 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -220,7 +220,7 @@ class Contract extends Component { key='editmeta' icon={ } label='edit' - onClick={ this.onEditClick } />, + onClick={ this.showEditDialog } />,
); @@ -131,4 +140,34 @@ export default class Dapps extends Component { onClickAcceptExternal = () => { this.store.closeExternalOverlay(); } + + openPermissionsModal = () => { + const { accounts } = this.props; + + this.permissionStore.openModal(accounts); + } } + +function mapStateToProps (state) { + const { accounts } = state.personal; + + /** + * Do not show the Wallet Accounts in the Dapps + * Permissions Modal. This will come in v1.6, but + * for now it would break dApps using Web3... + */ + const _accounts = omitBy(accounts, (account) => account.wallet); + + return { + accounts: _accounts + }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({}, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Dapps); diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index e167331a4..42342aff3 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -46,6 +46,7 @@ export default class DappsStore { constructor (api) { this._api = api; + this.readDisplayApps(); this.loadExternalOverlay(); this.loadApps(); this.subscribeToChanges(); diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index d6fd3a538..a955bd687 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -1,4 +1,4 @@ -/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* Copyright 2015, 2016 Parity Technologies (UK) Ltd. /* This file is part of Parity. /* /* Parity is free software: you can redistribute it and/or modify diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index 2f7c52957..42c52f47d 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -28,9 +28,6 @@ import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text import styles from './parityBar.css'; class ParityBar extends Component { - static contextTypes = { - muiTheme: PropTypes.object.isRequired - }; static propTypes = { pending: PropTypes.array, @@ -66,7 +63,6 @@ class ParityBar extends Component { renderBar () { const { dapp } = this.props; - const { muiTheme } = this.context; if (!dapp) { return null; @@ -80,7 +76,7 @@ class ParityBar extends Component { return (
- +
+ ); + } + + onUpdateUrl = (ev) => { + this.setState({ + currentUrl: ev.target.value + }); + }; + + onKey = (ev) => { + const key = ev.which; + + if (key === KEY_ESC) { + this.setState({ + currentUrl: this.props.url + }); + return; + } + + if (key === KEY_ENTER) { + this.onGo(); + return; + } + }; + + onGo = () => { + if (this.isPristine()) { + this.props.onRefresh(); + } else { + this.props.onChange(this.state.currentUrl); + } + }; +} diff --git a/js/src/views/Web/AddressBar/index.js b/js/src/views/Web/AddressBar/index.js new file mode 100644 index 000000000..a7f53f1e2 --- /dev/null +++ b/js/src/views/Web/AddressBar/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './addressBar'; diff --git a/js/src/views/Web/index.js b/js/src/views/Web/index.js new file mode 100644 index 000000000..6e1ade34b --- /dev/null +++ b/js/src/views/Web/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './web'; diff --git a/js/src/views/Web/web.css b/js/src/views/Web/web.css new file mode 100644 index 000000000..13cb06915 --- /dev/null +++ b/js/src/views/Web/web.css @@ -0,0 +1,68 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ +.wrapper { + border: 0; + position: absolute; + height: 100%; + width: 100%; +} + +.loading { + text-align: center; + margin-top: 5em; + color: #999; + font-size: 2em; +} + +$addressbar: 36px; +.url { + position: absolute; + top: 0; + width: 100%; + box-sizing: border-box; + height: $addressbar; + line-height: $addressbar; + display: flex; + background: rgba(0, 0, 0, 0.85); + + > button { + min-width: 50px !important; + } + + > input { + flex: 1; + font-size: 1.1em; + padding: 0.2em 0.5em; + border-radius: 0.25em; + border: none; + background: #666; + color: #eee; + outline: none; + box-shadow: 0 0 2px #111 inset; + margin: 2px 0; + } +} + +.frame { + position: absolute; + width: 100%; + height: calc(100% - $addressbar); + top: $addressbar; + bottom: 0; + left: 0; + right: 0; +} diff --git a/js/src/views/Web/web.js b/js/src/views/Web/web.js new file mode 100644 index 000000000..ac7d1c59e --- /dev/null +++ b/js/src/views/Web/web.js @@ -0,0 +1,118 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import store from 'store'; + +import AddressBar from './AddressBar'; + +import styles from './web.css'; + +const LS_LAST_ADDRESS = '_parity::webLastAddress'; + +export default class Web extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + } + + state = { + displayedUrl: this.lastAddress(), + isLoading: true, + token: null, + url: this.lastAddress() + }; + + componentDidMount () { + this.context.api.signer.generateWebProxyAccessToken().then(token => { + this.setState({ token }); + }); + } + + address () { + const { dappsUrl } = this.context.api; + const { url, token } = this.state; + const path = url.replace(/:/g, '').replace(/\/\//g, '/'); + + return `${dappsUrl}/web/${token}/${path}/`; + } + + lastAddress () { + return store.get(LS_LAST_ADDRESS) || 'https://mkr.market'; + } + + render () { + const { displayedUrl, isLoading, token } = this.state; + const address = this.address(); + + if (!token) { + return ( +
+

+ Requesting access token... +

+
+ ); + } + + return ( +
+ +