diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1022333fc..0cc444846 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ variables: GIT_DEPTH: "3" SIMPLECOV: "true" RUST_BACKTRACE: "1" + RUSTFLAGS: "-D warnings" cache: key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" untracked: true @@ -20,10 +21,17 @@ linux-stable: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> parity.md5 + - sh scripts/deb-build.sh amd64 + - cp target/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_amd64.deb" + - md5sum "parity_"$VER"_amd64.deb" >> "parity_"$VER"_amd64.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5" tags: - rust - rust-stable @@ -43,10 +51,17 @@ linux-stable-14.04: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> parity.md5 + - sh scripts/deb-build.sh amd64 + - cp target/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_amd64.deb" + - md5sum "parity_"$VER"_amd64.deb" >> "parity_"$VER"_amd64.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5" tags: - rust - rust-14.04 @@ -133,10 +148,17 @@ linux-armv7: - cargo build --target armv7-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity - md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> parity.md5 + - sh scripts/deb-build.sh armhf + - cp target/armv7-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_armhf.deb" + - md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" tags: - rust - rust-arm @@ -161,10 +183,17 @@ linux-arm: - cargo build --target arm-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity - md5sum target/arm-unknown-linux-gnueabihf/release/parity >> parity.md5 + - sh scripts/deb-build.sh armhf + - cp target/arm-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_armhf.deb" + - md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" tags: - rust - rust-arm @@ -217,10 +246,17 @@ linux-aarch64: - cargo build --target aarch64-unknown-linux-gnu --release --verbose - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity - md5sum target/aarch64-unknown-linux-gnu/release/parity >> parity.md5 + - sh scripts/deb-build.sh arm64 + - cp target/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_arm64.deb" + - md5sum "parity_"$VER"_arm64.deb" >> "parity_"$VER"_arm64.deb.md5" - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5" tags: - rust - rust-arm @@ -262,7 +298,6 @@ windows: - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings - rustup default stable-x86_64-pc-windows-msvc - - git submodule update --init - cargo build --release --verbose - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe @@ -298,7 +333,6 @@ windows: - target/release/parity.pdb - nsis/InstallParity.exe name: "x86_64-pc-windows-msvc_parity" - allow_failure: true test-linux: stage: test before_script: @@ -314,7 +348,6 @@ js-release: stage: build image: ethcore/javascript:latest only: - - js - master - beta - tags diff --git a/.gitmodules b/.gitmodules index a3e70108b..06b71f6ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = ethcore/res/ethereum/tests url = https://github.com/ethereum/tests.git branch = develop -[submodule "js/build"] - path = js/build - url = https://github.com/ethcore/js-precompiled diff --git a/Cargo.lock b/Cargo.lock index e716b85b8..fdb80f602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,8 +222,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "elastic-array" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.6.0" +source = "git+https://github.com/ethcore/elastic-array#70e4012e691b732c7c4cb04e9232799e6aa268bc" +dependencies = [ + "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "env_logger" @@ -328,6 +331,7 @@ name = "ethcore-dapps" version = "1.4.0" dependencies = [ "clippy 0.0.90 (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)", "ethcore-devtools 1.4.0", "ethcore-rpc 1.4.0", @@ -340,9 +344,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", - "parity-dapps-glue 1.4.0", - "parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", + "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-ui 1.4.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)", @@ -509,7 +511,7 @@ dependencies = [ "ethcore-util 1.4.0", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-dapps-glue 1.4.0", + "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-ui 1.4.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -541,7 +543,7 @@ dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", - "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.1", @@ -614,6 +616,7 @@ dependencies = [ "itertools 0.4.13 (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.15 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -813,7 +816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "json-ipc-server" version = "0.2.4" -source = "git+https://github.com/ethcore/json-ipc-server.git#5fbd0253750d3097b9a8fb27effa84c18d630bbb" +source = "git+https://github.com/ethcore/json-ipc-server.git#4642cd03ec1d23db89df80d22d5a88e7364ab885" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -854,7 +857,7 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "6.1.1" -source = "git+https://github.com/ethcore/jsonrpc-http-server.git#ee72e4778583daf901b5692468fc622f46abecb6" +source = "git+https://github.com/ethcore/jsonrpc-http-server.git#cd6d4cb37d672cc3057aecd0692876f9e85f3ba5" dependencies = [ "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1175,23 +1178,10 @@ name = "owning_ref" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "parity-dapps" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quasi_codegen 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parity-dapps-glue" version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1202,19 +1192,28 @@ dependencies = [ "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parity-dapps-home" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", -] - [[package]] name = "parity-ui" version = "1.4.0" dependencies = [ - "parity-dapps-glue 1.4.0", + "parity-ui-dev 1.4.0", + "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parity-ui-dev" +version = "1.4.0" +dependencies = [ + "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parity-ui-precompiled" +version = "1.4.0" +source = "git+https://github.com/ethcore/js-precompiled.git#9f8baa9d0e54056c41a842b351597d0565beda98" +dependencies = [ + "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1425,7 +1424,7 @@ dependencies = [ name = "rlp" version = "0.1.0" dependencies = [ - "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", "ethcore-bigint 0.1.1", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1884,7 +1883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" version = "0.5.2" -source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#609b21fdab96c8fffedec8699755ce3bea9454cb" +source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#00bd2134b07b4bc8ea47b7f6c7afce16bbe34c8f" dependencies = [ "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1958,7 +1957,7 @@ dependencies = [ "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" -"checksum elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bc9250a632e7c001b741eb0ec6cee93c9a5b6d5f1879696a4b94d62b012210a" +"checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" @@ -2016,8 +2015,8 @@ dependencies = [ "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 owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" -"checksum parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" -"checksum parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" +"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)" = "" "checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" diff --git a/Cargo.toml b/Cargo.toml index 0173533ee..62039696c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,11 @@ lazy_static = "0.2" regex = "0.1" isatty = "0.1" toml = "0.2" +serde = "0.8.0" +serde_json = "0.8.0" +hyper = { version = "0.9", default-features = false } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } +json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } fdlimit = { path = "util/fdlimit" } ethcore = { path = "ethcore" } ethcore-util = { path = "util" } @@ -40,12 +44,9 @@ ethcore-ipc = { path = "ipc/rpc" } ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-logger = { path = "logger" } rlp = { path = "util/rlp" } -json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } +ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.90", optional = true} -ethcore-stratum = { path = "stratum" } -serde = "0.8.0" -serde_json = "0.8.0" [target.'cfg(windows)'.dependencies] winapi = "0.2" @@ -53,19 +54,26 @@ winapi = "0.2" [target.'cfg(not(windows))'.dependencies] daemonize = "0.2" -[dependencies.hyper] -version = "0.9" -default-features = false - [features] -default = ["ui", "use-precompiled-js"] -ui = ["dapps", "ethcore-signer/ui"] -use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"] +default = ["ui-precompiled"] + +ui = [ + "dapps", + "ethcore-dapps/ui", + "ethcore-signer/ui", +] +ui-precompiled = [ + "dapps", + "ethcore-signer/ui-precompiled", + "ethcore-dapps/ui-precompiled", +] + dapps = ["ethcore-dapps"] ipc = ["ethcore/ipc", "ethsync/ipc"] jit = ["ethcore/jit"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] json-tests = ["ethcore/json-tests"] +test-heavy = ["ethcore/test-heavy"] stratum = ["ipc"] ethkey-cli = ["ethcore/ethkey-cli"] ethstore-cli = ["ethcore/ethstore-cli"] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 77ed00e19..9c49c7e28 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" [dependencies] rand = "0.3.14" log = "0.3" +env_logger = "0.3" jsonrpc-core = "3.0" jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } @@ -19,21 +20,17 @@ url = "1.0" rustc-serialize = "0.3" serde = "0.8" serde_json = "0.8" -serde_macros = { version = "0.8", optional = true } -zip = { version = "0.1", default-features = false } ethabi = "0.2.2" linked-hash-map = "0.3" +parity-dapps-glue = "1.4" +mime = "0.2" +serde_macros = { version = "0.8", optional = true } +zip = { version = "0.1", default-features = false } ethcore-devtools = { path = "../devtools" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } -parity-dapps-glue = { path = "./js-glue" } -mime = "0.2" -### DEPRECATED -parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -### /DEPRECATED mime_guess = { version = "1.6.1" } clippy = { version = "0.0.90", optional = true} @@ -46,7 +43,5 @@ default = ["serde_codegen"] nightly = ["serde_macros"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] -use-precompiled-js = [ - "parity-ui/use-precompiled-js", - "parity-dapps-home/use-precompiled-js", -] +ui = ["parity-ui/no-precompiled-js"] +ui-precompiled = ["parity-ui/use-precompiled-js"] diff --git a/dapps/js-glue/src/js.rs b/dapps/js-glue/src/js.rs index d3593a306..39c33689c 100644 --- a/dapps/js-glue/src/js.rs +++ b/dapps/js-glue/src/js.rs @@ -53,11 +53,11 @@ fn die(s: &'static str, e: T) -> ! { pub fn test(_path: &str) { } #[cfg(feature = "use-precompiled-js")] -pub fn build(_path: &str) { +pub fn build(_path: &str, _dest: &str) { } #[cfg(not(feature = "use-precompiled-js"))] -pub fn build(path: &str) { +pub fn build(path: &str, dest: &str) { let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD)) .arg("install") .arg("--no-progress") @@ -70,7 +70,7 @@ pub fn build(path: &str) { .arg("run") .arg("build") .env("NODE_ENV", "production") - .env("BUILD_DEST", "build") + .env("BUILD_DEST", dest) .current_dir(path) .status() .unwrap_or_else(|e| die("Building JS code", e)); diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 80c5e09de..90ab837f5 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -15,24 +15,31 @@ // along with Parity. If not, see . use std::sync::Arc; +use unicase::UniCase; use hyper::{server, net, Decoder, Encoder, Next, Control}; +use hyper::header; +use hyper::method::Method; +use hyper::header::AccessControlAllowOrigin; + use api::types::{App, ApiError}; -use api::response::{as_json, as_json_error, ping_response}; +use api::response; +use apps::fetcher::ContentFetcher; + use handlers::extract_url; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; -use apps::fetcher::ContentFetcher; +use jsonrpc_http_server::cors; #[derive(Clone)] pub struct RestApi { - local_domain: String, + cors_domains: Option>, endpoints: Arc, fetcher: Arc, } impl RestApi { - pub fn new(local_domain: String, endpoints: Arc, fetcher: Arc) -> Box { + pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { - local_domain: local_domain, + cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), endpoints: endpoints, fetcher: fetcher, }) @@ -53,6 +60,7 @@ impl Endpoint for RestApi { struct RestApiRouter { api: RestApi, + origin: Option, path: Option, control: Option, handler: Box, @@ -62,9 +70,10 @@ impl RestApiRouter { fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { RestApiRouter { path: Some(path), + origin: None, control: Some(control), api: api, - handler: as_json_error(&ApiError { + handler: response::as_json_error(&ApiError { code: "404".into(), title: "Not Found".into(), detail: "Resource you requested has not been found.".into(), @@ -80,11 +89,40 @@ impl RestApiRouter { _ => None } } + + /// Returns basic headers for a response (it may be overwritten by the handler) + fn response_headers(&self) -> header::Headers { + let mut headers = header::Headers::new(); + headers.set(header::AccessControlAllowCredentials); + headers.set(header::AccessControlAllowMethods(vec![ + Method::Options, + Method::Post, + Method::Get, + ])); + headers.set(header::AccessControlAllowHeaders(vec![ + UniCase("origin".to_owned()), + UniCase("content-type".to_owned()), + UniCase("accept".to_owned()), + ])); + + if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) { + headers.set(cors_header); + } + + headers + } } impl server::Handler for RestApiRouter { fn on_request(&mut self, request: server::Request) -> Next { + self.origin = cors::read_origin(&request); + + if let Method::Options = *request.method() { + self.handler = response::empty(); + return Next::write(); + } + let url = extract_url(&request); if url.is_none() { // Just return 404 if we can't parse URL @@ -99,11 +137,11 @@ impl server::Handler for RestApiRouter { let hash = url.path.get(2).map(|v| v.as_str()); // at this point path.app_id contains 'api', adjust it to the hash properly, otherwise // we will try and retrieve 'api' as the hash when doing the /api/content route - if let Some(hash) = hash.clone() { path.app_id = hash.to_owned() } + if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() } let handler = endpoint.and_then(|v| match v { - "apps" => Some(as_json(&self.api.list_apps())), - "ping" => Some(ping_response(&self.api.local_domain)), + "apps" => Some(response::as_json(&self.api.list_apps())), + "ping" => Some(response::ping()), "content" => self.resolve_content(hash, path, control), _ => None }); @@ -121,6 +159,7 @@ impl server::Handler for RestApiRouter { } fn on_response(&mut self, res: &mut server::Response) -> Next { + *res.headers_mut() = self.response_headers(); self.handler.on_response(res) } diff --git a/dapps/src/api/cors.rs b/dapps/src/api/cors.rs new file mode 100644 index 000000000..e69de29bb diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index 4064490b3..3e8f196ec 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -19,6 +19,10 @@ use serde_json; use endpoint::Handler; use handlers::{ContentHandler, EchoHandler}; +pub fn empty() -> Box { + Box::new(ContentHandler::ok("".into(), mime!(Text/Plain))) +} + pub fn as_json(val: &T) -> Box { let json = serde_json::to_string(val) .expect("serialization to string is infallible; qed"); @@ -31,10 +35,6 @@ pub fn as_json_error(val: &T) -> Box { Box::new(ContentHandler::not_found(json, mime!(Application/Json))) } -pub fn ping_response(local_domain: &str) -> Box { - Box::new(EchoHandler::cors(vec![ - format!("http://{}", local_domain), - // Allow CORS calls also for localhost - format!("http://{}", local_domain.replace("127.0.0.1", "localhost")), - ])) +pub fn ping() -> Box { + Box::new(EchoHandler::default()) } diff --git a/dapps/src/apps/cache.rs b/dapps/src/apps/cache.rs index c9ca12db9..9d1642fb0 100644 --- a/dapps/src/apps/cache.rs +++ b/dapps/src/apps/cache.rs @@ -66,10 +66,10 @@ impl ContentCache { }, ContentStatus::Ready(ref endpoint) => { trace!(target: "dapps", "Removing {} because of limit.", entry.0); - // Remove path - let res = fs::remove_dir_all(&endpoint.path()); + // Remove path (dir or file) + let res = fs::remove_dir_all(&endpoint.path()).or_else(|_| fs::remove_file(&endpoint.path())); if let Err(e) = res { - warn!(target: "dapps", "Unable to remove dapp: {:?}", e); + warn!(target: "dapps", "Unable to remove dapp/content from cache: {:?}", e); } } } diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 9d66276ea..e4ee63fea 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -46,6 +46,7 @@ pub struct ContentFetcher { resolver: R, cache: Arc>, sync: Arc, + embeddable_at: Option, } impl Drop for ContentFetcher { @@ -57,7 +58,7 @@ impl Drop for ContentFetcher { impl ContentFetcher { - pub fn new(resolver: R, sync_status: Arc) -> Self { + pub fn new(resolver: R, sync_status: Arc, embeddable_at: Option) -> Self { let mut dapps_path = env::temp_dir(); dapps_path.push(random_filename()); @@ -66,9 +67,19 @@ impl ContentFetcher { resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), + embeddable_at: embeddable_at, } } + fn still_syncing() -> 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") + )) + } + #[cfg(test)] fn set_status(&self, content_id: &str, status: ContentStatus) { self.cache.lock().insert(content_id.to_owned(), status); @@ -84,12 +95,10 @@ impl ContentFetcher { } // fallback to resolver if let Ok(content_id) = content_id.from_hex() { - // if app_id is valid, but we are syncing always return true. - if self.sync.is_major_importing() { - return true; - } // else try to resolve the app_id - self.resolver.resolve(content_id).is_some() + 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 } @@ -99,28 +108,19 @@ impl ContentFetcher { let mut cache = self.cache.lock(); let content_id = path.app_id.clone(); - if self.sync.is_major_importing() { - return 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") - )); - } - let (new_status, handler) = { let status = cache.get(&content_id); match status { - // Just server dapp + // Just serve the content Some(&mut ContentStatus::Ready(ref endpoint)) => { (None, endpoint.to_async_handler(path, control)) }, - // App is already being fetched + // Content is already being fetched Some(&mut ContentStatus::Fetching(ref fetch_control)) => { trace!(target: "dapps", "Content fetching in progress. Waiting..."); (None, fetch_control.to_handler(control)) }, - // We need to start fetching app + // 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."); @@ -141,6 +141,10 @@ impl ContentFetcher { }; 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()) + }, Some(URLHintResult::Dapp(dapp)) => { let (handler, fetch_control) = ContentFetcherHandler::new( dapp.url(), @@ -150,6 +154,7 @@ impl ContentFetcher { id: content_id.clone(), dapps_path: self.dapps_path.clone(), on_done: Box::new(on_done), + embeddable_at: self.embeddable_at, } ); @@ -170,6 +175,9 @@ impl ContentFetcher { (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) }, + None if self.sync.is_major_importing() => { + (None, Self::still_syncing()) + }, None => { // This may happen when sync status changes in between // `contains` and `to_handler` @@ -271,6 +279,7 @@ struct DappInstaller { id: String, dapps_path: PathBuf, on_done: Box) + Send>, + embeddable_at: Option, } impl DappInstaller { @@ -363,7 +372,7 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into()); + let app = LocalPageEndpoint::new(target, manifest.clone().into(), self.embeddable_at); // Return modified app manifest Ok((manifest.id.clone(), app)) @@ -396,14 +405,14 @@ mod tests { fn should_true_if_contains_the_app() { // given let path = env::temp_dir(); - let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false)); + 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(), - }); + }, None); // when fetcher.set_status("test", ContentStatus::Ready(handler)); diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index 4728757de..e7a11fc8e 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -97,12 +97,12 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { }) } -pub fn local_endpoints(dapps_path: String) -> Endpoints { +pub fn local_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { let mut pages = Endpoints::new(); for dapp in local_dapps(dapps_path) { pages.insert( dapp.id, - Box::new(LocalPageEndpoint::new(dapp.path, dapp.info)) + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, signer_port)) ); } pages diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 145c3e820..a7b97d37c 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -17,8 +17,7 @@ use endpoint::{Endpoints, Endpoint}; use page::PageEndpoint; use proxypac::ProxyPac; -use parity_dapps::{self, WebApp}; -use parity_dapps_glue::WebApp as NewWebApp; +use parity_dapps::WebApp; mod cache; mod fs; @@ -26,17 +25,14 @@ pub mod urlhint; pub mod fetcher; pub mod manifest; -extern crate parity_dapps_home; 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 fn main_page() -> &'static str { - "home" -} pub fn redirection_address(using_dapps_domains: bool, app_id: &str) -> String { if using_dapps_domains { format!("http://{}{}/", app_id, DAPPS_DOMAIN) @@ -46,72 +42,29 @@ pub fn redirection_address(using_dapps_domains: bool, app_id: &str) -> String { } pub fn utils() -> Box { - Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned())) + Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins - let mut pages = fs::local_endpoints(dapps_path); + let mut pages = fs::local_endpoints(dapps_path, signer_port); // NOTE [ToDr] Dapps will be currently embeded on 8180 - pages.insert("ui".into(), Box::new( - PageEndpoint::new_safe_to_embed(NewUi::default(), signer_port) - )); - - pages.insert("proxy".into(), ProxyPac::boxed()); - insert::(&mut pages, "home"); - + insert::(&mut pages, "ui", Embeddable::Yes(signer_port)); + pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); pages } -fn insert(pages: &mut Endpoints, id: &str) { - pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default()))); +fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { + pages.insert(id.to_owned(), Box::new(match embed_at { + Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port), + Embeddable::No => PageEndpoint::new(T::default()), + })); } -// TODO [ToDr] Temporary wrapper until we get rid of old built-ins. -use std::collections::HashMap; - -struct NewUi { - app: parity_ui::App, - files: HashMap<&'static str, parity_dapps::File>, -} - -impl Default for NewUi { - fn default() -> Self { - let app = parity_ui::App::default(); - let files = { - let mut files = HashMap::new(); - for (k, v) in &app.files { - files.insert(*k, parity_dapps::File { - path: v.path, - content: v.content, - content_type: v.content_type, - }); - } - files - }; - - NewUi { - app: app, - files: files, - } - } -} - -impl WebApp for NewUi { - fn file(&self, path: &str) -> Option<&parity_dapps::File> { - self.files.get(path) - } - - fn info(&self) -> parity_dapps::Info { - let info = self.app.info(); - parity_dapps::Info { - name: info.name, - version: info.version, - author: info.author, - description: info.description, - icon_url: info.icon_url, - } - } +enum Embeddable { + Yes(Option), + #[allow(dead_code)] + No, } diff --git a/dapps/src/handlers/echo.rs b/dapps/src/handlers/echo.rs index 9266de0c6..165ceb171 100644 --- a/dapps/src/handlers/echo.rs +++ b/dapps/src/handlers/echo.rs @@ -17,76 +17,19 @@ //! Echo Handler use std::io::Read; -use hyper::{header, server, Decoder, Encoder, Next}; -use hyper::method::Method; +use hyper::{server, Decoder, Encoder, Next}; use hyper::net::HttpStream; -use unicase::UniCase; use super::ContentHandler; -#[derive(Debug, PartialEq)] -/// Type of Cross-Origin request -enum Cors { - /// Not a Cross-Origin request - no headers needed - No, - /// Cross-Origin request with valid Origin - Allowed(String), - /// Cross-Origin request with invalid Origin - Forbidden, -} - +#[derive(Default)] pub struct EchoHandler { - safe_origins: Vec, content: String, - cors: Cors, handler: Option, } -impl EchoHandler { - - pub fn cors(safe_origins: Vec) -> Self { - EchoHandler { - safe_origins: safe_origins, - content: String::new(), - cors: Cors::Forbidden, - handler: None, - } - } - - fn cors_header(&self, origin: Option) -> Cors { - fn origin_is_allowed(origin: &str, safe_origins: &[String]) -> bool { - for safe in safe_origins { - if origin.starts_with(safe) { - return true; - } - } - false - } - - match origin { - Some(ref origin) if origin_is_allowed(origin, &self.safe_origins) => { - Cors::Allowed(origin.clone()) - }, - None => Cors::No, - _ => Cors::Forbidden, - } - } -} - impl server::Handler for EchoHandler { - fn on_request(&mut self, request: server::Request) -> Next { - let origin = request.headers().get_raw("origin") - .and_then(|list| list.get(0)) - .and_then(|origin| String::from_utf8(origin.clone()).ok()); - - self.cors = self.cors_header(origin); - - // Don't even read the payload if origin is forbidden! - if let Cors::Forbidden = self.cors { - self.handler = Some(ContentHandler::ok(String::new(), mime!(Text/Plain))); - Next::write() - } else { - Next::read() - } + fn on_request(&mut self, _: server::Request) -> Next { + Next::read() } fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { @@ -104,16 +47,6 @@ impl server::Handler for EchoHandler { } fn on_response(&mut self, res: &mut server::Response) -> Next { - if let Cors::Allowed(ref domain) = self.cors { - let mut headers = res.headers_mut(); - headers.set(header::Allow(vec![Method::Options, Method::Post, Method::Get])); - headers.set(header::AccessControlAllowHeaders(vec![ - UniCase("origin".to_owned()), - UniCase("content-type".to_owned()), - UniCase("accept".to_owned()), - ])); - headers.set(header::AccessControlAllowOrigin::Value(domain.clone())); - } self.handler.as_mut() .expect("handler always set in on_request, which is before now; qed") .on_response(res) @@ -125,28 +58,3 @@ impl server::Handler for EchoHandler { .on_response_writable(encoder) } } - -#[test] -fn should_return_correct_cors_value() { - // given - let safe_origins = vec!["chrome-extension://".to_owned(), "http://localhost:8080".to_owned()]; - let cut = EchoHandler { - safe_origins: safe_origins, - content: String::new(), - cors: Cors::No, - handler: None, - }; - - // when - let res1 = cut.cors_header(Some("http://ethcore.io".into())); - let res2 = cut.cors_header(Some("http://localhost:8080".into())); - let res3 = cut.cors_header(Some("chrome-extension://deadbeefcafe".into())); - let res4 = cut.cors_header(None); - - - // then - assert_eq!(res1, Cors::Forbidden); - assert_eq!(res2, Cors::Allowed("http://localhost:8080".into())); - assert_eq!(res3, Cors::Allowed("chrome-extension://deadbeefcafe".into())); - assert_eq!(res4, Cors::No); -} diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index c4f98fc26..3d96e8a40 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -30,6 +30,7 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; +use signer_address; /// Adds security-related headers to the Response. pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { @@ -40,7 +41,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option if let Some(port) = embeddable_at { headers.set_raw( "X-Frame-Options", - vec![format!("ALLOW-FROM http://127.0.0.1:{}", port).into_bytes()] + vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] ); } else { // TODO [ToDr] Should we be more strict here (DENY?)? diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 9e9a26ee3..0041dbedf 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -59,19 +59,17 @@ extern crate ethcore_rpc; extern crate ethcore_util as util; extern crate linked_hash_map; extern crate fetch; - +extern crate parity_dapps_glue as parity_dapps; #[macro_use] extern crate log; - #[macro_use] extern crate mime; #[cfg(test)] extern crate ethcore_devtools as devtools; +#[cfg(test)] +extern crate env_logger; -extern crate parity_dapps_glue; -// TODO [ToDr] - Deprecate when we get rid of old dapps. -extern crate parity_dapps; mod endpoint; mod apps; @@ -95,7 +93,7 @@ use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use ethcore_rpc::Extendable; -static DAPPS_DOMAIN : &'static str = ".parity"; +use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; /// Indicates sync status pub trait SyncStatus: Send + Sync { @@ -197,6 +195,17 @@ impl Server { Some(allowed) } + /// Returns a list of CORS domains for API endpoint. + fn cors_domains(signer_port: Option) -> Vec { + match signer_port { + Some(port) => vec![ + format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), + format!("http://{}", signer_address(port)), + ], + None => vec![], + } + } + fn start_http( addr: &SocketAddr, hosts: Option>, @@ -209,15 +218,17 @@ impl Server { ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port)); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port)); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone())); + let cors_domains = Self::cors_domains(signer_port); + let special = Arc::new({ let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert( router::SpecialEndpoint::Api, - api::RestApi::new(format!("{}", addr), endpoints.clone(), content_fetcher.clone()) + api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone()) ); special }); @@ -226,7 +237,7 @@ impl Server { try!(hyper::Server::http(addr)) .handle(move |ctrl| router::Router::new( ctrl, - apps::main_page(), + signer_port.clone(), content_fetcher.clone(), endpoints.clone(), special.clone(), @@ -290,6 +301,10 @@ pub fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } +fn signer_address(port: u16) -> String { + format!("127.0.0.1:{}", port) +} + #[cfg(test)] mod util_tests { use super::Server; @@ -309,4 +324,17 @@ mod util_tests { assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()])); assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()])); } + + #[test] + fn should_return_cors_domains() { + // given + + // when + let none = Server::cors_domains(None); + let some = Server::cors_domains(Some(18180)); + + // then + assert_eq!(none, Vec::::new()); + assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]); + } } diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index e702fe4d5..5390f5aac 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -26,14 +26,16 @@ pub struct LocalPageEndpoint { path: PathBuf, mime: Option, info: Option, + embeddable_at: Option, } impl LocalPageEndpoint { - pub fn new(path: PathBuf, info: EndpointInfo) -> Self { + pub fn new(path: PathBuf, info: EndpointInfo, embeddable_at: Option) -> Self { LocalPageEndpoint { path: path, mime: None, info: Some(info), + embeddable_at: embeddable_at, } } @@ -42,6 +44,7 @@ impl LocalPageEndpoint { path: path, mime: Some(mime), info: None, + embeddable_at: None, } } @@ -62,7 +65,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: None, + safe_to_embed_at_port: self.embeddable_at, }) } else { Box::new(handler::PageHandler { @@ -70,7 +73,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: None, + safe_to_embed_at_port: self.embeddable_at, }) } } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index cd225330a..2d7c4e3ce 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -18,30 +18,45 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; -use apps::DAPPS_DOMAIN; +use apps::{HOME_PAGE, DAPPS_DOMAIN}; +use signer_address; -pub struct ProxyPac; +pub struct ProxyPac { + signer_port: Option, +} impl ProxyPac { - pub fn boxed() -> Box { - Box::new(ProxyPac) + pub fn boxed(signer_port: Option) -> Box { + Box::new(ProxyPac { + signer_port: signer_port + }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { + let signer = self.signer_port + .map(signer_address) + .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); + let content = format!( r#" function FindProxyForURL(url, host) {{ - if (shExpMatch(host, "*{0}")) + if (shExpMatch(host, "{0}{1}")) {{ - return "PROXY {1}:{2}"; + return "PROXY {4}"; + }} + + if (shExpMatch(host, "*{1}")) + {{ + return "PROXY {2}:{3}"; }} return "DIRECT"; }} "#, - DAPPS_DOMAIN, path.host, path.port); + HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer); + Box::new(ContentHandler::ok(content, mime!(Application/Javascript))) } } diff --git a/dapps/src/router/host_validation.rs b/dapps/src/router/host_validation.rs index 5be30ef8b..2b7e6c9e4 100644 --- a/dapps/src/router/host_validation.rs +++ b/dapps/src/router/host_validation.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . -use DAPPS_DOMAIN; +use apps::DAPPS_DOMAIN; use hyper::{server, header, StatusCode}; use hyper::net::HttpStream; diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index 2748b5d24..ff60df996 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -20,13 +20,13 @@ pub mod auth; mod host_validation; -use DAPPS_DOMAIN; +use signer_address; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode}; use hyper::net::HttpStream; -use apps; +use apps::{self, DAPPS_DOMAIN}; use apps::fetcher::ContentFetcher; use endpoint::{Endpoint, Endpoints, EndpointPath}; use handlers::{Redirection, extract_url, ContentHandler}; @@ -43,7 +43,7 @@ pub enum SpecialEndpoint { pub struct Router { control: Option, - main_page: &'static str, + signer_port: Option, endpoints: Arc, fetch: Arc, special: Arc>>, @@ -61,57 +61,78 @@ impl server::Handler for Router { let endpoint = extract_endpoint(&url); let is_utils = endpoint.1 == SpecialEndpoint::Utils; + trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); + // Validate Host header if let Some(ref hosts) = self.allowed_hosts { + trace!(target: "dapps", "Validating host headers against: {:?}", hosts); let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect()); if !is_valid { + debug!(target: "dapps", "Rejecting invalid host header."); self.handler = host_validation::host_invalid_response(); return self.handler.on_request(req); } } + trace!(target: "dapps", "Checking authorization."); // Check authorization let auth = self.authorization.is_authorized(&req); if let Authorized::No(handler) = auth { + debug!(target: "dapps", "Authorization denied."); self.handler = handler; return self.handler.on_request(req); } let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed"); + debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint); self.handler = match endpoint { // First check special endpoints (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) => { + trace!(target: "dapps", "Resolving to local/builtin dapp."); self.endpoints.get(&path.app_id) .expect("special 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) => { + trace!(target: "dapps", "Resolving to fetchable content."); self.fetch.to_async_handler(path.clone(), control) }, // 404 for non-existent content - (Some(ref path), _) if *req.method() == hyper::method::Method::Get => { - let address = apps::redirection_address(path.using_dapps_domains, self.main_page); + (Some(_), _) if *req.method() == hyper::method::Method::Get => { + trace!(target: "dapps", "Resolving to 404."); Box::new(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested content was not found.", - Some(&format!("Go back to the Home Page.", address)) + None, )) }, - // Redirect any GET request to home. + // Redirect any other GET request to signer. _ if *req.method() == hyper::method::Method::Get => { - let address = apps::redirection_address(false, self.main_page); - Redirection::boxed(address.as_str()) + if let Some(port) = self.signer_port { + trace!(target: "dapps", "Redirecting to signer interface."); + Redirection::boxed(&format!("http://{}", signer_address(port))) + } else { + trace!(target: "dapps", "Signer disabled, returning 404."); + Box::new(ContentHandler::error( + StatusCode::NotFound, + "404 Not Found", + "Your homepage is not available when Trusted Signer is disabled.", + Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), + )) + } }, // RPC by default _ => { + trace!(target: "dapps", "Resolving to RPC call."); self.special.get(&SpecialEndpoint::Rpc) .expect("RPC endpoint always stored; qed") .to_async_handler(EndpointPath::default(), control) @@ -141,7 +162,7 @@ impl server::Handler for Router { impl Router { pub fn new( control: Control, - main_page: &'static str, + signer_port: Option, content_fetcher: Arc, endpoints: Arc, special: Arc>>, @@ -154,7 +175,7 @@ impl Router { .to_handler(EndpointPath::default()); Router { control: Some(control), - main_page: main_page, + signer_port: signer_port, endpoints: endpoints, fetch: content_fetcher, special: special, diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index 649d283ce..625bfc269 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -24,7 +24,7 @@ pub fn rpc(handler: Arc, panic_handler: Arc Box::new(RpcEndpoint { handler: handler, panic_handler: panic_handler, - cors_domain: Some(vec![AccessControlAllowOrigin::Null]), + cors_domain: None, // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. allowed_hosts: None, }) diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index c0e3bb93b..ea4c08c60 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -34,7 +34,7 @@ fn should_return_error() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#)); assert_security_headers(&response.headers); } @@ -57,8 +57,8 @@ fn should_serve_apps() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); - assert!(response.body.contains("Parity Home Screen"), response.body); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); + assert!(response.body.contains("Parity UI"), response.body); assert_security_headers(&response.headers); } @@ -80,7 +80,7 @@ fn should_handle_ping() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, "0\n\n".to_owned()); assert_security_headers(&response.headers); } @@ -107,3 +107,54 @@ fn should_try_to_resolve_dapp() { assert_security_headers(&response.headers); } +#[test] +fn should_return_signer_port_cors_headers() { + // given + let server = serve(); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://127.0.0.1:18180\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"), + "CORS header for signer missing: {:?}", + response.headers + ); +} + +#[test] +fn should_return_signer_port_cors_headers_for_home_parity() { + // given + let server = serve(); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"), + "CORS header for home.parity missing: {:?}", + response.headers + ); +} diff --git a/dapps/src/tests/authorization.rs b/dapps/src/tests/authorization.rs index 808214a55..86fe4d207 100644 --- a/dapps/src/tests/authorization.rs +++ b/dapps/src/tests/authorization.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_auth, request, assert_security_headers}; +use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed}; #[test] fn should_require_authorization() { @@ -66,7 +66,7 @@ fn should_allow_on_valid_auth() { // when let response = request(server, "\ - GET /home/ HTTP/1.1\r\n\ + GET /ui/ HTTP/1.1\r\n\ Host: 127.0.0.1:8080\r\n\ Connection: close\r\n\ Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n @@ -76,5 +76,5 @@ fn should_allow_on_valid_auth() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_security_headers(&response.headers); + assert_security_headers_for_embed(&response.headers); } diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index 9f02e3385..1cabca5cb 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_registrar, request, assert_security_headers}; +use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers}; #[test] fn should_resolve_dapp() { @@ -37,3 +37,31 @@ fn should_resolve_dapp() { assert_security_headers(&response.headers); } +#[test] +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, + "\ + GET / HTTP/1.1\r\n\ + Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 503 Service Unavailable".to_owned()); + assert_eq!(registrar.calls.lock().len(), 4); + assert_security_headers(&response.headers); +} diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index c1837d5a2..1fa2e777a 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -18,6 +18,7 @@ use std::env; use std::str; use std::sync::Arc; use rustc_serialize::hex::FromHex; +use env_logger::LogBuilder; use ServerBuilder; use Server; @@ -27,6 +28,7 @@ use devtools::http_client; const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; +const SIGNER_PORT: u16 = 18180; pub struct FakeRegistrar { pub calls: Arc>>, @@ -58,11 +60,23 @@ impl ContractClient for FakeRegistrar { } } -pub fn init_server(hosts: Option>) -> (Server, Arc) { +fn init_logger() { + // Initialize logger + if let Ok(log) = env::var("RUST_LOG") { + let mut builder = LogBuilder::new(); + builder.parse(&log); + builder.init().expect("Logger is initialized only once."); + } +} + +pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc) { + 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 builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); + 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_port(Some(SIGNER_PORT)); ( builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), registrar, @@ -70,23 +84,29 @@ pub fn init_server(hosts: Option>) -> (Server, Arc) { } pub fn serve_with_auth(user: &str, pass: &str) -> Server { + 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 builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); + let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); + builder.with_signer_port(Some(SIGNER_PORT)); builder.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).0 + init_server(hosts, false).0 } pub fn serve_with_registrar() -> (Server, Arc) { - init_server(None) + init_server(None, false) +} + +pub fn serve_with_registrar_and_sync() -> (Server, Arc) { + init_server(None, true) } pub fn serve() -> Server { - init_server(None).0 + init_server(None, false).0 } pub fn request(server: Server, request: &str) -> http_client::Response { @@ -94,5 +114,8 @@ pub fn request(server: Server, request: &str) -> http_client::Response { } pub fn assert_security_headers(headers: &[String]) { - http_client::assert_security_headers_present(headers) + http_client::assert_security_headers_present(headers, None) +} +pub fn assert_security_headers_for_embed(headers: &[String]) { + http_client::assert_security_headers_present(headers, Some(SIGNER_PORT)) } diff --git a/dapps/src/tests/redirection.rs b/dapps/src/tests/redirection.rs index 92fc6ce80..aee1cb964 100644 --- a/dapps/src/tests/redirection.rs +++ b/dapps/src/tests/redirection.rs @@ -33,7 +33,7 @@ fn should_redirect_to_home() { // then assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Location: /home/"); + assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180"); } #[test] @@ -53,7 +53,7 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() { // then assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Location: /home/"); + assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180"); } #[test] @@ -73,7 +73,6 @@ fn should_display_404_on_invalid_dapp() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert!(response.body.contains("href=\"/home/")); assert_security_headers(&response.headers); } @@ -94,7 +93,6 @@ fn should_display_404_on_invalid_dapp_with_domain() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert!(response.body.contains("href=\"http://home.parity/")); assert_security_headers(&response.headers); } @@ -161,7 +159,7 @@ fn should_serve_proxy_pac() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.body, "86\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); + assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); assert_security_headers(&response.headers); } diff --git a/dapps/src/tests/validation.rs b/dapps/src/tests/validation.rs index c39350cce..ae02a6c2c 100644 --- a/dapps/src/tests/validation.rs +++ b/dapps/src/tests/validation.rs @@ -45,7 +45,7 @@ fn should_allow_valid_host() { // when let response = request(server, "\ - GET /home/ HTTP/1.1\r\n\ + GET /ui/ HTTP/1.1\r\n\ Host: localhost:8080\r\n\ Connection: close\r\n\ \r\n\ @@ -66,7 +66,7 @@ fn should_serve_dapps_domains() { let response = request(server, "\ GET / HTTP/1.1\r\n\ - Host: home.parity\r\n\ + Host: ui.parity\r\n\ Connection: close\r\n\ \r\n\ {} @@ -98,3 +98,30 @@ fn should_allow_parity_utils_even_on_invalid_domain() { assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); } +#[test] +fn should_not_return_cors_headers_for_rpc() { + // given + let server = serve_hosts(Some(vec!["localhost:8080".into()])); + + // when + let response = request(server, + "\ + POST /rpc HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: null\r\n\ + Content-Type: application/json\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + !response.headers_raw.contains("Access-Control-Allow-Origin"), + "CORS headers were not expected: {:?}", + response.headers + ); +} + diff --git a/dapps/ui/Cargo.toml b/dapps/ui/Cargo.toml index ff88d0f80..e71782dfa 100644 --- a/dapps/ui/Cargo.toml +++ b/dapps/ui/Cargo.toml @@ -1,19 +1,18 @@ [package] -description = "Parity built-in dapps." +description = "Ethcore Parity UI" +homepage = "http://ethcore.io" +license = "GPL-3.0" name = "parity-ui" version = "1.4.0" -license = "GPL-3.0" authors = ["Ethcore "] -build = "build.rs" - -[features] -default = ["with-syntex"] -use-precompiled-js = ["parity-dapps-glue/use-precompiled-js"] -with-syntex = ["parity-dapps-glue/with-syntex"] [build-dependencies] -parity-dapps-glue = { path = "../js-glue" } +rustc_version = "0.1" [dependencies] -parity-dapps-glue = { path = "../js-glue" } +parity-ui-dev = { path = "../../js", optional = true } +parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true } +[features] +no-precompiled-js = ["parity-ui-dev"] +use-precompiled-js = ["parity-ui-precompiled"] diff --git a/dapps/ui/src/lib.rs b/dapps/ui/src/lib.rs index 25d336fab..6a69a503d 100644 --- a/dapps/ui/src/lib.rs +++ b/dapps/ui/src/lib.rs @@ -14,9 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -#[cfg(feature = "with-syntex")] -include!(concat!(env!("OUT_DIR"), "/lib.rs")); -#[cfg(not(feature = "with-syntex"))] -include!("lib.rs.in"); +#[cfg(feature = "parity-ui-dev")] +mod inner { + extern crate parity_ui_dev; + pub use self::parity_ui_dev::*; +} + +#[cfg(feature = "parity-ui-precompiled")] +mod inner { + extern crate parity_ui_precompiled; + + pub use self::parity_ui_precompiled::*; +} + + +pub use self::inner::*; diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index 3f0bd463e..acba2989e 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -65,11 +65,18 @@ pub fn request(address: &SocketAddr, request: &str) -> Response { } /// Check if all required security headers are present -pub fn assert_security_headers_present(headers: &[String]) { - assert!( - headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), - "X-Frame-Options missing: {:?}", headers - ); +pub fn assert_security_headers_present(headers: &[String], port: Option) { + if let Some(port) = port { + assert!( + headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(), + "X-Frame-Options: ALLOW-FROM missing: {:?}", headers + ); + } else { + assert!( + headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), + "X-Frame-Options: SAMEORIGIN missing: {:?}", headers + ); + } assert!( headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(), "X-XSS-Protection missing: {:?}", headers diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 2ad0f0bec..d5f57defd 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -164,8 +164,7 @@ "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", - "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@zero.parity.io:30303", - "enode://cc92c4c40d612a10c877ca023ef0496c843fbc92b6c6c0d55ce0b863d51d821c4bd70daebb54324a6086374e6dc05708fed39862b275f169cb678e655da9d07d@136.243.154.246:30303" + "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/src/account_db.rs b/ethcore/src/account_db.rs index 2d00f8ed5..0761b7fba 100644 --- a/ethcore/src/account_db.rs +++ b/ethcore/src/account_db.rs @@ -96,9 +96,9 @@ impl<'db> HashDB for AccountDB<'db>{ unimplemented!() } - fn get(&self, key: &H256) -> Option<&[u8]> { + fn get(&self, key: &H256) -> Option { if key == &SHA3_NULL_RLP { - return Some(&NULL_RLP_STATIC); + return Some(DBValue::from_slice(&NULL_RLP_STATIC)); } self.db.get(&combine_key(&self.address_hash, key)) } @@ -114,7 +114,7 @@ impl<'db> HashDB for AccountDB<'db>{ unimplemented!() } - fn emplace(&mut self, _key: H256, _value: Bytes) { + fn emplace(&mut self, _key: H256, _value: DBValue) { unimplemented!() } @@ -122,7 +122,7 @@ impl<'db> HashDB for AccountDB<'db>{ unimplemented!() } - fn get_aux(&self, hash: &[u8]) -> Option> { + fn get_aux(&self, hash: &[u8]) -> Option { self.db.get_aux(hash) } } @@ -158,9 +158,9 @@ impl<'db> HashDB for AccountDBMut<'db>{ unimplemented!() } - fn get(&self, key: &H256) -> Option<&[u8]> { + fn get(&self, key: &H256) -> Option { if key == &SHA3_NULL_RLP { - return Some(&NULL_RLP_STATIC); + return Some(DBValue::from_slice(&NULL_RLP_STATIC)); } self.db.get(&combine_key(&self.address_hash, key)) } @@ -178,16 +178,16 @@ impl<'db> HashDB for AccountDBMut<'db>{ } let k = value.sha3(); let ak = combine_key(&self.address_hash, &k); - self.db.emplace(ak, value.to_vec()); + self.db.emplace(ak, DBValue::from_slice(value)); k } - fn emplace(&mut self, key: H256, value: Bytes) { + fn emplace(&mut self, key: H256, value: DBValue) { if key == SHA3_NULL_RLP { return; } let key = combine_key(&self.address_hash, &key); - self.db.emplace(key, value.to_vec()) + self.db.emplace(key, value) } fn remove(&mut self, key: &H256) { @@ -202,7 +202,7 @@ impl<'db> HashDB for AccountDBMut<'db>{ self.db.insert_aux(hash, value); } - fn get_aux(&self, hash: &[u8]) -> Option> { + fn get_aux(&self, hash: &[u8]) -> Option { self.db.get_aux(hash) } @@ -218,9 +218,9 @@ impl<'db> HashDB for Wrapping<'db> { unimplemented!() } - fn get(&self, key: &H256) -> Option<&[u8]> { + fn get(&self, key: &H256) -> Option { if key == &SHA3_NULL_RLP { - return Some(&NULL_RLP_STATIC); + return Some(DBValue::from_slice(&NULL_RLP_STATIC)); } self.0.get(key) } @@ -236,7 +236,7 @@ impl<'db> HashDB for Wrapping<'db> { unimplemented!() } - fn emplace(&mut self, _key: H256, _value: Bytes) { + fn emplace(&mut self, _key: H256, _value: DBValue) { unimplemented!() } @@ -252,9 +252,9 @@ impl<'db> HashDB for WrappingMut<'db>{ unimplemented!() } - fn get(&self, key: &H256) -> Option<&[u8]> { + fn get(&self, key: &H256) -> Option { if key == &SHA3_NULL_RLP { - return Some(&NULL_RLP_STATIC); + return Some(DBValue::from_slice(&NULL_RLP_STATIC)); } self.0.get(key) } @@ -273,7 +273,7 @@ impl<'db> HashDB for WrappingMut<'db>{ self.0.insert(value) } - fn emplace(&mut self, key: H256, value: Bytes) { + fn emplace(&mut self, key: H256, value: DBValue) { if key == SHA3_NULL_RLP { return; } @@ -286,4 +286,4 @@ impl<'db> HashDB for WrappingMut<'db>{ } self.0.remove(key) } -} \ No newline at end of file +} diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 5c1398e02..7a4958ddc 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -265,6 +265,20 @@ impl AccountProvider { Ok(()) } + /// Returns `true` if the password for `account` is `password`. `false` if not. + pub fn test_password(&self, account: &Address, password: String) -> Result { + match self.sstore.sign(&account, &password, &Default::default()) { + Ok(_) => Ok(true), + Err(SSError::InvalidPassword) => Ok(false), + Err(e) => Err(Error::SStore(e)), + } + } + + /// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given. + pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { + self.sstore.change_password(&account, &password, &new_password).map_err(Error::SStore) + } + /// Helper method used for unlocking accounts. fn unlock_account(&self, account: Address, password: String, unlock: Unlock) -> Result<(), Error> { // verify password by signing dump message diff --git a/ethcore/src/action_params.rs b/ethcore/src/action_params.rs index 46c159269..8b863c625 100644 --- a/ethcore/src/action_params.rs +++ b/ethcore/src/action_params.rs @@ -15,10 +15,14 @@ // along with Parity. If not, see . //! Evm input params. -use common::*; +use util::{Address, Bytes, Uint, U256}; +use util::hash::{H256, FixedHash}; +use util::sha3::{Hashable, SHA3_EMPTY}; use ethjson; use types::executed::CallType; +use std::sync::Arc; + /// Transaction value #[derive(Clone, Debug)] pub enum ActionValue { diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5f6515c0d..79f009fd1 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -16,7 +16,7 @@ //! Ethcore basic typenames. -use util::*; +use util::hash::H2048; /// Type for a 2048-bit log-bloom, as used by our blocks. pub type LogBloom = H2048; diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 6645bf492..5d7305b91 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -587,10 +587,17 @@ pub fn enact_verified( mod tests { use tests::helpers::*; use super::*; - use common::*; use engines::Engine; + use env_info::LastHashes; + use error::Error; + use header::Header; use factory::Factories; use state_db::StateDB; + use views::BlockView; + use util::Address; + use util::hash::FixedHash; + + use std::sync::Arc; /// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header #[cfg_attr(feature="dev", allow(too_many_arguments))] diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 799fa383d..de0c72a38 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -394,6 +394,8 @@ impl BlockProvider for BlockChain { } } +/// An iterator which walks the blockchain towards the genesis. +#[derive(Clone)] pub struct AncestryIter<'a> { current: H256, chain: &'a BlockChain, @@ -403,11 +405,10 @@ impl<'a> Iterator for AncestryIter<'a> { type Item = H256; fn next(&mut self) -> Option { if self.current.is_zero() { - Option::None + None } else { - let mut n = self.chain.block_details(&self.current).unwrap().parent; - mem::swap(&mut self.current, &mut n); - Some(n) + self.chain.block_details(&self.current) + .map(|details| mem::replace(&mut self.current, details.parent)) } } } @@ -999,17 +1000,29 @@ impl BlockChain { if !self.is_known(parent) { return None; } let mut excluded = HashSet::new(); - for a in self.ancestry_iter(parent.clone()).unwrap().take(uncle_generations) { - excluded.extend(self.uncle_hashes(&a).unwrap().into_iter()); - excluded.insert(a); + let ancestry = match self.ancestry_iter(parent.clone()) { + Some(iter) => iter, + None => return None, + }; + + for a in ancestry.clone().take(uncle_generations) { + if let Some(uncles) = self.uncle_hashes(&a) { + excluded.extend(uncles); + excluded.insert(a); + } else { + break + } } let mut ret = Vec::new(); - for a in self.ancestry_iter(parent.clone()).unwrap().skip(1).take(uncle_generations) { - ret.extend(self.block_details(&a).unwrap().children.iter() - .filter(|h| !excluded.contains(h)) - ); + for a in ancestry.skip(1).take(uncle_generations) { + if let Some(details) = self.block_details(&a) { + ret.extend(details.children.iter().filter(|h| !excluded.contains(h))) + } else { + break + } } + Some(ret) } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 9abe93beb..c330d53c7 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -204,7 +204,7 @@ impl Client { let engine = spec.engine.clone(); - let block_queue = BlockQueue::new(config.queue.clone(), engine.clone(), message_channel.clone()); + let block_queue = BlockQueue::new(config.queue.clone(), engine.clone(), message_channel.clone(), config.verifier_type.verifying_seal()); let panic_handler = PanicHandler::new_in_arc(); panic_handler.forward_from(&block_queue); diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index b87da437f..850e5c938 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -117,6 +117,8 @@ pub struct ClientConfig { pub jump_table_size: usize, /// State pruning history size. pub history: u64, + /// Check seal valididity on block import + pub check_seal: bool, } #[cfg(test)] diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 1e8aa9d72..3898ab6cd 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -37,6 +37,7 @@ pub use block_import_error::BlockImportError; pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportError; pub use self::traits::{BlockChainClient, MiningBlockChainClient}; +pub use verification::VerifierType; /// IPC interfaces #[cfg(feature="ipc")] diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index a8face23b..0bc9e70fa 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -194,15 +194,20 @@ pub trait BlockChainClient : Sync + Send { fn gas_price_statistics(&self, sample_size: usize, distribution_size: usize) -> Result, ()> { let mut h = self.chain_info().best_block_hash; let mut corpus = Vec::new(); - 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 header = block.header_view(); - if header.number() == 0 { - break; + 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 header = block.header_view(); + if header.number() == 0 { + if corpus.is_empty() { + corpus.push(20_000_000_000u64.into()); // we have literally no information - it' as good a number as any. + } + break; + } + block.transaction_views().iter().foreach(|t| corpus.push(t.gas_price())); + h = header.parent_hash().clone(); } - block.transaction_views().iter().foreach(|t| corpus.push(t.gas_price())); - h = header.parent_hash().clone(); } corpus.sort(); let n = corpus.len(); diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 0e91ee1a3..10325439e 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -19,17 +19,22 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::Weak; use std::time::{UNIX_EPOCH, Duration}; -use common::*; +use util::*; use ethkey::verify_address; use rlp::{UntrustedRlp, View, encode, decode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::Engine; +use header::Header; +use error::{Error, BlockError}; use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; use service::ClientIoMessage; +use transaction::SignedTransaction; +use env_info::EnvInfo; +use builtin::Builtin; /// `AuthorityRound` params. #[derive(Debug, PartialEq)] diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 9c8cbd24b..8656fa944 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -16,14 +16,20 @@ //! A blockchain engine that supports a basic, non-BFT proof-of-authority. -use common::*; use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; +use builtin::Builtin; use spec::CommonParams; use engines::Engine; +use env_info::EnvInfo; +use error::{BlockError, Error}; use evm::Schedule; use ethjson; +use header::Header; +use transaction::SignedTransaction; + +use util::*; /// `BasicAuthority` params. #[derive(Debug, PartialEq)] @@ -177,10 +183,13 @@ impl Engine for BasicAuthority { #[cfg(test)] mod tests { - use common::*; + use util::*; use block::*; + use env_info::EnvInfo; + use error::{BlockError, Error}; use tests::helpers::*; use account_provider::AccountProvider; + use header::Header; use spec::Spec; /// Create a new test chain spec with `BasicAuthority` consensus engine. diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 174a80ea8..acead19b4 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -18,11 +18,11 @@ use std::collections::BTreeMap; use util::Address; use builtin::Builtin; use engines::Engine; +use env_info::EnvInfo; use spec::CommonParams; use evm::Schedule; -use env_info::EnvInfo; use block::ExecutedBlock; -use common::Bytes; +use util::Bytes; use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. @@ -67,10 +67,11 @@ impl Engine for InstantSeal { #[cfg(test)] mod tests { - use common::*; + use util::*; use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; + use header::Header; use block::*; #[test] diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 8e6344f46..b8797305b 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -26,13 +26,18 @@ pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::authority_round::AuthorityRound; -use common::*; +use util::*; use account_provider::AccountProvider; use block::ExecutedBlock; +use builtin::Builtin; +use env_info::EnvInfo; +use error::Error; use spec::CommonParams; use evm::Schedule; use io::IoChannel; use service::ClientIoMessage; +use header::Header; +use transaction::SignedTransaction; /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// Provides hooks into each of the major parts of block import. diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index e0c18292b..060a20aa2 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -15,9 +15,14 @@ // along with Parity. If not, see . use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager, H256 as EH256}; -use common::*; +use util::*; use block::*; +use builtin::Builtin; +use env_info::EnvInfo; +use error::{BlockError, Error}; +use header::Header; use spec::CommonParams; +use transaction::SignedTransaction; use engines::Engine; use evm::Schedule; use ethjson; @@ -187,8 +192,8 @@ impl Engine for Ethash { // Commit state so that we can actually figure out the state root. if let Err(e) = fields.state.commit() { - warn!("Encountered error on state commit: {}", e); - } + warn!("Encountered error on state commit: {}", e); + } } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -371,9 +376,12 @@ impl Header { #[cfg(test)] mod tests { - use common::*; + use util::*; use block::*; use tests::helpers::*; + use env_info::EnvInfo; + use error::{BlockError, Error}; + use header::Header; use super::super::new_morden; use super::Ethash; use rlp; diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 219b3bf5c..d8299324d 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -65,10 +65,11 @@ pub fn new_morden() -> Spec { load(include_bytes!("../../res/ethereum/morden.jso #[cfg(test)] mod tests { - use common::*; + use util::*; use state::*; use super::*; use tests::helpers::*; + use views::BlockView; #[test] fn ensure_db_good() { diff --git a/ethcore/src/evm/benches/mod.rs b/ethcore/src/evm/benches/mod.rs index 8ef730d88..cbed7d881 100644 --- a/ethcore/src/evm/benches/mod.rs +++ b/ethcore/src/evm/benches/mod.rs @@ -24,7 +24,8 @@ extern crate test; use self::test::{Bencher, black_box}; -use common::*; +use util::*; +use action_params::ActionParams; use evm::{self, Factory, VMType}; use evm::tests::FakeExt; diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index d4c329be0..3fde3f664 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use common::*; +use util::*; use super::u256_to_address; use evm::{self, CostType}; use evm::instructions::{self, Instruction, InstructionInfo}; @@ -64,14 +64,14 @@ impl Gasometer { Some(cap_divisor) if self.current_gas >= needed => { let gas_remaining = self.current_gas - needed; let max_gas_provided = gas_remaining - gas_remaining / Gas::from(cap_divisor); - if let Some(Ok(r)) = requested { + if let Some(Ok(r)) = requested { Ok(min(r, max_gas_provided)) } else { Ok(max_gas_provided) } }, _ => { - if let Some(r) = requested { + if let Some(r) = requested { r } else if self.current_gas >= needed { Ok(self.current_gas - needed) @@ -84,10 +84,10 @@ impl Gasometer { #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] /// Determine how much gas is used by the given instruction, given the machine's state. - /// + /// /// We guarantee that the final element of the returned tuple (`provided`) will be `Some` /// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case, - /// it will be the amount of gas that the current context provides to the child context. + /// it will be the amount of gas that the current context provides to the child context. pub fn get_gas_cost_mem( &mut self, ext: &evm::Ext, @@ -183,7 +183,7 @@ impl Gasometer { gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into())); }; - // TODO: refactor to avoid duplicate calculation here and later on. + // TODO: refactor to avoid duplicate calculation here and later on. let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); let requested = Gas::from_u256(*stack.peek(0)); @@ -199,7 +199,7 @@ impl Gasometer { try!(mem_needed(stack.peek(2), stack.peek(3))) ); - // TODO: refactor to avoid duplicate calculation here and later on. + // TODO: refactor to avoid duplicate calculation here and later on. let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); let requested = Gas::from_u256(*stack.peek(0)); @@ -212,9 +212,9 @@ impl Gasometer { let mut gas = Gas::from(schedule.create_gas); let mem = try!(mem_needed(stack.peek(1), stack.peek(2))); - // TODO: refactor to avoid duplicate calculation here and later on. + // TODO: refactor to avoid duplicate calculation here and later on. let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); - let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); + let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); let provided = try!(self.gas_provided(schedule, cost_so_far, None)); gas = overflowing!(gas.overflow_add(provided)); diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index f9d386a21..a39e09e79 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -29,12 +29,14 @@ use self::memory::Memory; pub use self::shared_cache::SharedCache; use std::marker::PhantomData; -use common::*; +use action_params::{ActionParams, ActionValue}; use types::executed::CallType; -use super::instructions::{self, Instruction, InstructionInfo}; +use evm::instructions::{self, Instruction, InstructionInfo}; use evm::{self, MessageCallResult, ContractCreateResult, GasLeft, CostType}; use bit_set::BitSet; +use util::*; + type CodePosition = usize; type ProgramCounter = usize; diff --git a/ethcore/src/evm/jit.rs b/ethcore/src/evm/jit.rs index 16a7b29e5..6fa617396 100644 --- a/ethcore/src/evm/jit.rs +++ b/ethcore/src/evm/jit.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . //! Just in time compiler execution environment. -use common::*; +use util::*; use evmjit; use evm::{self, GasLeft}; use types::executed::CallType; diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index f685e279d..ba002d649 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use common::*; +use util::*; +use action_params::{ActionParams, ActionValue}; +use env_info::EnvInfo; use types::executed::CallType; use evm::{self, Ext, Schedule, Factory, GasLeft, VMType, ContractCreateResult, MessageCallResult}; use std::fmt::Debug; diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 5856086c6..f05cc4fd8 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -15,13 +15,17 @@ // along with Parity. If not, see . //! Transaction Execution environment. -use common::*; +use util::*; +use action_params::{ActionParams, ActionValue}; use state::{State, Substate}; use engines::Engine; use types::executed::CallType; +use env_info::EnvInfo; +use error::ExecutionError; use evm::{self, Ext, Factory, Finalize}; use externalities::*; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; +use transaction::{Action, SignedTransaction}; use crossbeam; pub use types::executed::{Executed, ExecutionResult}; @@ -250,7 +254,7 @@ impl<'a> Executive<'a> { vm_tracer: &mut V ) -> evm::Result where T: Tracer, V: VMTracer { // backup used in case of running out of gas - self.state.snapshot(); + self.state.checkpoint(); // at first, transfer value to destination if let ActionValue::Transfer(val) = params.value { @@ -269,7 +273,7 @@ impl<'a> Executive<'a> { let cost = self.engine.cost_of_builtin(¶ms.code_address, data); if cost <= params.gas { self.engine.execute_builtin(¶ms.code_address, data, &mut output); - self.state.discard_snapshot(); + self.state.discard_checkpoint(); // trace only top level calls to builtins to avoid DDoS attacks if self.depth == 0 { @@ -289,7 +293,7 @@ impl<'a> Executive<'a> { Ok(params.gas - cost) } else { // just drain the whole gas - self.state.revert_to_snapshot(); + self.state.revert_to_checkpoint(); tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into()); @@ -335,7 +339,7 @@ impl<'a> Executive<'a> { res } else { // otherwise it's just a basic transaction, only do tracing, if necessary. - self.state.discard_snapshot(); + self.state.discard_checkpoint(); tracer.trace_call(trace_info, U256::zero(), trace_output, vec![]); Ok(params.gas) @@ -354,7 +358,7 @@ impl<'a> Executive<'a> { vm_tracer: &mut V ) -> evm::Result where T: Tracer, V: VMTracer { // backup used in case of running out of gas - self.state.snapshot(); + self.state.checkpoint(); // part of substate that may be reverted let mut unconfirmed_substate = Substate::new(); @@ -485,10 +489,10 @@ impl<'a> Executive<'a> { | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::OutOfStack {..}) => { - self.state.revert_to_snapshot(); + self.state.revert_to_checkpoint(); }, Ok(_) | Err(evm::Error::Internal) => { - self.state.discard_snapshot(); + self.state.discard_checkpoint(); substate.accrue(un_substate); } } @@ -500,13 +504,18 @@ impl<'a> Executive<'a> { mod tests { use ethkey::{Generator, Random}; use super::*; - use common::*; + use util::*; + use action_params::{ActionParams, ActionValue}; + use env_info::EnvInfo; use evm::{Factory, VMType}; + use error::ExecutionError; use state::Substate; use tests::helpers::*; use trace::trace; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer}; use trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, VMTracer, NoopVMTracer, ExecutiveVMTracer}; + use transaction::{Action, Transaction}; + use types::executed::CallType; #[test] diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 67c04aefb..bbe81a511 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -15,9 +15,11 @@ // along with Parity. If not, see . //! Transaction Execution environment. -use common::*; +use util::*; +use action_params::{ActionParams, ActionValue}; use state::{State, Substate}; use engines::Engine; +use env_info::EnvInfo; use executive::*; use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult, Factory}; use types::executed::CallType; @@ -253,6 +255,8 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT } fn log(&mut self, topics: Vec, data: &[u8]) { + use log_entry::LogEntry; + let address = self.origin_info.address.clone(); self.substate.logs.push(LogEntry { address: address, @@ -303,8 +307,9 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT #[cfg(test)] mod tests { - use common::*; + use util::*; use engines::Engine; + use env_info::EnvInfo; use evm::Ext; use state::{State, Substate}; use tests::helpers::*; diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 50f1f573c..228933570 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -17,7 +17,7 @@ //! Block header. use util::*; -use basic_types::*; +use basic_types::{LogBloom, Seal, ZERO_LOGBLOOM}; use time::get_time; use rlp::*; diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 8979b8253..1d4faec62 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -15,9 +15,11 @@ // along with Parity. If not, see . use super::test_common::*; +use action_params::ActionParams; use state::{State, Substate}; use executive::*; use engines::Engine; +use env_info::EnvInfo; use evm; use evm::{Schedule, Ext, Factory, Finalize, VMType, ContractCreateResult, MessageCallResult}; use externalities::*; diff --git a/ethcore/src/json_tests/homestead_chain.rs b/ethcore/src/json_tests/homestead_chain.rs index d92b8a7f1..37a9d0a21 100644 --- a/ethcore/src/json_tests/homestead_chain.rs +++ b/ethcore/src/json_tests/homestead_chain.rs @@ -37,6 +37,5 @@ declare_test!{BlockchainTests_Homestead_bcUncleTest, "BlockchainTests/Homestead/ declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"} declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"} declare_test!{BlockchainTests_Homestead_bcShanghaiLove, "BlockchainTests/Homestead/bcShanghaiLove"} -// TODO [ToDr] uncomment as soon as eip150 tests are merged to develop branch of ethereum/tests -// declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"} +declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"} declare_test!{BlockchainTests_Homestead_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"} diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 16e532401..c3e74af5d 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -17,6 +17,7 @@ use super::test_common::*; use tests::helpers::*; use pod_state::{self, PodState}; +use log_entry::LogEntry; use ethereum; use ethjson; diff --git a/ethcore/src/json_tests/test_common.rs b/ethcore/src/json_tests/test_common.rs index 7f7051bf0..e77b3df93 100644 --- a/ethcore/src/json_tests/test_common.rs +++ b/ethcore/src/json_tests/test_common.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -pub use common::*; +pub use util::*; macro_rules! test { ($name: expr) => { diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index a06e3b5dc..50061cbfd 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -18,6 +18,7 @@ use super::test_common::*; use evm; use ethjson; use rlp::{UntrustedRlp, View}; +use transaction::{Action, SignedTransaction}; fn do_json_test(json_data: &[u8]) -> Vec { let tests = ethjson::transaction::Test::load(json_data).unwrap(); diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 2e71cbcf4..99ef5209b 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -140,7 +140,6 @@ pub mod db; mod cache_manager; mod blooms; -mod common; mod basic_types; mod env_info; mod pod_account; diff --git a/ethcore/src/migrations/state/v7.rs b/ethcore/src/migrations/state/v7.rs index 9af75a8ed..49df041eb 100644 --- a/ethcore/src/migrations/state/v7.rs +++ b/ethcore/src/migrations/state/v7.rs @@ -154,7 +154,7 @@ impl OverlayRecentV7 { // 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.to_owned(), dest)); + try!(batch.insert(V7_LATEST_ERA_KEY.into(), val.clone().to_vec(), dest)); let mut era = decode::(&val); loop { diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs index a92e03ebc..afee32f94 100644 --- a/ethcore/src/pod_account.rs +++ b/ethcore/src/pod_account.rs @@ -163,7 +163,7 @@ pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option StateDiff { #[cfg(test)] mod test { - use common::*; + use util::*; use types::state_diff::*; use types::account_diff::*; use pod_account::PodAccount; diff --git a/ethcore/src/snapshot/account.rs b/ethcore/src/snapshot/account.rs index 30f2cd956..7e4585365 100644 --- a/ethcore/src/snapshot/account.rs +++ b/ethcore/src/snapshot/account.rs @@ -19,7 +19,7 @@ use account_db::{AccountDB, AccountDBMut}; use snapshot::Error; -use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP}; +use util::{U256, FixedHash, H256, Bytes, HashDB, DBValue, SHA3_EMPTY, SHA3_NULL_RLP}; use util::trie::{TrieDB, Trie}; use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View}; @@ -112,7 +112,7 @@ impl Account { let mut stream = RlpStream::new_list(pairs.len()); for (k, v) in pairs { - stream.begin_list(2).append(&k).append(&v); + stream.begin_list(2).append(&k).append(&&*v); } let pairs_rlp = stream.out(); @@ -130,7 +130,7 @@ impl Account { match acct_db.get(&self.code_hash) { Some(c) => { used_code.insert(self.code_hash.clone()); - account_stream.append(&CodeState::Inline.raw()).append(&c); + account_stream.append(&CodeState::Inline.raw()).append(&&*c); } None => { warn!("code lookup failed during snapshot"); @@ -178,7 +178,7 @@ impl Account { CodeState::Hash => { let code_hash = try!(rlp.val_at(3)); if let Some(code) = code_map.get(&code_hash) { - acct_db.emplace(code_hash.clone(), code.clone()); + acct_db.emplace(code_hash.clone(), DBValue::from_slice(&code)); } (code_hash, None) @@ -226,7 +226,7 @@ mod tests { use snapshot::tests::helpers::fill_storage; use util::sha3::{SHA3_EMPTY, SHA3_NULL_RLP}; - use util::{Address, FixedHash, H256, HashDB}; + use util::{Address, FixedHash, H256, HashDB, DBValue}; use rlp::{UntrustedRlp, View}; use std::collections::{HashSet, HashMap}; @@ -292,7 +292,7 @@ mod tests { { let mut acct_db = AccountDBMut::new(db.as_hashdb_mut(), &addr2); - acct_db.emplace(code_hash.clone(), b"this is definitely code".to_vec()); + acct_db.emplace(code_hash.clone(), DBValue::from_slice(b"this is definitely code")); } let account1 = Account { diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 927c27424..4fa00f771 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -29,8 +29,7 @@ use engines::Engine; use ids::BlockID; use views::BlockView; -use util::{Bytes, Hashable, HashDB, snappy, U256, Uint}; -use util::memorydb::MemoryDB; +use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint}; use util::Mutex; use util::hash::{FixedHash, H256}; use util::journaldb::{self, Algorithm, JournalDB}; @@ -38,6 +37,7 @@ use util::kvdb::Database; use util::trie::{TrieDB, TrieDBMut, Trie, TrieMut}; use util::sha3::SHA3_NULL_RLP; use rlp::{RlpStream, Stream, UntrustedRlp, View}; +use bloom_journal::Bloom; use self::account::Account; use self::block::AbridgedBlock; @@ -46,7 +46,7 @@ use self::io::SnapshotWriter; use super::state_db::StateDB; use super::state::Account as StateAccount; -use crossbeam::{scope, ScopedJoinHandle}; +use crossbeam::scope; use rand::{Rng, OsRng}; pub use self::error::Error; @@ -136,7 +136,7 @@ pub fn take_snapshot( let writer = Mutex::new(writer); let (state_hashes, block_hashes) = try!(scope(|scope| { - let block_guard = scope.spawn(|| chunk_blocks(chain, (number, block_at), &writer, p)); + 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| { @@ -176,10 +176,15 @@ struct BlockChunker<'a> { impl<'a> BlockChunker<'a> { // Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash. // Loops until we reach the first desired block, and writes out the remainder. - fn chunk_all(&mut self, first_hash: H256) -> Result<(), Error> { + fn chunk_all(&mut self) -> Result<(), Error> { let mut loaded_size = 0; + let mut last = self.current_hash; + + let genesis_hash = self.chain.genesis_hash(); + + for _ in 0..SNAPSHOT_BLOCKS { + if self.current_hash == genesis_hash { break } - while self.current_hash != first_hash { let (block, receipts) = try!(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))); @@ -197,21 +202,21 @@ impl<'a> BlockChunker<'a> { // cut off the chunk if too large. - if new_loaded_size > PREFERRED_CHUNK_SIZE { - try!(self.write_chunk()); + if new_loaded_size > PREFERRED_CHUNK_SIZE && self.rlps.len() > 0 { + try!(self.write_chunk(last)); loaded_size = pair.len(); } else { loaded_size = new_loaded_size; } self.rlps.push_front(pair); + + last = self.current_hash; self.current_hash = view.header_view().parent_hash(); } if loaded_size != 0 { - // we don't store the first block, so once we get to this point, - // the "first" block will be first_number + 1. - try!(self.write_chunk()); + try!(self.write_chunk(last)); } Ok(()) @@ -219,23 +224,24 @@ impl<'a> BlockChunker<'a> { // write out the data in the buffers to a chunk on disk // - // we preface each chunk with the parent of the first block's details. - fn write_chunk(&mut self) -> Result<(), Error> { - // since the block we're inspecting now doesn't go into the - // chunk if it's too large, the current hash is the parent hash - // for the first block in that chunk. - let parent_hash = self.current_hash; - + // we preface each chunk with the parent of the first block's details, + // obtained from the details of the last block written. + fn write_chunk(&mut self, last: H256) -> Result<(), Error> { trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len()); - let (parent_number, parent_details) = try!(self.chain.block_number(&parent_hash) - .and_then(|n| self.chain.block_details(&parent_hash).map(|d| (n, d))) - .ok_or(Error::BlockNotFound(parent_hash))); - let parent_total_difficulty = parent_details.total_difficulty; + let (last_header, last_details) = try!(self.chain.block_header(&last) + .and_then(|n| self.chain.block_details(&last).map(|d| (n, d))) + .ok_or(Error::BlockNotFound(last))); + + let parent_number = last_header.number() - 1; + let parent_hash = last_header.parent_hash(); + let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty(); + + trace!(target: "snapshot", "parent last written block: {}", parent_hash); let num_entries = self.rlps.len(); let mut rlp_stream = RlpStream::new_list(3 + num_entries); - rlp_stream.append(&parent_number).append(&parent_hash).append(&parent_total_difficulty); + rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty); for pair in self.rlps.drain(..) { rlp_stream.append_raw(&pair, 1); @@ -264,17 +270,7 @@ impl<'a> BlockChunker<'a> { /// The path parameter is the directory to store the block chunks in. /// This function assumes the directory exists already. /// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis. -pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), writer: &Mutex, progress: &'a Progress) -> Result, Error> { - let (start_number, start_hash) = start_block_info; - - let first_hash = if start_number < SNAPSHOT_BLOCKS { - // use the genesis hash. - chain.genesis_hash() - } else { - let first_num = start_number - SNAPSHOT_BLOCKS; - try!(chain.block_hash(first_num).ok_or(Error::IncompleteChain)) - }; - +pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_hash: H256, writer: &Mutex, progress: &'a Progress) -> Result, Error> { let mut chunker = BlockChunker { chain: chain, rlps: VecDeque::new(), @@ -285,7 +281,7 @@ pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), wr progress: progress, }; - try!(chunker.chunk_all(first_hash)); + try!(chunker.chunk_all()); Ok(chunker.hashes) } @@ -372,7 +368,7 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex, // maps code hashes to code itself. missing_code: HashMap>, // maps code hashes to lists of accounts missing that code. + bloom: Bloom, } impl StateRebuilder { @@ -404,6 +401,7 @@ impl StateRebuilder { state_root: SHA3_NULL_RLP, code_map: HashMap::new(), missing_code: HashMap::new(), + bloom: StateDB::load_bloom(&*db), } } @@ -422,43 +420,19 @@ impl StateRebuilder { // new code contained within this chunk. let mut chunk_code = HashMap::new(); - // build account tries in parallel. - // Todo [rob] keep a thread pool around so we don't do this per-chunk. - try!(scope(|scope| { - let mut handles = Vec::new(); - for (account_chunk, out_pairs_chunk) in account_fat_rlps.chunks(chunk_size).zip(pairs.chunks_mut(chunk_size)) { - let code_map = &self.code_map; - let handle: ScopedJoinHandle> = scope.spawn(move || { - let mut db = MemoryDB::new(); - let status = try!(rebuild_accounts(&mut db, account_chunk, out_pairs_chunk, code_map)); - - trace!(target: "snapshot", "thread rebuilt {} account tries", account_chunk.len()); - Ok((db, status)) - }); - - handles.push(handle); + for (account_chunk, out_pairs_chunk) in account_fat_rlps.chunks(chunk_size).zip(pairs.chunks_mut(chunk_size)) { + let code_map = &self.code_map; + let status = try!(rebuild_accounts(self.db.as_hashdb_mut(), account_chunk, out_pairs_chunk, code_map)); + chunk_code.extend(status.new_code); + for (addr_hash, code_hash) in status.missing_code { + self.missing_code.entry(code_hash).or_insert_with(Vec::new).push(addr_hash); } - - // consolidate all edits into the main overlay. - for handle in handles { - let (thread_db, status): (MemoryDB, _) = try!(handle.join()); - self.db.consolidate(thread_db); - - chunk_code.extend(status.new_code); - - for (addr_hash, code_hash) in status.missing_code { - self.missing_code.entry(code_hash).or_insert_with(Vec::new).push(addr_hash); - } - } - - Ok::<_, ::error::Error>(()) - })); - + } // patch up all missing code. must be done after collecting all new missing code entries. for (code_hash, code) in chunk_code { for addr_hash in self.missing_code.remove(&code_hash).unwrap_or_else(Vec::new) { let mut db = AccountDBMut::from_hash(self.db.as_hashdb_mut(), addr_hash); - db.emplace(code_hash, code.clone()); + db.emplace(code_hash, DBValue::from_slice(&code)); } self.code_map.insert(code_hash, code); @@ -466,9 +440,6 @@ impl StateRebuilder { let backing = self.db.backing().clone(); - // bloom has to be updated - let mut bloom = StateDB::load_bloom(&backing); - // batch trie writes { let mut account_trie = if self.state_root != SHA3_NULL_RLP { @@ -479,17 +450,17 @@ impl StateRebuilder { for (hash, thin_rlp) in pairs { if &thin_rlp[..] != &empty_rlp[..] { - bloom.set(&*hash); + self.bloom.set(&*hash); } try!(account_trie.insert(&hash, &thin_rlp)); } } - let bloom_journal = bloom.drain_journal(); + let bloom_journal = self.bloom.drain_journal(); let mut batch = backing.transaction(); try!(StateDB::commit_bloom(&mut batch, bloom_journal)); try!(self.db.inject(&mut batch)); - try!(backing.write(batch).map_err(::util::UtilError::SimpleString)); + backing.write_buffered(batch); trace!(target: "snapshot", "current state root: {:?}", self.state_root); Ok(()) } @@ -596,7 +567,7 @@ impl BlockRebuilder { let rlp = UntrustedRlp::new(chunk); let item_count = rlp.item_count(); - trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 2); + trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3); // todo: assert here that these values are consistent with chunks being in order. let mut cur_number = try!(rlp.val_at::(0)) + 1; @@ -632,7 +603,7 @@ impl BlockRebuilder { } else { self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false); } - self.db.write(batch).expect("Error writing to the DB"); + self.db.write_buffered(batch); self.chain.commit(); parent_hash = BlockView::new(&block_bytes).hash(); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 57782e6cd..9b66a5cdc 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -74,6 +74,7 @@ struct Restoration { snappy_buffer: Bytes, final_state_root: H256, guard: Guard, + db: Arc, } struct RestorationParams<'a> { @@ -105,12 +106,13 @@ impl Restoration { manifest: manifest, state_chunks_left: state_chunks, block_chunks_left: block_chunks, - state: StateRebuilder::new(raw_db, params.pruning), + state: StateRebuilder::new(raw_db.clone(), params.pruning), blocks: blocks, writer: params.writer, snappy_buffer: Vec::new(), final_state_root: root, guard: params.guard, + db: raw_db, }) } @@ -467,39 +469,47 @@ impl Service { /// Feed a chunk of either kind. no-op if no restoration or status is wrong. fn feed_chunk(&self, hash: H256, chunk: &[u8], is_state: bool) -> Result<(), Error> { // TODO: be able to process block chunks and state chunks at same time? - let mut restoration = self.restoration.lock(); + let (result, db) = { + let mut restoration = self.restoration.lock(); - match self.status() { - RestorationStatus::Inactive | RestorationStatus::Failed => Ok(()), - RestorationStatus::Ongoing { .. } => { - let res = { - let rest = match *restoration { - Some(ref mut r) => r, - None => return Ok(()), - }; - - match is_state { - true => rest.feed_state(hash, chunk), - false => rest.feed_blocks(hash, chunk, &*self.engine), - }.map(|_| rest.is_done()) - }; - - match res { - Ok(is_done) => { - match is_state { - true => self.state_chunks.fetch_add(1, Ordering::SeqCst), - false => self.block_chunks.fetch_add(1, Ordering::SeqCst), + match self.status() { + RestorationStatus::Inactive | RestorationStatus::Failed => return Ok(()), + RestorationStatus::Ongoing { .. } => { + let (res, db) = { + let rest = match *restoration { + Some(ref mut r) => r, + None => return Ok(()), }; - match is_done { - true => self.finalize_restoration(&mut *restoration), - false => Ok(()) + (match is_state { + true => rest.feed_state(hash, chunk), + false => rest.feed_blocks(hash, chunk, &*self.engine), + }.map(|_| rest.is_done()), rest.db.clone()) + }; + + let res = match res { + Ok(is_done) => { + match is_state { + true => self.state_chunks.fetch_add(1, Ordering::SeqCst), + false => self.block_chunks.fetch_add(1, Ordering::SeqCst), + }; + + match is_done { + true => { + try!(db.flush().map_err(::util::UtilError::SimpleString)); + drop(db); + return self.finalize_restoration(&mut *restoration); + }, + false => Ok(()) + } } - } - other => other.map(drop), + other => other.map(drop), + }; + (res, db) } } - } + }; + result.and_then(|_| db.flush().map_err(|e| ::util::UtilError::SimpleString(e).into())) } /// Feed a state chunk to be processed synchronously. @@ -549,8 +559,9 @@ impl SnapshotService for Service { } fn begin_restore(&self, manifest: ManifestData) { - self.io_channel.send(ClientIoMessage::BeginRestoration(manifest)) - .expect("snapshot service and io service are kept alive by client service; qed"); + if let Err(e) = self.io_channel.send(ClientIoMessage::BeginRestoration(manifest)) { + trace!("Error sending snapshot service message: {:?}", e); + } } fn abort_restore(&self) { @@ -559,13 +570,15 @@ impl SnapshotService for Service { } fn restore_state_chunk(&self, hash: H256, chunk: Bytes) { - self.io_channel.send(ClientIoMessage::FeedStateChunk(hash, chunk)) - .expect("snapshot service and io service are kept alive by client service; qed"); + if let Err(e) = self.io_channel.send(ClientIoMessage::FeedStateChunk(hash, chunk)) { + trace!("Error sending snapshot service message: {:?}", e); + } } fn restore_block_chunk(&self, hash: H256, chunk: Bytes) { - self.io_channel.send(ClientIoMessage::FeedBlockChunk(hash, chunk)) - .expect("snapshot service and io service are kept alive by client service; qed"); + if let Err(e) = self.io_channel.send(ClientIoMessage::FeedBlockChunk(hash, chunk)) { + trace!("Error sending snapshot service message: {:?}", e); + } } } diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 06e069655..62c6ea2fe 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -57,7 +57,7 @@ fn chunk_and_restore(amount: u64) { // snapshot it. let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap()); - let block_hashes = chunk_blocks(&bc, (amount, best_hash), &writer, &Progress::default()).unwrap(); + let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap(); writer.into_inner().finish(::snapshot::ManifestData { state_hashes: Vec::new(), block_hashes: block_hashes, diff --git a/ethcore/src/snapshot/tests/helpers.rs b/ethcore/src/snapshot/tests/helpers.rs index cb928346e..c97f138d7 100644 --- a/ethcore/src/snapshot/tests/helpers.rs +++ b/ethcore/src/snapshot/tests/helpers.rs @@ -21,6 +21,7 @@ use account_db::AccountDBMut; use rand::Rng; use snapshot::account::Account; +use util::DBValue; use util::hash::{FixedHash, H256}; use util::hashdb::HashDB; use util::trie::{Alphabet, StandardMap, SecTrieDBMut, TrieMut, ValueMode}; @@ -66,7 +67,7 @@ impl StateProducer { let mut account = Account::from_thin_rlp(&*account_data); let acct_db = AccountDBMut::from_hash(db, *address_hash); fill_storage(acct_db, account.storage_root_mut(), &mut self.storage_seed); - *account_data = account.to_thin_rlp(); + *account_data = DBValue::from_vec(account.to_thin_rlp()); } // sweep again to alter account trie. diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 9110287c9..26318b673 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -16,10 +16,12 @@ //! Parameters for a block chain. -use common::*; +use util::*; +use builtin::Builtin; use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound}; use pod_state::*; use account_db::*; +use header::{BlockNumber, Header}; use state_db::StateDB; use super::genesis::Genesis; use super::seal::Generic as GenericSeal; diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 6a56d95c2..b26c79cba 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -172,7 +172,7 @@ impl Account { using it will not fail."); let item: U256 = match db.get(key){ - Ok(x) => x.map_or_else(U256::zero, decode), + Ok(x) => x.map_or_else(U256::zero, |v| decode(&*v)), Err(e) => panic!("Encountered potential DB corruption: {}", e), }; let value: H256 = item.into(); @@ -253,8 +253,8 @@ impl Account { self.is_cached() || match db.get(&self.code_hash) { Some(x) => { - self.code_cache = Arc::new(x.to_vec()); self.code_size = Some(x.len()); + self.code_cache = Arc::new(x.to_vec()); true }, _ => { @@ -351,7 +351,7 @@ impl Account { self.code_filth = Filth::Clean; }, (true, false) => { - db.emplace(self.code_hash.clone(), (*self.code_cache).clone()); + db.emplace(self.code_hash.clone(), DBValue::from_slice(&*self.code_cache)); self.code_size = Some(self.code_cache.len()); self.code_filth = Filth::Clean; }, diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index b4e9178f1..6befcad12 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -16,14 +16,18 @@ use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry; -use common::*; +use util::*; +use receipt::Receipt; use engines::Engine; +use env_info::EnvInfo; +use error::Error; use executive::{Executive, TransactOptions}; use factory::Factories; use trace::FlatTrace; use pod_account::*; use pod_state::{self, PodState}; use types::state_diff::StateDiff; +use transaction::SignedTransaction; use state_db::StateDB; mod account; @@ -167,24 +171,24 @@ impl AccountEntry { /// Upon destruction all the local cache data propagated into the global cache. /// Propagated items might be rejected if current state is non-canonical. /// -/// State snapshotting. +/// State checkpointing. /// -/// A new snapshot can be created with `snapshot()`. Snapshots can be +/// A new checkpoint can be created with `checkpoint()`. checkpoints can be /// created in a hierarchy. -/// When a snapshot is active all changes are applied directly into -/// `cache` and the original value is copied into an active snapshot. -/// Reverting a snapshot with `revert_to_snapshot` involves copying -/// original values from the latest snapshot back into `cache`. The code +/// When a checkpoint is active all changes are applied directly into +/// `cache` and the original value is copied into an active checkpoint. +/// Reverting a checkpoint with `revert_to_checkpoint` involves copying +/// original values from the latest checkpoint back into `cache`. The code /// takes care not to overwrite cached storage while doing that. -/// Snapshot can be discateded with `discard_snapshot`. All of the orignal -/// backed-up values are moved into a parent snapshot (if any). +/// checkpoint can be discateded with `discard_checkpoint`. All of the orignal +/// backed-up values are moved into a parent checkpoint (if any). /// pub struct State { db: StateDB, root: H256, cache: RefCell>, // The original account is preserved in - snapshots: RefCell>>>, + checkpoints: RefCell>>>, account_start_nonce: U256, factories: Factories, } @@ -213,7 +217,7 @@ impl State { db: db, root: root, cache: RefCell::new(HashMap::new()), - snapshots: RefCell::new(Vec::new()), + checkpoints: RefCell::new(Vec::new()), account_start_nonce: account_start_nonce, factories: factories, } @@ -229,7 +233,7 @@ impl State { db: db, root: root, cache: RefCell::new(HashMap::new()), - snapshots: RefCell::new(Vec::new()), + checkpoints: RefCell::new(Vec::new()), account_start_nonce: account_start_nonce, factories: factories }; @@ -237,21 +241,21 @@ impl State { Ok(state) } - /// Create a recoverable snaphot of this state. - pub fn snapshot(&mut self) { - self.snapshots.get_mut().push(HashMap::new()); + /// Create a recoverable checkpoint of this state. + pub fn checkpoint(&mut self) { + self.checkpoints.get_mut().push(HashMap::new()); } - /// Merge last snapshot with previous. - pub fn discard_snapshot(&mut self) { - // merge with previous snapshot - let last = self.snapshots.get_mut().pop(); - if let Some(mut snapshot) = last { - if let Some(ref mut prev) = self.snapshots.get_mut().last_mut() { + /// Merge last checkpoint with previous. + pub fn discard_checkpoint(&mut self) { + // merge with previous checkpoint + let last = self.checkpoints.get_mut().pop(); + if let Some(mut checkpoint) = last { + if let Some(ref mut prev) = self.checkpoints.get_mut().last_mut() { if prev.is_empty() { - **prev = snapshot; + **prev = checkpoint; } else { - for (k, v) in snapshot.drain() { + for (k, v) in checkpoint.drain() { prev.entry(k).or_insert(v); } } @@ -259,15 +263,15 @@ impl State { } } - /// Revert to the last snapshot and discard it. - pub fn revert_to_snapshot(&mut self) { - if let Some(mut snapshot) = self.snapshots.get_mut().pop() { - for (k, v) in snapshot.drain() { + /// Revert to the last checkpoint and discard it. + pub fn revert_to_checkpoint(&mut self) { + if let Some(mut checkpoint) = self.checkpoints.get_mut().pop() { + for (k, v) in checkpoint.drain() { match v { Some(v) => { match self.cache.get_mut().entry(k) { Entry::Occupied(mut e) => { - // Merge snapshotted changes back into the main account + // Merge checkpointed changes back into the main account // storage preserving the cache. e.get_mut().overwrite_with(v); }, @@ -293,14 +297,14 @@ impl State { fn insert_cache(&self, address: &Address, account: AccountEntry) { // Dirty account which is not in the cache means this is a new account. - // It goes directly into the snapshot as there's nothing to rever to. + // It goes directly into the checkpoint as there's nothing to rever to. // // In all other cases account is read as clean first, and after that made - // dirty in and added to the snapshot with `note_cache`. + // dirty in and added to the checkpoint with `note_cache`. if account.is_dirty() { - if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { - if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); + if let Some(ref mut checkpoint) = self.checkpoints.borrow_mut().last_mut() { + if !checkpoint.contains_key(address) { + checkpoint.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); return; } } @@ -309,9 +313,9 @@ impl State { } fn note_cache(&self, address: &Address) { - if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { - if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_dirty)); + if let Some(ref mut checkpoint) = self.checkpoints.borrow_mut().last_mut() { + if !checkpoint.contains_key(address) { + checkpoint.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_dirty)); } } } @@ -404,7 +408,7 @@ impl State { // account is not found in the global cache, get from the DB and insert into local let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let maybe_acc = match db.get(address) { - Ok(acc) => acc.map(Account::from_rlp), + Ok(acc) => acc.map(|v| Account::from_rlp(&v)), Err(e) => panic!("Potential DB corruption encountered: {}", e), }; let r = maybe_acc.as_ref().map_or(H256::new(), |a| { @@ -548,7 +552,7 @@ impl State { /// Commits our cached account changes into the trie. pub fn commit(&mut self) -> Result<(), Error> { - assert!(self.snapshots.borrow().is_empty()); + assert!(self.checkpoints.borrow().is_empty()); Self::commit_into(&self.factories, &mut self.db, &mut self.root, &mut *self.cache.borrow_mut()) } @@ -561,7 +565,7 @@ impl State { #[cfg(feature = "json-tests")] /// Populate the state from `accounts`. pub fn populate_from(&mut self, accounts: PodState) { - assert!(self.snapshots.borrow().is_empty()); + assert!(self.checkpoints.borrow().is_empty()); for (add, acc) in accounts.drain().into_iter() { self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc)))); } @@ -569,7 +573,7 @@ impl State { /// Populate a PodAccount map from this state. pub fn to_pod(&self) -> PodState { - assert!(self.snapshots.borrow().is_empty()); + assert!(self.checkpoints.borrow().is_empty()); // TODO: handle database rather than just the cache. // will need fat db. PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| { @@ -644,7 +648,7 @@ impl State { // not found in the global cache, get from the DB and insert into local let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let mut maybe_acc = match db.get(a) { - Ok(acc) => acc.map(Account::from_rlp), + Ok(acc) => acc.map(|v| Account::from_rlp(&v)), Err(e) => panic!("Potential DB corruption encountered: {}", e), }; if let Some(ref mut account) = maybe_acc.as_mut() { @@ -676,7 +680,7 @@ impl State { let maybe_acc = if self.db.check_account_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let maybe_acc = match db.get(a) { - Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(acc))), + Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))), Ok(None) => AccountEntry::new_clean(None), Err(e) => panic!("Potential DB corruption encountered: {}", e), }; @@ -739,7 +743,7 @@ impl Clone for State { db: self.db.boxed_clone(), root: self.root.clone(), cache: RefCell::new(cache), - snapshots: RefCell::new(Vec::new()), + checkpoints: RefCell::new(Vec::new()), account_start_nonce: self.account_start_nonce.clone(), factories: self.factories.clone(), } @@ -756,7 +760,7 @@ use super::*; use util::{U256, H256, FixedHash, Address, Hashable}; use tests::helpers::*; use devtools::*; -use env_info::*; +use env_info::EnvInfo; use spec::*; use transaction::*; use util::log::init_log; @@ -1777,34 +1781,34 @@ fn ensure_cached() { } #[test] -fn snapshot_basic() { +fn checkpoint_basic() { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); let a = Address::zero(); - state.snapshot(); + state.checkpoint(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.discard_snapshot(); + state.discard_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); - state.snapshot(); + state.checkpoint(); state.add_balance(&a, &U256::from(1u64)); assert_eq!(state.balance(&a), U256::from(70u64)); - state.revert_to_snapshot(); + state.revert_to_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); } #[test] -fn snapshot_nested() { +fn checkpoint_nested() { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); let a = Address::zero(); - state.snapshot(); - state.snapshot(); + state.checkpoint(); + state.checkpoint(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.discard_snapshot(); + state.discard_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); - state.revert_to_snapshot(); + state.revert_to_checkpoint(); assert_eq!(state.balance(&a), U256::from(0)); } diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 082cd3c78..e152ac37a 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -20,11 +20,12 @@ use ethereum; use block::IsBlock; use tests::helpers::*; use types::filter::Filter; -use common::*; +use util::*; use devtools::*; use miner::Miner; use rlp::{Rlp, View}; use spec::Spec; +use views::BlockView; #[test] fn imports_from_empty() { diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 33df1fab7..4d91a8c00 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -17,18 +17,22 @@ use ethkey::KeyPair; use io::*; use client::{BlockChainClient, Client, ClientConfig}; -use common::*; +use util::*; use spec::*; use state_db::StateDB; use block::{OpenBlock, Drain}; use blockchain::{BlockChain, Config as BlockChainConfig}; +use builtin::Builtin; use evm::Schedule; use engines::Engine; +use env_info::EnvInfo; use ethereum; use devtools::*; use miner::Miner; +use header::Header; +use transaction::{Action, SignedTransaction, Transaction}; use rlp::{self, RlpStream, Stream}; -use db::COL_STATE; +use views::BlockView; #[cfg(feature = "json-tests")] pub enum ChainEra { @@ -346,7 +350,7 @@ pub fn get_temp_state() -> GuardedTempResult { pub fn get_temp_state_db_in(path: &Path) -> StateDB { let db = new_db(path.to_str().expect("Only valid utf8 paths for tests.")); - let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, COL_STATE); + let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, ::db::COL_STATE); StateDB::new(journal_db, 5 * 1024 * 1024) } diff --git a/ethcore/src/verification/mod.rs b/ethcore/src/verification/mod.rs index ed9e0ec4c..239c88597 100644 --- a/ethcore/src/verification/mod.rs +++ b/ethcore/src/verification/mod.rs @@ -31,6 +31,8 @@ pub use self::queue::{BlockQueue, Config as QueueConfig, VerificationQueue, Queu pub enum VerifierType { /// Verifies block normally. Canon, + /// Verifies block normallly, but skips seal verification. + CanonNoSeal, /// Does not verify block at all. /// Used in tests. Noop, @@ -44,7 +46,17 @@ impl Default for VerifierType { pub fn new(v: VerifierType) -> Box { match v { - VerifierType::Canon => Box::new(CanonVerifier), + VerifierType::Canon | VerifierType::CanonNoSeal => Box::new(CanonVerifier), VerifierType::Noop => Box::new(NoopVerifier), } } + +impl VerifierType { + /// Check if seal verification is enabled for this verifier type. + pub fn verifying_seal(&self) -> bool { + match *self { + VerifierType::Canon => true, + VerifierType::Noop | VerifierType::CanonNoSeal => false, + } + } +} diff --git a/ethcore/src/verification/queue/kind.rs b/ethcore/src/verification/queue/kind.rs index b6b6c5cf6..17b997490 100644 --- a/ethcore/src/verification/queue/kind.rs +++ b/ethcore/src/verification/queue/kind.rs @@ -57,7 +57,7 @@ pub trait Kind: 'static + Sized + Send + Sync { fn create(input: Self::Input, engine: &Engine) -> Result; /// Attempt to verify the `Unverified` item using the given engine. - fn verify(unverified: Self::Unverified, engine: &Engine) -> Result; + fn verify(unverified: Self::Unverified, engine: &Engine, check_seal: bool) -> Result; } /// The blocks verification module. @@ -89,9 +89,9 @@ pub mod blocks { } } - fn verify(un: Self::Unverified, engine: &Engine) -> Result { + fn verify(un: Self::Unverified, engine: &Engine, check_seal: bool) -> Result { let hash = un.hash(); - match verify_block_unordered(un.header, un.bytes, engine) { + match verify_block_unordered(un.header, un.bytes, engine, check_seal) { Ok(verified) => Ok(verified), Err(e) => { warn!(target: "client", "Stage 2 block verification failed for {}: {:?}", hash, e); @@ -176,8 +176,11 @@ pub mod headers { verify_header_params(&input, engine).map(|_| input) } - fn verify(unverified: Self::Unverified, engine: &Engine) -> Result { - engine.verify_block_unordered(&unverified, None).map(|_| unverified) + fn verify(unverified: Self::Unverified, engine: &Engine, check_seal: bool) -> Result { + match check_seal { + true => engine.verify_block_unordered(&unverified, None).map(|_| unverified), + false => Ok(unverified), + } } } } diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index cf6ca3f53..f801bbe2e 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -159,7 +159,7 @@ struct Verification { impl VerificationQueue { /// Creates a new queue instance. - pub fn new(config: Config, engine: Arc, message_channel: IoChannel) -> Self { + pub fn new(config: Config, engine: Arc, message_channel: IoChannel, check_seal: bool) -> Self { let verification = Arc::new(Verification { unverified: Mutex::new(VecDeque::new()), verifying: Mutex::new(VecDeque::new()), @@ -198,7 +198,7 @@ impl VerificationQueue { .name(format!("Verifier #{}", i)) .spawn(move || { panic_handler.catch_panic(move || { - VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty) + VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty, check_seal) }).unwrap() }) .expect("Error starting block verification thread") @@ -219,7 +219,7 @@ impl VerificationQueue { } } - fn verify(verification: Arc>, engine: Arc, wait: Arc, ready: Arc, deleting: Arc, empty: Arc) { + fn verify(verification: Arc>, engine: Arc, wait: Arc, ready: Arc, deleting: Arc, empty: Arc, check_seal: bool) { while !deleting.load(AtomicOrdering::Acquire) { { let mut more_to_verify = verification.more_to_verify.lock().unwrap(); @@ -253,7 +253,7 @@ impl VerificationQueue { }; let hash = item.hash(); - let is_ready = match K::verify(item, &*engine) { + let is_ready = match K::verify(item, &*engine, check_seal) { Ok(verified) => { let mut verifying = verification.verifying.lock(); let mut idx = None; @@ -529,7 +529,7 @@ mod tests { fn get_test_queue() -> BlockQueue { let spec = get_test_spec(); let engine = spec.engine; - BlockQueue::new(Config::default(), engine, IoChannel::disconnected()) + BlockQueue::new(Config::default(), engine, IoChannel::disconnected(), true) } #[test] @@ -537,7 +537,7 @@ mod tests { // TODO better test let spec = Spec::new_test(); let engine = spec.engine; - let _ = BlockQueue::new(Config::default(), engine, IoChannel::disconnected()); + let _ = BlockQueue::new(Config::default(), engine, IoChannel::disconnected(), true); } #[test] @@ -601,7 +601,7 @@ mod tests { let engine = spec.engine; let mut config = Config::default(); config.max_mem_use = super::MIN_MEM_LIMIT; // empty queue uses about 15000 - let queue = BlockQueue::new(config, engine, IoChannel::disconnected()); + let queue = BlockQueue::new(config, engine, IoChannel::disconnected(), true); assert!(!queue.queue_info().is_full()); let mut blocks = get_good_dummy_block_seq(50); for b in blocks.drain(..) { diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 2d297499d..1b8eddfe8 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -21,10 +21,14 @@ /// 2. Signatures verification done in the queue. /// 3. Final verification against the blockchain done before enactment. -use common::*; +use util::*; use engines::Engine; +use error::{BlockError, Error}; use blockchain::*; +use header::{BlockNumber, Header}; use rlp::{UntrustedRlp, View}; +use transaction::SignedTransaction; +use views::BlockView; /// Preprocessed block data gathered in `verify_block_unordered` call pub struct PreverifiedBlock { @@ -66,10 +70,12 @@ pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Res /// Phase 2 verification. Perform costly checks such as transaction signatures and block nonce for ethash. /// Still operates on a individual block /// Returns a `PreverifiedBlock` structure populated with transactions -pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine) -> Result { - 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)); +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)); + } } // Verify transactions. let mut transactions = Vec::new(); diff --git a/ethcrypto/src/lib.rs b/ethcrypto/src/lib.rs index 7a1aba48c..103e750e6 100644 --- a/ethcrypto/src/lib.rs +++ b/ethcrypto/src/lib.rs @@ -166,7 +166,9 @@ pub mod ecies { /// Encrypt a message with a public key pub fn encrypt(public: &Public, shared_mac: &[u8], plain: &[u8]) -> Result, Error> { - let r = Random.generate().unwrap(); + let r = Random.generate() + .expect("context known to have key-generation capabilities; qed"); + let z = try!(ecdh::agree(r.secret(), public)); let mut key = [0u8; 32]; let mut mkey = [0u8; 32]; @@ -201,7 +203,9 @@ pub mod ecies { /// Encrypt a message with a public key pub fn encrypt_single_message(public: &Public, plain: &[u8]) -> Result, Error> { - let r = Random.generate().unwrap(); + let r = Random.generate() + .expect("context known to have key-generation capabilities"); + let z = try!(ecdh::agree(r.secret(), public)); let mut key = [0u8; 32]; let mut mkey = [0u8; 32]; diff --git a/ethstore/Cargo.toml b/ethstore/Cargo.toml index 7fa2a6890..03347cbd7 100644 --- a/ethstore/Cargo.toml +++ b/ethstore/Cargo.toml @@ -18,6 +18,7 @@ docopt = { version = "0.6", optional = true } time = "0.1.34" lazy_static = "0.2" itertools = "0.4" +parking_lot = "0.3" ethcrypto = { path = "../ethcrypto" } [build-dependencies] diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index fe1c46e63..6616ec15d 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -28,7 +28,9 @@ const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> { use std::ffi; use libc; - let cstr = ffi::CString::new(file_path.to_str().unwrap()).unwrap(); + + let cstr = try!(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), @@ -63,15 +65,15 @@ impl DiskDirectory { let paths = try!(fs::read_dir(&self.path)) .flat_map(Result::ok) .filter(|entry| { - let metadata = entry.metadata(); + let metadata = entry.metadata().ok(); let file_name = entry.file_name(); - let name = file_name.to_str().unwrap(); + let name = file_name.to_string_lossy(); // filter directories - metadata.is_ok() && !metadata.unwrap().is_dir() && + metadata.map_or(false, |m| !m.is_dir()) && // hidden files !name.starts_with(".") && // other ignored files - !IGNORED_FILES.contains(&name) + !IGNORED_FILES.contains(&&*name) }) .map(|entry| entry.path()) .collect::>(); diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index faaea8f9a..4360a39f0 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -15,7 +15,6 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use std::sync::RwLock; use std::mem; use ethkey::KeyPair; use crypto::KEY_ITERATIONS; @@ -26,6 +25,7 @@ use account::SafeAccount; use {Error, SecretStore}; use json; use json::UUID; +use parking_lot::RwLock; use presale::PresaleWallet; use import; @@ -56,13 +56,13 @@ impl EthStore { let account = try!(self.dir.insert(account.clone())); // update cache - let mut cache = self.cache.write().unwrap(); + let mut cache = self.cache.write(); cache.insert(account.address.clone(), account); Ok(()) } fn reload_accounts(&self) -> Result<(), Error> { - let mut cache = self.cache.write().unwrap(); + let mut cache = self.cache.write(); let accounts = try!(self.dir.load()); let new_accounts: BTreeMap<_, _> = accounts.into_iter().map(|account| (account.address.clone(), account)).collect(); mem::replace(&mut *cache, new_accounts); @@ -71,13 +71,13 @@ impl EthStore { fn get(&self, address: &Address) -> Result { { - let cache = self.cache.read().unwrap(); + let cache = self.cache.read(); if let Some(account) = cache.get(address) { return Ok(account.clone()) } } try!(self.reload_accounts()); - let cache = self.cache.read().unwrap(); + let cache = self.cache.read(); cache.get(address).cloned().ok_or(Error::InvalidAccount) } } @@ -111,7 +111,7 @@ impl SecretStore for EthStore { fn accounts(&self) -> Result, Error> { try!(self.reload_accounts()); - Ok(self.cache.read().unwrap().keys().cloned().collect()) + Ok(self.cache.read().keys().cloned().collect()) } fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { @@ -131,7 +131,7 @@ impl SecretStore for EthStore { if can_remove { try!(self.dir.remove(address)); - let mut cache = self.cache.write().unwrap(); + let mut cache = self.cache.write(); cache.remove(address); Ok(()) } else { diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 302e165cf..f8619ff19 100644 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -26,12 +26,15 @@ extern crate serde_json; extern crate rustc_serialize; extern crate crypto as rcrypto; extern crate tiny_keccak; -#[macro_use] -extern crate lazy_static; +extern crate parking_lot; + // reexport it nicely extern crate ethkey as _ethkey; extern crate ethcrypto as crypto; +#[macro_use] +extern crate lazy_static; + pub mod dir; pub mod ethkey; diff --git a/ethstore/src/presale.rs b/ethstore/src/presale.rs index 2904db6ef..ff3bde6f4 100644 --- a/ethstore/src/presale.rs +++ b/ethstore/src/presale.rs @@ -33,7 +33,8 @@ impl From for PresaleWallet { impl PresaleWallet { pub fn open

(path: P) -> Result where P: AsRef { let file = try!(fs::File::open(path)); - let presale = json::PresaleWallet::load(file).unwrap(); + let presale = try!(json::PresaleWallet::load(file) + .map_err(|e| Error::InvalidKeyFile(format!("{}", e)))); Ok(PresaleWallet::from(presale)) } diff --git a/js/.gitignore b/js/.gitignore index c846423fc..b3ece001c 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -1,5 +1,7 @@ node_modules npm-debug.log +build .build .coverage +.dist .happypack diff --git a/js/Cargo.precompiled.toml b/js/Cargo.precompiled.toml new file mode 100644 index 000000000..b8175e4f7 --- /dev/null +++ b/js/Cargo.precompiled.toml @@ -0,0 +1,19 @@ +[package] +description = "Parity built-in dapps." +name = "parity-ui-precompiled" +version = "1.4.0" +license = "GPL-3.0" +authors = ["Ethcore "] +build = "build.rs" + +[features] +default = ["with-syntex", "use-precompiled-js"] +use-precompiled-js = ["parity-dapps-glue/use-precompiled-js"] +with-syntex = ["parity-dapps-glue/with-syntex"] + +[build-dependencies] +parity-dapps-glue = "1.4" + +[dependencies] +parity-dapps-glue = "1.4" + diff --git a/js/Cargo.toml b/js/Cargo.toml new file mode 100644 index 000000000..e52bfec9e --- /dev/null +++ b/js/Cargo.toml @@ -0,0 +1,18 @@ +[package] +description = "Parity built-in dapps." +name = "parity-ui-dev" +version = "1.4.0" +license = "GPL-3.0" +authors = ["Ethcore "] +build = "build.rs" + +[features] +default = ["with-syntex"] +with-syntex = ["parity-dapps-glue/with-syntex"] + +[build-dependencies] +parity-dapps-glue = "1.4" + +[dependencies] +parity-dapps-glue = "1.4" + diff --git a/js/assets/images/contracts/coin-euro-64x64.png b/js/assets/images/contracts/coin-euro-64x64.png index 199f2b53e..0af66131b 100644 Binary files a/js/assets/images/contracts/coin-euro-64x64.png and b/js/assets/images/contracts/coin-euro-64x64.png differ diff --git a/js/assets/images/contracts/coin-euro.png b/js/assets/images/contracts/coin-euro.png new file mode 100644 index 000000000..d4a5b081e Binary files /dev/null and b/js/assets/images/contracts/coin-euro.png differ diff --git a/js/assets/images/contracts/coin-pound-64x64.png b/js/assets/images/contracts/coin-pound-64x64.png index 442ec5016..645a9d674 100644 Binary files a/js/assets/images/contracts/coin-pound-64x64.png and b/js/assets/images/contracts/coin-pound-64x64.png differ diff --git a/js/assets/images/contracts/coin-pound.png b/js/assets/images/contracts/coin-pound.png new file mode 100644 index 000000000..1158f2377 Binary files /dev/null and b/js/assets/images/contracts/coin-pound.png differ diff --git a/js/assets/images/contracts/coin-us-dollar-64x64.png b/js/assets/images/contracts/coin-us-dollar-64x64.png index 0ac124912..44255e1f9 100644 Binary files a/js/assets/images/contracts/coin-us-dollar-64x64.png and b/js/assets/images/contracts/coin-us-dollar-64x64.png differ diff --git a/js/assets/images/contracts/coin-us-dollar.png b/js/assets/images/contracts/coin-us-dollar.png new file mode 100644 index 000000000..918a86ac4 Binary files /dev/null and b/js/assets/images/contracts/coin-us-dollar.png differ diff --git a/js/assets/images/contracts/coin-yuan-64x64.png b/js/assets/images/contracts/coin-yuan-64x64.png index f485991a7..ec16a144e 100644 Binary files a/js/assets/images/contracts/coin-yuan-64x64.png and b/js/assets/images/contracts/coin-yuan-64x64.png differ diff --git a/js/assets/images/contracts/coin-yuan.png b/js/assets/images/contracts/coin-yuan.png new file mode 100644 index 000000000..1e7b7282a Binary files /dev/null and b/js/assets/images/contracts/coin-yuan.png differ diff --git a/js/build b/js/build deleted file mode 160000 index f94a8eddb..000000000 --- a/js/build +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f94a8eddb8789410dda0db03d4f1d6ae42b31208 diff --git a/js/build-server.js b/js/build-server.js index 797e89183..9153f5ed2 100644 --- a/js/build-server.js +++ b/js/build-server.js @@ -24,17 +24,38 @@ var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); +var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true }); -app.use(express.static('build')); +app.use(express.static('.build')); app.use('/api/*', proxy({ target: 'http://127.0.0.1:8080', changeOrigin: true })); +app.use('/app/*', proxy({ + target: 'http://127.0.0.1:8080', + changeOrigin: true, + pathRewrite: { + '^/app': '' + } +})); + +app.use('/parity-utils/*', proxy({ + target: 'http://127.0.0.1:3000', + changeOrigin: true, + pathRewrite: { + '^/parity-utils': '' + } +})); + app.use('/rpc/*', proxy({ target: 'http://127.0.0.1:8080', changeOrigin: true })); -app.listen(3000); +app.use(wsProxy); + +var server = app.listen(3000); + +server.on('upgrade', wsProxy.upgrade); diff --git a/dapps/ui/build.rs b/js/build.rs similarity index 90% rename from dapps/ui/build.rs rename to js/build.rs index 395f513bd..82bf1ac93 100644 --- a/dapps/ui/build.rs +++ b/js/build.rs @@ -17,6 +17,6 @@ extern crate parity_dapps_glue; fn main() { - parity_dapps_glue::js::build(concat!(env!("CARGO_MANIFEST_DIR"), "/../../js")); + parity_dapps_glue::js::build(env!("CARGO_MANIFEST_DIR"), "build"); parity_dapps_glue::generate(); } diff --git a/js/package.json b/js/package.json index 9bfcfb9be..bafb3efcd 100644 --- a/js/package.json +++ b/js/package.json @@ -23,17 +23,22 @@ "Promise" ], "scripts": { - "build": "npm run build:dll && npm run build:app", + "build": "npm run build:dll && npm run build:app && npm run build:lib", "build:app": "webpack --progress", - "build:dll": "webpack --config webpack.vendor.js --progress", - "ci:build": "npm run ci:build:dll && npm run ci:build:app", + "build:lib": "webpack --config webpack.libraries --progress", + "build:dll": "webpack --config webpack.vendor --progress", + + "ci:build": "npm run ci:build:dll && npm run ci:build:app && npm run ci:build:lib", "ci:build:app": "NODE_ENV=production webpack", - "ci:build:dll": "NODE_ENV=production webpack --config webpack.vendor.js", + "ci:build:lib": "NODE_ENV=production webpack --config webpack.libraries", + "ci:build:dll": "NODE_ENV=production webpack --config webpack.vendor", + + "start": "npm install && npm run build:dll && npm run start:app", + "start:app": "webpack-dev-server -d --history-api-fallback --open --hot --inline --progress --colors --port 3000", + "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", - "start": "npm install && npm run build:dll && npm run start:app", - "start:app": "webpack-dev-server -d --history-api-fallback --open --hot --inline --progress --colors --port 3000", "test": "mocha 'src/**/*.spec.js'", "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", "test:e2e": "mocha 'src/**/*.e2e.js'" @@ -109,6 +114,7 @@ "blockies": "0.0.2", "bytes": "^2.4.0", "es6-promise": "^3.2.1", + "file-saver": "^1.3.3", "format-json": "^1.0.3", "format-number": "^2.0.1", "geopattern": "^1.2.3", diff --git a/js/scripts/build.sh b/js/scripts/build.sh index 07487ffd5..a6f4a913c 100755 --- a/js/scripts/build.sh +++ b/js/scripts/build.sh @@ -6,7 +6,15 @@ cd .. # run build (production) and store the exit code EXITCODE=0 -npm run ci:build || EXITCODE=1 +BUILDDIR=./.dist +rm -rf $BUILDDIR +mkdir -p $BUILDDIR/src +BUILD_DEST=$BUILDDIR/build npm run ci:build || EXITCODE=1 + +# Copy rust files +cp Cargo.precompiled.toml $BUILDDIR/Cargo.toml +cp build.rs $BUILDDIR +cp src/lib.rs* $BUILDDIR/src # back to root popd diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 05dc1d784..392bdd3b8 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -1,8 +1,19 @@ #!/bin/bash +set -e + +# setup the git user defaults for the current repo +function setup_git_user { + git config push.default simple + git config merge.ours.driver true + git config user.email "jaco+gitlab@ethcore.io" + git config user.name "GitLab Build Bot" +} # change into the build directory -pushd `dirname $0` -cd ../.build +BASEDIR=`dirname $0` +GITLOG=./.git/gitcommand.log +pushd $BASEDIR +cd ../.dist # variables UTCDATE=`date -u "+%Y%m%d-%H%M%S"` @@ -11,23 +22,31 @@ UTCDATE=`date -u "+%Y%m%d-%H%M%S"` rm -rf ./.git git init -# our user details -git config push.default simple -git config merge.ours.driver true -git config user.email "jaco+gitlab@ethcore.io" -git config user.name "GitLab Build Bot" - # add local files and send it up +setup_git_user git remote add origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git -git fetch origin +git fetch origin 2>$GITLOG git checkout -b $CI_BUILD_REF_NAME git add . git commit -m "$UTCDATE [compiled]" git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" -git push origin $CI_BUILD_REF_NAME +git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG # back to root popd +# inti git with right origin +setup_git_user +git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git + +# at this point we have a detached head on GitLab, reset +git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG + +# bump js-precompiled, add, commit & push +cargo update -p parity-ui-precompiled +git add . || true +git commit -m "[ci skip] js-precompiled $UTCDATE" +git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG + # exit with exit code exit 0 diff --git a/js/scripts/update-precompiled.sh b/js/scripts/update-precompiled.sh index 85548cc14..0b9461bf0 100755 --- a/js/scripts/update-precompiled.sh +++ b/js/scripts/update-precompiled.sh @@ -1,19 +1,11 @@ #!/bin/bash +set -e - -# change into the submodule build directory +# change into main dir pushd `dirname $0` -cd ../build +cd ../../ -if [ -z "$1" ]; then - popd - echo "Usage: $0 " - exit 1 -fi - -git fetch -git fetch origin $1 -git merge $1 -X theirs +cargo update -p parity-ui-precompiled popd exit 0 diff --git a/js/src/api/api.js b/js/src/api/api.js index c82129772..9768b9acb 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -90,8 +90,8 @@ export default class Api { return this._subscriptions.subscribe(subscriptionName, callback); } - unsubscribe (subscriptionName, subscriptionId) { - return this._subscriptions.unsubscribe(subscriptionName, subscriptionId); + unsubscribe (subscriptionId) { + return this._subscriptions.unsubscribe(subscriptionId); } pollMethod (method, input, validate) { diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 40caa7643..cef75eda7 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -150,7 +150,9 @@ export default class Contract { log.event = event.name; decoded.params.forEach((param) => { - log.params[param.name] = param.token.value; + const { type, value } = param.token; + + log.params[param.name] = { type, value }; }); return log; diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 7ac3e099c..0d6169e26 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -173,12 +173,12 @@ describe('api/contract/Contract', () => { expect(log.event).to.equal('Message'); expect(log.address).to.equal('0x22bff18ec62281850546a664bb63a5c06ac5f76c'); expect(log.params).to.deep.equal({ - at: new BigNumber('1457965151'), - message: 'post(message)', - messageId: new BigNumber('281474976731085'), - parentId: new BigNumber(0), - postId: new BigNumber('281474976731104'), - sender: '0x63Cf90D3f0410092FC0fca41846f596223979195' + at: { type: 'uint', value: new BigNumber('1457965151') }, + message: { type: 'string', value: 'post(message)' }, + messageId: { type: 'uint', value: new BigNumber('281474976731085') }, + parentId: { type: 'uint', value: new BigNumber(0) }, + postId: { type: 'uint', value: new BigNumber('281474976731104') }, + sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' } }); }); }); @@ -464,12 +464,12 @@ describe('api/contract/Contract', () => { event: 'Message', logIndex: new BigNumber(0), params: { - at: new BigNumber(1457965151), - message: 'post(message)', - messageId: new BigNumber(281474976731085), - parentId: new BigNumber(0), - postId: new BigNumber(281474976731104), - sender: '0x63Cf90D3f0410092FC0fca41846f596223979195' + at: { type: 'uint', value: new BigNumber(1457965151) }, + message: { type: 'string', value: 'post(message)' }, + messageId: { type: 'uint', value: new BigNumber(281474976731085) }, + parentId: { type: 'uint', value: new BigNumber(0) }, + postId: { type: 'uint', value: new BigNumber(281474976731104) }, + sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' } }, topics: [ '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0' diff --git a/js/src/api/rpc/ethcore/ethcore.js b/js/src/api/rpc/ethcore/ethcore.js index 1ccc95bba..b9dec3b69 100644 --- a/js/src/api/rpc/ethcore/ethcore.js +++ b/js/src/api/rpc/ethcore/ethcore.js @@ -32,6 +32,12 @@ export default class Ethcore { .execute('ethcore_addReservedPeer', encode); } + dappsPort () { + return this._transport + .execute('ethcore_dappsPort') + .then(outNumber); + } + defaultExtraData () { return this._transport .execute('ethcore_defaultExtraData'); @@ -154,6 +160,12 @@ export default class Ethcore { .execute('ethcore_setTransactionsLimit', inNumber16(quantity)); } + signerPort () { + return this._transport + .execute('ethcore_signerPort') + .then(outNumber); + } + transactionsLimit () { return this._transport .execute('ethcore_transactionsLimit') diff --git a/js/src/api/rpc/personal/personal.js b/js/src/api/rpc/personal/personal.js index 2609bc509..e35333102 100644 --- a/js/src/api/rpc/personal/personal.js +++ b/js/src/api/rpc/personal/personal.js @@ -33,6 +33,11 @@ export default class Personal { .execute('personal_confirmRequest', inNumber16(requestId), options, password); } + changePassword (account, password, newPassword) { + return this._transport + .execute('personal_changePassword', inAddress(account), password, newPassword); + } + generateAuthorizationToken () { return this._transport .execute('personal_generateAuthorizationToken'); @@ -105,6 +110,11 @@ export default class Personal { .execute('personal_signerEnabled'); } + testPassword (account, password) { + return this._transport + .execute('personal_testPassword', inAddress(account), password); + } + unlockAccount (account, password, duration = 1) { return this._transport .execute('personal_unlockAccount', inAddress(account), password, inNumber10(duration)); diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 6f9b7cf7e..61e06499e 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -29,17 +29,14 @@ const events = { 'personal_requestsToConfirm': { module: 'signer' } }; -let nextSubscriptionId = 0; - export default class Manager { constructor (api) { this._api = api; - this.subscriptions = {}; + this.subscriptions = []; this.values = {}; Object.keys(events).forEach((subscriptionName) => { - this.subscriptions[subscriptionName] = {}; this.values[subscriptionName] = { error: null, data: null @@ -71,61 +68,59 @@ export default class Manager { return; } - const subscriptionId = nextSubscriptionId++; + const subscriptionId = this.subscriptions.length; const { error, data } = this.values[subscriptionName]; const engine = this[`_${subscription.module}`]; - this.subscriptions[subscriptionName][subscriptionId] = callback; + this.subscriptions[subscriptionId] = { + name: subscriptionName, + id: subscriptionId, + callback + }; if (!engine.isStarted) { engine.start(); } else { - this._sendData(subscriptionName, subscriptionId, callback, error, data); + this._sendData(subscriptionId, error, data); } resolve(subscriptionId); }); } - unsubscribe (subscriptionName, subscriptionId) { + unsubscribe (subscriptionId) { return new Promise((resolve, reject) => { - const subscription = this._validateType(subscriptionName); - - if (isError(subscription)) { - reject(subscription); + if (!this.subscriptions[subscriptionId]) { + reject(new Error(`Cannot find subscription ${subscriptionId}`)); return; } - if (!this.subscriptions[subscriptionName][subscriptionId]) { - reject(new Error(`Cannot find subscription ${subscriptionId} for type ${subscriptionName}`)); - return; - } - - delete this.subscriptions[subscriptionName][subscriptionId]; + delete this.subscriptions[subscriptionId]; resolve(); }); } - _sendData (subscriptionName, subscriptionId, callback, error, data) { + _sendData (subscriptionId, error, data) { + const { callback } = this.subscriptions[subscriptionId]; + try { callback(error, data); } catch (error) { - console.error(`Unable to update callback for ${subscriptionName}, subscriptionId ${subscriptionId}`, error); - this.unsubscribe(subscriptionName, subscriptionId); + console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); + this.unsubscribe(subscriptionId); } } _updateSubscriptions = (subscriptionName, error, data) => { - if (!this.subscriptions[subscriptionName]) { - throw new Error(`Cannot find entry point for subscriptions of type ${subscriptionName}`); - } + const subscriptions = this.subscriptions + .filter(subscription => subscription.name === subscriptionName); this.values[subscriptionName] = { error, data }; - Object.keys(this.subscriptions[subscriptionName]).forEach((subscriptionId) => { - const callback = this.subscriptions[subscriptionName][subscriptionId]; - this._sendData(subscriptionName, subscriptionId, callback, error, data); - }); + subscriptions + .forEach((subscription) => { + this._sendData(subscription.id, error, data); + }); } } diff --git a/js/src/api/subscriptions/manager.spec.js b/js/src/api/subscriptions/manager.spec.js index 4b6b84b84..5e434efec 100644 --- a/js/src/api/subscriptions/manager.spec.js +++ b/js/src/api/subscriptions/manager.spec.js @@ -20,6 +20,7 @@ import Manager, { events } from './manager'; function newStub () { const start = () => manager._updateSubscriptions(manager.__test, null, 'test'); + const manager = new Manager({ transport: { isConnected: true @@ -53,7 +54,7 @@ describe('api/subscriptions/manager', () => { describe('constructor', () => { it('sets up the subscription types & defaults', () => { - expect(Object.keys(manager.subscriptions)).to.deep.equal(Object.keys(events)); + expect(manager.subscriptions).to.be.an.array; expect(Object.keys(manager.values)).to.deep.equal(Object.keys(events)); }); }); @@ -74,6 +75,7 @@ describe('api/subscriptions/manager', () => { manager.__test = eventName; cb = sinon.stub(); sinon.spy(engine, 'start'); + return manager .subscribe(eventName, cb) .then((_subscriptionId) => { @@ -86,7 +88,7 @@ describe('api/subscriptions/manager', () => { }); it('returns a subscriptionId', () => { - expect(subscriptionId).to.be.ok; + expect(subscriptionId).to.be.a.number; }); it('calls the subscription callback with updated values', () => { @@ -95,4 +97,38 @@ describe('api/subscriptions/manager', () => { }); }); }); + + describe('unsubscriptions', () => { + Object + .keys(events) + .filter((eventName) => eventName.indexOf('_') !== -1) + .forEach((eventName) => { + const { module } = events[eventName]; + let engine; + let cb; + + describe(eventName, () => { + beforeEach(() => { + engine = manager[`_${module}`]; + manager.__test = eventName; + cb = sinon.stub(); + sinon.spy(engine, 'start'); + + return manager + .subscribe(eventName, cb) + .then((_subscriptionId) => { + manager.unsubscribe(_subscriptionId); + }) + .then(() => { + manager._updateSubscriptions(manager.__test, null, 'test2'); + }); + }); + + it('does not call the callback after unsibscription', () => { + expect(cb).to.have.been.calledWith(null, 'test'); + expect(cb).to.not.have.been.calledWith(null, 'test2'); + }); + }); + }); + }); }); diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index ecab2a5a2..119f4ba76 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -93,21 +93,30 @@ export default class Ws extends JsonRpcBase { } _onMessage = (event) => { - const result = JSON.parse(event.data); - const { method, params, json, resolve, reject } = this._messages[result.id]; - - Logging.send(method, params, { json, result }); - - if (result.error) { - this.error(event.data); - - reject(new Error(`${result.error.code}: ${result.error.message}`)); - delete this._messages[result.id]; - return; + // Event sent by Signer Broadcaster + if (event.data === 'new_message') { + return false; } - resolve(result.result); - delete this._messages[result.id]; + try { + const result = JSON.parse(event.data); + const { method, params, json, resolve, reject } = this._messages[result.id]; + + Logging.send(method, params, { json, result }); + + if (result.error) { + this.error(event.data); + + reject(new Error(`${result.error.code}: ${result.error.message}`)); + delete this._messages[result.id]; + return; + } + + resolve(result.result); + delete this._messages[result.id]; + } catch (e) { + console.error('ws::_onMessage', event.data, e); + } } _send = (id) => { diff --git a/js/src/dapps/basiccoin.html b/js/src/dapps/basiccoin.html index 9bcc368f3..7ac5cb3cb 100644 --- a/js/src/dapps/basiccoin.html +++ b/js/src/dapps/basiccoin.html @@ -10,7 +10,7 @@

- + diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index 1e268e720..d84085c98 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { api } from '../parity'; +// import { api } from '../parity'; import { attachInstances } from '../services'; import Header from './Header'; @@ -83,9 +83,10 @@ export default class Application extends Component { Promise .all([ attachInstances(), - api.personal.accountsInfo() + null // api.personal.accountsInfo() ]) .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { + accountsInfo = accountsInfo || {}; this.setState({ loading: false, managerInstance, diff --git a/js/src/dapps/basiccoin/Deploy/Events/events.js b/js/src/dapps/basiccoin/Deploy/Events/events.js index a21672a8e..4b51afb59 100644 --- a/js/src/dapps/basiccoin/Deploy/Events/events.js +++ b/js/src/dapps/basiccoin/Deploy/Events/events.js @@ -110,6 +110,10 @@ export default class Events extends Component { logToEvent = (log) => { log.key = api.util.sha3(JSON.stringify(log)); + log.params = Object.keys(log.params).reduce((params, name) => { + params[name] = log.params[name].value; + return params; + }, {}); return log; } diff --git a/js/src/dapps/basiccoin/Transfer/Events/events.js b/js/src/dapps/basiccoin/Transfer/Events/events.js index dcead03bb..101c77f73 100644 --- a/js/src/dapps/basiccoin/Transfer/Events/events.js +++ b/js/src/dapps/basiccoin/Transfer/Events/events.js @@ -114,6 +114,10 @@ export default class Events extends Component { logToEvent = (log) => { log.key = api.util.sha3(JSON.stringify(log)); + log.params = Object.keys(log.params).reduce((params, name) => { + params[name] = log.params[name].value; + return params; + }, {}); return log; } @@ -139,7 +143,6 @@ export default class Events extends Component { .concat(pendingEvents) .filter((log) => !minedNew.find((event) => event.transactionHash === log.transactionHash)); const events = [].concat(pendingNew).concat(minedNew); - console.log('*** events', events.map((event) => event.address)); this.setState({ loading: false, events, minedEvents: minedNew, pendingEvents: pendingNew }); } } diff --git a/js/src/dapps/gavcoin.html b/js/src/dapps/gavcoin.html index 928310a52..f777f2920 100644 --- a/js/src/dapps/gavcoin.html +++ b/js/src/dapps/gavcoin.html @@ -10,7 +10,7 @@
- + diff --git a/js/src/dapps/gavcoin/Application/application.js b/js/src/dapps/gavcoin/Application/application.js index 04c5abe01..29c86c78d 100644 --- a/js/src/dapps/gavcoin/Application/application.js +++ b/js/src/dapps/gavcoin/Application/application.js @@ -205,11 +205,12 @@ export default class Application extends Component { return Promise .all([ registry.getAddress.call({}, [api.util.sha3('gavcoin'), 'A']), - api.personal.listAccounts(), - api.personal.accountsInfo() + api.eth.accounts(), + null // api.personal.accountsInfo() ]); }) .then(([address, addresses, infos]) => { + infos = infos || {}; console.log(`gavcoin was found at ${address}`); const contract = api.newContract(abis.gavcoin, address); @@ -220,11 +221,11 @@ export default class Application extends Component { contract, instance: contract.instance, accounts: addresses.map((address) => { - const info = infos[address]; + const info = infos[address] || {}; return { address, - name: info.name || 'Unnamed', + name: info.name, uuid: info.uuid }; }) diff --git a/js/src/dapps/gavcoin/Events/Event/event.js b/js/src/dapps/gavcoin/Events/Event/event.js index 4fb29a382..0b4094ac0 100644 --- a/js/src/dapps/gavcoin/Events/Event/event.js +++ b/js/src/dapps/gavcoin/Events/Event/event.js @@ -80,7 +80,7 @@ export default class Event extends Component { const { accounts } = this.context; const account = accounts.find((_account) => _account.address === address); - if (account) { + if (account && account.name) { return (
{ account.name } diff --git a/js/src/dapps/gavcoin/Events/events.js b/js/src/dapps/gavcoin/Events/events.js index cb287b3a7..ba71d6541 100644 --- a/js/src/dapps/gavcoin/Events/events.js +++ b/js/src/dapps/gavcoin/Events/events.js @@ -106,7 +106,10 @@ export default class Events extends Component { logIndex, transactionHash, transactionIndex, - params, + params: Object.keys(params).reduce((data, name) => { + data[name] = params[name].value; + return data; + }, {}), key }; }; diff --git a/js/src/dapps/gavcoin/Loading/loading.js b/js/src/dapps/gavcoin/Loading/loading.js index 9e00cc8f1..78aaa8828 100644 --- a/js/src/dapps/gavcoin/Loading/loading.js +++ b/js/src/dapps/gavcoin/Loading/loading.js @@ -24,7 +24,7 @@ export default class Loading extends Component { render () { return (
- +
); } diff --git a/js/src/dapps/githubhint.html b/js/src/dapps/githubhint.html index 0084dd051..085b15953 100644 --- a/js/src/dapps/githubhint.html +++ b/js/src/dapps/githubhint.html @@ -10,7 +10,7 @@
- + diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index ee198ff6d..1904be2d7 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -28,21 +28,22 @@ export function attachInterface () { return Promise .all([ registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), - api.personal.listAccounts(), - api.personal.accountsInfo() + api.eth.accounts(), + null // api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { + accountsInfo = accountsInfo || {}; console.log(`githubhint was found at ${address}`); const contract = api.newContract(abis.githubhint, address); const accounts = addresses.reduce((obj, address) => { - const info = accountsInfo[address]; + const info = accountsInfo[address] || {}; return Object.assign(obj, { [address]: { address, - name: info.name || 'Unnamed', + name: info.name, uuid: info.uuid } }); diff --git a/js/src/dapps/registry.html b/js/src/dapps/registry.html index 21b09dc12..83c5e8c9b 100644 --- a/js/src/dapps/registry.html +++ b/js/src/dapps/registry.html @@ -10,7 +10,7 @@
- + diff --git a/js/src/dapps/registry/Application/application.js b/js/src/dapps/registry/Application/application.js index 3d3d8d582..d0c4bd2f7 100644 --- a/js/src/dapps/registry/Application/application.js +++ b/js/src/dapps/registry/Application/application.js @@ -79,7 +79,7 @@ export default class Application extends Component {

) : ( - + ) } ); diff --git a/js/src/dapps/registry/Events/events.js b/js/src/dapps/registry/Events/events.js index ffb1fc919..10280ae52 100644 --- a/js/src/dapps/registry/Events/events.js +++ b/js/src/dapps/registry/Events/events.js @@ -48,9 +48,9 @@ const renderEvent = (classNames, verb) => (e, accounts, contacts) => { return ( - { renderAddress(e.parameters.owner, accounts, contacts) } + { renderAddress(e.parameters.owner.value, accounts, contacts) } { verb } - { renderHash(bytesToHex(e.parameters.name)) } + { renderHash(bytesToHex(e.parameters.name.value)) } { renderStatus(e.timestamp, e.state === 'pending') } ); @@ -64,10 +64,10 @@ const renderDataChanged = (e, accounts, contacts) => { return ( - { renderAddress(e.parameters.owner, accounts, contacts) } + { renderAddress(e.parameters.owner.value, accounts, contacts) } updated - key { new Buffer(e.parameters.plainKey).toString('utf8') } of { renderHash(bytesToHex(e.parameters.name)) } + key { new Buffer(e.parameters.plainKey.value).toString('utf8') } of { renderHash(bytesToHex(e.parameters.name.value)) } { renderStatus(e.timestamp, e.state === 'pending') } diff --git a/js/src/dapps/registry/addresses/actions.js b/js/src/dapps/registry/addresses/actions.js index b6091acb5..dfd7d16a3 100644 --- a/js/src/dapps/registry/addresses/actions.js +++ b/js/src/dapps/registry/addresses/actions.js @@ -21,16 +21,13 @@ export const set = (addresses) => ({ type: 'addresses set', addresses }); export const fetch = () => (dispatch) => { return Promise .all([ - api.personal.listAccounts(), - api.personal.accountsInfo() + api.eth.accounts(), + null // api.personal.accountsInfo() ]) .then(([ accounts, data ]) => { - const addresses = Object.keys(data) - .filter((address) => data[address] && !data[address].meta.deleted) - .map((address) => ({ - ...data[address], address, - isAccount: accounts.includes(address) - })); + const addresses = accounts.map((address) => { + return { address, isAccount: true }; + }); dispatch(set(addresses)); }) .catch((error) => { diff --git a/js/src/dapps/registry/ui/address.js b/js/src/dapps/registry/ui/address.js index e4a5a953e..f0b9d65da 100644 --- a/js/src/dapps/registry/ui/address.js +++ b/js/src/dapps/registry/ui/address.js @@ -32,9 +32,9 @@ const align = { export default (address, accounts, contacts, shortenHash = true) => { let caption; if (accounts[address]) { - caption = ({ accounts[address].name }); + caption = ({ accounts[address].name || address }); } else if (contacts[address]) { - caption = ({ contacts[address].name }); + caption = ({ contacts[address].name || address }); } else { caption = ({ shortenHash ? renderHash(address) : address }); } diff --git a/js/src/dapps/registry/ui/image.js b/js/src/dapps/registry/ui/image.js index 7e7a52a88..e0101e1f2 100644 --- a/js/src/dapps/registry/ui/image.js +++ b/js/src/dapps/registry/ui/image.js @@ -16,6 +16,8 @@ import React from 'react'; +import { parityNode } from '../../../environment'; + const styles = { padding: '.5em', border: '1px solid #777' @@ -23,7 +25,7 @@ const styles = { export default (address) => ( { diff --git a/js/src/dapps/signaturereg.html b/js/src/dapps/signaturereg.html index 3f74be28a..be62400d2 100644 --- a/js/src/dapps/signaturereg.html +++ b/js/src/dapps/signaturereg.html @@ -10,7 +10,7 @@
- + diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index 3963d394c..7219ddff1 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -30,7 +30,10 @@ const logToEvent = (log) => { logIndex, transactionHash, transactionIndex, - params, + params: Object.keys(params).reduce((data, name) => { + data[name] = params[name].value; + return data; + }, {}), key }; }; @@ -46,16 +49,17 @@ export function attachInterface (callback) { return Promise .all([ registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), - api.personal.listAccounts(), - api.personal.accountsInfo() + api.eth.accounts(), + null // api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { + accountsInfo = accountsInfo || {}; console.log(`signaturereg was found at ${address}`); const contract = api.newContract(abis.signaturereg, address); const accounts = addresses.reduce((obj, address) => { - const info = accountsInfo[address]; + const info = accountsInfo[address] || {}; return Object.assign(obj, { [address]: { diff --git a/js/src/dapps/tokenreg.html b/js/src/dapps/tokenreg.html index ecb03d005..bcf04e298 100644 --- a/js/src/dapps/tokenreg.html +++ b/js/src/dapps/tokenreg.html @@ -10,7 +10,7 @@
- + diff --git a/js/src/dapps/tokenreg/Accounts/actions.js b/js/src/dapps/tokenreg/Accounts/actions.js index 2e597f2e5..f093b5300 100644 --- a/js/src/dapps/tokenreg/Accounts/actions.js +++ b/js/src/dapps/tokenreg/Accounts/actions.js @@ -37,11 +37,11 @@ export const setSelectedAccount = (address) => ({ export const loadAccounts = () => (dispatch) => { Promise .all([ - api.personal.listAccounts(), - api.personal.accountsInfo() + api.eth.accounts(), + null // api.personal.accountsInfo() ]) - .then(results => { - const [ accounts, accountsInfo ] = results; + .then(([ accounts, accountsInfo ]) => { + accountsInfo = accountsInfo || {}; const accountsList = accounts .map(address => ({ diff --git a/js/src/dapps/tokenreg/Loading/loading.js b/js/src/dapps/tokenreg/Loading/loading.js index 3b7619323..bdcc98df6 100644 --- a/js/src/dapps/tokenreg/Loading/loading.js +++ b/js/src/dapps/tokenreg/Loading/loading.js @@ -25,9 +25,11 @@ export default class Loading extends Component { }; render () { + const size = (this.props.size || 2) * 60; + return (
- +
); } diff --git a/js/src/dapps/tokenreg/Status/actions.js b/js/src/dapps/tokenreg/Status/actions.js index a479179b2..b7de9c108 100644 --- a/js/src/dapps/tokenreg/Status/actions.js +++ b/js/src/dapps/tokenreg/Status/actions.js @@ -82,7 +82,7 @@ export const loadContractDetails = () => (dispatch, getState) => { Promise .all([ - api.personal.listAccounts(), + api.eth.accounts(), instance.owner.call(), instance.fee.call() ]) @@ -148,27 +148,27 @@ export const subscribeEvents = () => (dispatch, getState) => { return dispatch(setTokenData(params.id.toNumber(), { tla: '...', base: -1, - address: params.addr, - name: params.name, + address: params.addr.value, + name: params.name.value, isPending: true })); } if (event === 'Registered' && type === 'mined') { - return dispatch(loadToken(params.id.toNumber())); + return dispatch(loadToken(params.id.value.toNumber())); } if (event === 'Unregistered' && type === 'pending') { - return dispatch(setTokenPending(params.id.toNumber(), true)); + return dispatch(setTokenPending(params.id.value.toNumber(), true)); } if (event === 'Unregistered' && type === 'mined') { - return dispatch(deleteToken(params.id.toNumber())); + return dispatch(deleteToken(params.id.value.toNumber())); } if (event === 'MetaChanged' && type === 'pending') { return dispatch(setTokenData( - params.id.toNumber(), + params.id.value.toNumber(), { metaPending: true, metaMined: false } )); } @@ -176,13 +176,13 @@ export const subscribeEvents = () => (dispatch, getState) => { if (event === 'MetaChanged' && type === 'mined') { setTimeout(() => { dispatch(setTokenData( - params.id.toNumber(), + params.id.value.toNumber(), { metaPending: false, metaMined: false } )); }, 5000); return dispatch(setTokenData( - params.id.toNumber(), + params.id.value.toNumber(), { metaPending: false, metaMined: true } )); } diff --git a/js/src/dapps/tokenreg/Tokens/Token/token.js b/js/src/dapps/tokenreg/Tokens/Token/token.js index bee925689..18942b085 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/token.js +++ b/js/src/dapps/tokenreg/Tokens/Token/token.js @@ -30,6 +30,7 @@ import styles from './token.css'; import { metaDataKeys } from '../../constants'; import { api } from '../../parity'; +import { parityNode } from '../../../../environment'; export default class Token extends Component { static propTypes = { @@ -267,7 +268,7 @@ export default class Token extends Component { meta-data:

- +
); } diff --git a/js/src/dev.parity.html b/js/src/dev.parity.html index 20b8e965f..56811f7c2 100644 --- a/js/src/dev.parity.html +++ b/js/src/dev.parity.html @@ -7,6 +7,6 @@ dev::Parity.js - + diff --git a/js/src/dev.web3.html b/js/src/dev.web3.html index 97a47eb72..e55e0109b 100644 --- a/js/src/dev.web3.html +++ b/js/src/dev.web3.html @@ -7,6 +7,6 @@ dev::Web3 - + diff --git a/js/src/environment/index.js b/js/src/environment/index.js index d8e81f343..9b95bb0da 100644 --- a/js/src/environment/index.js +++ b/js/src/environment/index.js @@ -18,3 +18,15 @@ // import './perf-debug'; import './tests'; + +const parityNode = ( + process.env.PARITY_URL && `http://${process.env.PARITY_URL}` + ) || ( + process.env.NODE_ENV === 'production' + ? 'http://127.0.0.1:8080' + : '' + ); + +export { + parityNode +}; diff --git a/js/src/index.js b/js/src/index.js index 08b02c2a3..24649b026 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -17,12 +17,6 @@ import 'babel-polyfill'; import 'whatwg-fetch'; -// redirect when not on 127.0.0.1:8180 -const host = `${window.location.hostname}:${window.location.port}`; -if (host === '127.0.0.1:8080' || host === 'localhost:8080') { - window.location = 'http://127.0.0.1:8180'; -} - import es6Promise from 'es6-promise'; es6Promise.polyfill(); diff --git a/js/src/jsonrpc/interfaces/ethcore.js b/js/src/jsonrpc/interfaces/ethcore.js index 5b2910618..4ebff1952 100644 --- a/js/src/jsonrpc/interfaces/ethcore.js +++ b/js/src/jsonrpc/interfaces/ethcore.js @@ -40,6 +40,15 @@ export default { } }, + dappsPort: { + desc: 'Returns the port the dapps are running on, error if not enabled', + params: [], + returns: { + type: Quantity, + desc: 'The port number' + } + }, + defaultExtraData: { desc: 'Returns the default extra data', params: [], @@ -294,6 +303,15 @@ export default { } }, + signerPort: { + desc: 'Returns the port the signer is running on, error if not enabled', + params: [], + returns: { + type: Quantity, + desc: 'The port number' + } + }, + transactionsLimit: { desc: 'Changes limit for transactions in queue.', params: [], diff --git a/ethcore/src/common.rs b/js/src/lib.rs similarity index 75% rename from ethcore/src/common.rs rename to js/src/lib.rs index 41fdd5397..25d336fab 100644 --- a/ethcore/src/common.rs +++ b/js/src/lib.rs @@ -14,14 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -pub use util::*; -pub use basic_types::*; -pub use error::*; -pub use env_info::*; -pub use views::*; -pub use builtin::*; -pub use header::*; -pub use transaction::*; -pub use log_entry::*; -pub use receipt::*; -pub use action_params::*; +#[cfg(feature = "with-syntex")] +include!(concat!(env!("OUT_DIR"), "/lib.rs")); + +#[cfg(not(feature = "with-syntex"))] +include!("lib.rs.in"); + diff --git a/dapps/ui/src/lib.rs.in b/js/src/lib.rs.in similarity index 88% rename from dapps/ui/src/lib.rs.in rename to js/src/lib.rs.in index 319d17ed3..b3f09556a 100644 --- a/dapps/ui/src/lib.rs.in +++ b/js/src/lib.rs.in @@ -20,7 +20,7 @@ use std::collections::HashMap; use parity_dapps_glue::{WebApp, File, Info}; #[derive(WebAppFiles)] -#[webapp(path = "../../../js/build")] +#[webapp(path = "../build")] pub struct App { pub files: HashMap<&'static str, File>, } @@ -43,7 +43,7 @@ impl WebApp for App { name: "Parity UI", version: env!("CARGO_PKG_VERSION"), author: "Ethcore ", - description: "New UI for Parity. (Experimental)", + description: "New UI for Parity.", icon_url: "icon.png", } } @@ -51,5 +51,5 @@ impl WebApp for App { #[test] fn test_js() { - parity_dapps_glue::js::build(concat!(env!("CARGO_MANIFEST_DIR"), "/../../js")); + parity_dapps_glue::js::build(env!("CARGO_MANIFEST_DIR")); } diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index 103208945..afcf9b127 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -36,7 +36,7 @@ export default class EditMeta extends Component { } state = { - meta: this.props.account.meta, + meta: Object.assign({}, this.props.account.meta), metaErrors: {}, name: this.props.account.name, nameError: null @@ -102,20 +102,55 @@ export default class EditMeta extends Component { renderTags () { const { meta } = this.state; const { tags } = meta || []; - const onChange = (chips) => this.onMetaChange('tags', chips); return ( ); } + onAddTag = (tag) => { + const { meta } = this.state; + const { tags } = meta || []; + + this.onMetaChange('tags', [].concat(tags, tag)); + } + + onDeleteTag = (tag) => { + const { meta } = this.state; + const { tags } = meta || []; + + const newTags = tags + .filter(t => t !== tag); + + this.onMetaChange('tags', newTags); + } + + onTagsInputChange = (value) => { + const { meta } = this.state; + const { tags = [] } = meta; + + const tokens = value.split(/[\s,;]+/); + + const newTokens = tokens + .slice(0, -1) + .filter(t => t.length > 0); + + const inputValue = tokens.slice(-1)[0].trim(); + + this.onMetaChange('tags', [].concat(tags, newTokens)); + this.refs.tagsInput.setState({ inputValue }); + } + onNameChange = (name) => { this.setState(validateName(name)); } diff --git a/js/src/modals/PasswordManager/index.js b/js/src/modals/PasswordManager/index.js new file mode 100644 index 000000000..9676de163 --- /dev/null +++ b/js/src/modals/PasswordManager/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './passwordManager.js'; diff --git a/js/src/modals/PasswordManager/passwordManager.css b/js/src/modals/PasswordManager/passwordManager.css new file mode 100644 index 000000000..aa0f72cb3 --- /dev/null +++ b/js/src/modals/PasswordManager/passwordManager.css @@ -0,0 +1,73 @@ +/* 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 . +*/ + +.accountContainer { + display: flex; + flex-direction: row; + margin-bottom: 1.5rem; +} + +.accountInfos { + display: flex; + flex-direction: column; + justify-content: space-around; +} + +.accountInfos > * { + margin: 0.25rem 0; +} + +.hintLabel { + text-transform: uppercase; + font-size: 0.7rem; + margin-right: 0.5rem; +} + +.accountAddress { + font-family: monospace; + font-size: 0.9rem; +} + +.accountName { + font-size: 1.1rem; +} + +.passwordHint { + font-size: 0.9rem; + color: lightgrey; +} + +.message { + margin-top: 1rem; + width: 100%; + height: 2.5rem; + text-align: center; + line-height: 2.5rem; + transition: height 350ms 0 !important; + overflow: hidden; +} + +.hideMessage { + height: 0; + background-color: transparent !important; +} + +.form { + margin-top: 0; + padding: 0 0.5rem 1rem; + background-color: rgba(255, 255, 255, 0.05); +} diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js new file mode 100644 index 000000000..800fba929 --- /dev/null +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -0,0 +1,383 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import ContentClear from 'material-ui/svg-icons/content/clear'; +import CheckIcon from 'material-ui/svg-icons/navigation/check'; +import SendIcon from 'material-ui/svg-icons/content/send'; + +import { Tabs, Tab } from 'material-ui/Tabs'; +import Paper from 'material-ui/Paper'; + +import Form, { Input } from '../../ui/Form'; +import { Button, Modal, IdentityName, IdentityIcon } from '../../ui'; + +import styles from './passwordManager.css'; + +const TEST_ACTION = 'TEST_ACTION'; +const CHANGE_ACTION = 'CHANGE_ACTION'; + +export default class PasswordManager extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + } + + static propTypes = { + account: PropTypes.object.isRequired, + onClose: PropTypes.func + } + + state = { + action: TEST_ACTION, + waiting: false, + showMessage: false, + message: { value: '', success: true }, + currentPass: '', + newPass: '', + repeatNewPass: '', + repeatValid: true, + passwordHint: this.props.account.meta && this.props.account.meta.passwordHint || '' + } + + render () { + return ( + + { this.renderAccount() } + { this.renderPage() } + { this.renderMessage() } + + ); + } + + renderMessage () { + const { message, showMessage } = this.state; + + const style = message.success + ? { + backgroundColor: 'rgba(174, 213, 129, 0.75)' + } + : { + backgroundColor: 'rgba(229, 115, 115, 0.75)' + }; + + const classes = [ styles.message ]; + + if (!showMessage) { + classes.push(styles.hideMessage); + } + + return ( + + { message.value } + + ); + } + + renderAccount () { + const { account } = this.props; + const { address, meta } = account; + + const passwordHint = meta && meta.passwordHint + ? ( + + Hint + { meta.passwordHint } + + ) + : null; + + return ( +
+ +
+ + + { address } + + { passwordHint } +
+
+ ); + } + + renderPage () { + const { account } = this.props; + const { waiting, repeatValid } = this.state; + const disabled = !!waiting; + + const repeatError = repeatValid + ? null + : 'the two passwords differ'; + + const { meta } = account; + const passwordHint = meta && meta.passwordHint || ''; + + return ( + + +
+
+ +
+
+
+ +
+
+ + + + + + +
+
+
+
+ ); + } + + renderDialogActions () { + const { onClose } = this.props; + const { action, waiting, repeatValid } = this.state; + + const cancelBtn = ( +