Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2e6fc0a65 | ||
|
|
29d50f8f30 | ||
|
|
536cf73db8 | ||
|
|
f15a3ca977 | ||
|
|
096a44d9bf | ||
|
|
ff3ae33d99 | ||
|
|
8e6d09e6ac | ||
|
|
516e16a36b | ||
|
|
77b14b2736 | ||
|
|
e01636fc34 | ||
|
|
7b2cfd1cfb | ||
|
|
cf6d870b09 | ||
|
|
f20db41169 | ||
|
|
6830273cb9 | ||
|
|
65594b8865 | ||
|
|
5b30a61158 | ||
|
|
0bc2aeca8d | ||
|
|
1478109c43 | ||
|
|
bbd2bd0e17 | ||
|
|
1e212771b5 | ||
|
|
72bb687f5e | ||
|
|
5e70507c78 | ||
|
|
2b588d57f0 | ||
|
|
e7fbf10819 |
@@ -38,7 +38,7 @@ linux-stable:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
tags:
|
||||
- rust
|
||||
@@ -107,7 +107,7 @@ linux-centos:
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -146,7 +146,7 @@ linux-i686:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -192,7 +192,7 @@ linux-armv7:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -238,7 +238,7 @@ linux-arm:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -252,10 +252,10 @@ linux-armv6:
|
||||
stage: build
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
- beta
|
||||
# - beta
|
||||
# - tags
|
||||
# - stable
|
||||
# - triggers
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabi-gcc
|
||||
- export CXX=arm-linux-gnueabi-g++
|
||||
@@ -277,7 +277,7 @@ linux-armv6:
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -322,7 +322,7 @@ linux-aarch64:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@@ -359,7 +359,7 @@ darwin:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- osx
|
||||
@@ -421,7 +421,7 @@ windows:
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
|
||||
# - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
tags:
|
||||
- rust-windows
|
||||
@@ -529,13 +529,11 @@ js-release:
|
||||
push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
image: ethcore/rust:stable
|
||||
script:
|
||||
# - curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
tags:
|
||||
- curl
|
||||
|
||||
53
Cargo.lock
generated
53
Cargo.lock
generated
@@ -390,6 +390,7 @@ version = "0.1.2"
|
||||
dependencies = [
|
||||
"bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -413,8 +414,8 @@ dependencies = [
|
||||
"fetch 0.1.0",
|
||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -582,10 +583,10 @@ dependencies = [
|
||||
"ethsync 1.5.0",
|
||||
"fetch 0.1.0",
|
||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-reactor 0.1.0",
|
||||
"parity-updater 1.5.0",
|
||||
@@ -610,7 +611,7 @@ dependencies = [
|
||||
"ethcore-io 1.5.0",
|
||||
"ethcore-rpc 1.5.0",
|
||||
"ethcore-util 1.5.0",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-ui 1.5.0",
|
||||
@@ -629,8 +630,8 @@ dependencies = [
|
||||
"ethcore-ipc-codegen 1.5.0",
|
||||
"ethcore-ipc-nano 1.5.0",
|
||||
"ethcore-util 1.5.0",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)",
|
||||
@@ -879,7 +880,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.10.0-a.0"
|
||||
source = "git+https://github.com/ethcore/hyper#7d4f7fa0baddcb2b0c523f7c05855d67de94fe88"
|
||||
source = "git+https://github.com/ethcore/hyper#6baea9d444dd1652220ee9b4aeadaebb3de6a955"
|
||||
dependencies = [
|
||||
"cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -951,7 +952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "jsonrpc-core"
|
||||
version = "4.0.0"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio-old#057cc58a849d18933a9cce60a33559c7505bd7ce"
|
||||
dependencies = [
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -963,10 +964,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jsonrpc-http-server"
|
||||
version = "6.1.1"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio-old#057cc58a849d18933a9cce60a33559c7505bd7ce"
|
||||
dependencies = [
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -974,11 +975,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jsonrpc-ipc-server"
|
||||
version = "0.2.4"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio-old#057cc58a849d18933a9cce60a33559c7505bd7ce"
|
||||
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)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -989,20 +990,20 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "jsonrpc-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio-old#057cc58a849d18933a9cce60a33559c7505bd7ce"
|
||||
dependencies = [
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc-tcp-server"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||
source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio-old#057cc58a849d18933a9cce60a33559c7505bd7ce"
|
||||
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)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1470,7 +1471,7 @@ dependencies = [
|
||||
"ethcore-signer 1.5.0",
|
||||
"ethcore-util 1.5.0",
|
||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1501,7 +1502,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
version = "1.4.0"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git#66ef86da6f741953694199813e1eb6b76b3f5b5f"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git#35f7fd8782c82081f992fae7f266ce61cb0c95cd"
|
||||
dependencies = [
|
||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -2414,11 +2415,11 @@ dependencies = [
|
||||
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
|
||||
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
|
||||
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
||||
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)" = "<none>"
|
||||
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)" = "<none>"
|
||||
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)" = "<none>"
|
||||
"checksum jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)" = "<none>"
|
||||
"checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio-old)" = "<none>"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
|
||||
|
||||
@@ -13,8 +13,8 @@ rand = "0.3"
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
futures = "0.1"
|
||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||
unicase = "1.3"
|
||||
url = "1.0"
|
||||
|
||||
@@ -34,7 +34,7 @@ use endpoint::EndpointPath;
|
||||
use handlers::{ContentHandler, StreamingHandler};
|
||||
use page::{LocalPageEndpoint, PageHandlerWaiting};
|
||||
|
||||
const FETCH_TIMEOUT: u64 = 30;
|
||||
const FETCH_TIMEOUT: u64 = 300;
|
||||
|
||||
pub enum ValidatorResponse {
|
||||
Local(LocalPageEndpoint),
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"accountStartNonce": "0x0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x3"
|
||||
"networkID" : "0x3",
|
||||
"forkBlock": 333922,
|
||||
"forkCanonHash": "0x8737eb141d4f05db57af63fc8d3b4d4d8f9cddb0c4e1ab855de8c288fdc1924f"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
|
||||
@@ -1329,6 +1329,7 @@ mod tests {
|
||||
use transaction::{Transaction, Action};
|
||||
use log_entry::{LogEntry, LocalizedLogEntry};
|
||||
use spec::Spec;
|
||||
use ethkey::Secret;
|
||||
|
||||
fn new_db(path: &str) -> Arc<Database> {
|
||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
||||
@@ -1467,6 +1468,10 @@ mod tests {
|
||||
// TODO: insert block that already includes one of them as an uncle to check it's not allowed.
|
||||
}
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork_transaction_addresses() {
|
||||
let mut canon_chain = ChainGenerator::default();
|
||||
@@ -1482,7 +1487,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
|
||||
let b1a = canon_chain
|
||||
@@ -1546,7 +1551,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t2 = Transaction {
|
||||
nonce: 1.into(),
|
||||
@@ -1555,7 +1560,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t3 = Transaction {
|
||||
nonce: 2.into(),
|
||||
@@ -1564,7 +1569,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let b1a = canon_chain
|
||||
.with_transaction(t1.clone())
|
||||
@@ -1870,7 +1875,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 101.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t2 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@@ -1878,7 +1883,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 102.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t3 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@@ -1886,7 +1891,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 103.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let tx_hash1 = t1.hash();
|
||||
let tx_hash2 = t2.hash();
|
||||
let tx_hash3 = t3.hash();
|
||||
|
||||
@@ -873,6 +873,83 @@ impl BlockChainClient for Client {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
|
||||
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
|
||||
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
|
||||
let last_hashes = self.build_last_hashes(header.parent_hash());
|
||||
let env_info = EnvInfo {
|
||||
number: header.number(),
|
||||
author: header.author(),
|
||||
timestamp: header.timestamp(),
|
||||
difficulty: header.difficulty(),
|
||||
last_hashes: last_hashes,
|
||||
gas_used: U256::zero(),
|
||||
gas_limit: UPPER_CEILING.into(),
|
||||
};
|
||||
// that's just a copy of the state.
|
||||
let original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
|
||||
let sender = t.sender().map_err(|e| {
|
||||
let message = format!("Transaction malformed: {:?}", e);
|
||||
ExecutionError::TransactionMalformed(message)
|
||||
})?;
|
||||
let balance = original_state.balance(&sender);
|
||||
let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false };
|
||||
let mut tx = t.clone();
|
||||
|
||||
let mut cond = |gas| {
|
||||
tx.gas = gas;
|
||||
|
||||
let mut state = original_state.clone();
|
||||
let needed_balance = tx.value + tx.gas * tx.gas_price;
|
||||
if balance < needed_balance {
|
||||
// give the sender a sufficient balance
|
||||
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
|
||||
}
|
||||
|
||||
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
|
||||
.transact(&tx, options.clone())
|
||||
.map(|r| r.exception.is_none())
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
let mut upper = header.gas_limit();
|
||||
if !cond(upper) {
|
||||
// impossible at block gas limit - try `UPPER_CEILING` instead.
|
||||
// TODO: consider raising limit by powers of two.
|
||||
upper = UPPER_CEILING.into();
|
||||
if !cond(upper) {
|
||||
trace!(target: "estimate_gas", "estimate_gas failed with {}", upper);
|
||||
return Err(CallError::Execution(ExecutionError::Internal))
|
||||
}
|
||||
}
|
||||
let lower = t.gas_required(&self.engine.schedule(&env_info)).into();
|
||||
if cond(lower) {
|
||||
trace!(target: "estimate_gas", "estimate_gas succeeded with {}", lower);
|
||||
return Ok(lower)
|
||||
}
|
||||
|
||||
/// Find transition point between `lower` and `upper` where `cond` changes from `false` to `true`.
|
||||
/// Returns the lowest value between `lower` and `upper` for which `cond` returns true.
|
||||
/// We assert: `cond(lower) = false`, `cond(upper) = true`
|
||||
fn binary_chop<F>(mut lower: U256, mut upper: U256, mut cond: F) -> U256 where F: FnMut(U256) -> bool {
|
||||
while upper - lower > 1.into() {
|
||||
let mid = (lower + upper) / 2.into();
|
||||
trace!(target: "estimate_gas", "{} .. {} .. {}", lower, mid, upper);
|
||||
let c = cond(mid);
|
||||
match c {
|
||||
true => upper = mid,
|
||||
false => lower = mid,
|
||||
};
|
||||
trace!(target: "estimate_gas", "{} => {} .. {}", c, lower, upper);
|
||||
}
|
||||
upper
|
||||
}
|
||||
|
||||
// binary chop to non-excepting call with gas somewhere between 21000 and block gas limit
|
||||
trace!(target: "estimate_gas", "estimate_gas chopping {} .. {}", lower, upper);
|
||||
Ok(binary_chop(lower, upper, cond))
|
||||
}
|
||||
|
||||
fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError> {
|
||||
let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?;
|
||||
let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?;
|
||||
@@ -1594,7 +1671,7 @@ mod tests {
|
||||
use util::Hashable;
|
||||
|
||||
// given
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret_slice(&"test".sha3()).unwrap();
|
||||
let secret = key.secret();
|
||||
|
||||
let block_number = 1;
|
||||
|
||||
@@ -242,7 +242,7 @@ impl TestBlockChainClient {
|
||||
value: U256::from(100),
|
||||
data: "3331600055".from_hex().unwrap(),
|
||||
gas: U256::from(100_000),
|
||||
gas_price: U256::one(),
|
||||
gas_price: U256::from(200_000_000_000u64),
|
||||
nonce: U256::zero()
|
||||
};
|
||||
let signed_tx = tx.sign(keypair.secret(), None);
|
||||
@@ -308,11 +308,11 @@ impl TestBlockChainClient {
|
||||
value: U256::from(100),
|
||||
data: "3331600055".from_hex().unwrap(),
|
||||
gas: U256::from(100_000),
|
||||
gas_price: U256::one(),
|
||||
gas_price: U256::from(20_000_000_000u64),
|
||||
nonce: U256::zero()
|
||||
};
|
||||
let signed_tx = tx.sign(keypair.secret(), None);
|
||||
self.set_balance(signed_tx.sender().unwrap(), 10_000_000.into());
|
||||
self.set_balance(signed_tx.sender().unwrap(), 10_000_000_000_000_000_000u64.into());
|
||||
let res = self.miner.import_external_transactions(self, vec![signed_tx]);
|
||||
let res = res.into_iter().next().unwrap().expect("Successful import");
|
||||
assert_eq!(res, TransactionImportResult::Current);
|
||||
@@ -379,6 +379,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
self.execution_result.read().clone().unwrap()
|
||||
}
|
||||
|
||||
fn estimate_gas(&self, _t: &SignedTransaction, _block: BlockId) -> Result<U256, CallError> {
|
||||
Ok(21000.into())
|
||||
}
|
||||
|
||||
fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result<Executed, CallError> {
|
||||
self.execution_result.read().clone().unwrap()
|
||||
}
|
||||
|
||||
@@ -184,6 +184,9 @@ pub trait BlockChainClient : Sync + Send {
|
||||
/// Makes a non-persistent transaction call.
|
||||
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError>;
|
||||
|
||||
/// Estimates how much gas will be necessary for a call.
|
||||
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError>;
|
||||
|
||||
/// Replays a given transaction for inspection.
|
||||
fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError>;
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ mod tests {
|
||||
use env_info::EnvInfo;
|
||||
use header::Header;
|
||||
use error::{Error, BlockError};
|
||||
use ethkey::Secret;
|
||||
use rlp::encode;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
@@ -411,8 +412,8 @@ mod tests {
|
||||
#[test]
|
||||
fn generates_seal_and_does_not_double_propose() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr1 = tap.insert_account("1".sha3(), "1").unwrap();
|
||||
let addr2 = tap.insert_account("2".sha3(), "2").unwrap();
|
||||
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
|
||||
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
|
||||
|
||||
let spec = Spec::new_test_round();
|
||||
let engine = &*spec.engine;
|
||||
@@ -445,7 +446,7 @@ mod tests {
|
||||
fn proposer_switching() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
@@ -464,7 +465,7 @@ mod tests {
|
||||
fn rejects_future_block() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ mod tests {
|
||||
use error::{BlockError, Error};
|
||||
use tests::helpers::*;
|
||||
use account_provider::AccountProvider;
|
||||
use ethkey::Secret;
|
||||
use header::Header;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
@@ -261,7 +262,7 @@ mod tests {
|
||||
#[test]
|
||||
fn can_generate_seal() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("".sha3(), "").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &*spec.engine;
|
||||
@@ -281,7 +282,7 @@ mod tests {
|
||||
#[test]
|
||||
fn seals_internally() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let authority = tap.insert_account("".sha3(), "").unwrap();
|
||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.is_sealer(&Address::default()).unwrap());
|
||||
|
||||
@@ -23,15 +23,35 @@ use header::Header;
|
||||
use rlp::*;
|
||||
use ethkey::{recover, public_to_address};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
/// Message transmitted between consensus participants.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct ConsensusMessage {
|
||||
pub vote_step: VoteStep,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
pub signature: H520,
|
||||
}
|
||||
|
||||
/// Complete step of the consensus process.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct VoteStep {
|
||||
pub height: Height,
|
||||
pub round: Round,
|
||||
pub step: Step,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
}
|
||||
|
||||
impl VoteStep {
|
||||
pub fn new(height: Height, round: Round, step: Step) -> Self {
|
||||
VoteStep { height: height, round: round, step: step }
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
}
|
||||
|
||||
fn consensus_round(header: &Header) -> Result<Round, ::rlp::DecoderError> {
|
||||
let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed");
|
||||
@@ -42,53 +62,29 @@ impl ConsensusMessage {
|
||||
pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Self {
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: height,
|
||||
round: round,
|
||||
step: step,
|
||||
block_hash: block_hash,
|
||||
vote_step: VoteStep::new(height, round, step),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
||||
Ok(ConsensusMessage {
|
||||
vote_step: VoteStep::new(header.number() as Height, consensus_round(header)?, Step::Propose),
|
||||
signature: UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()?,
|
||||
height: header.number() as Height,
|
||||
round: consensus_round(header)?,
|
||||
step: Step::Propose,
|
||||
block_hash: Some(header.bare_hash()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_commit(proposal: &ConsensusMessage, signature: H520) -> Self {
|
||||
let mut vote_step = proposal.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: proposal.height,
|
||||
round: proposal.round,
|
||||
step: Step::Precommit,
|
||||
vote_step: vote_step,
|
||||
block_hash: proposal.block_hash,
|
||||
signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
|
||||
pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool {
|
||||
self.height == height && self.round == round && self.step == step
|
||||
}
|
||||
|
||||
pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option<BlockHash>) -> bool {
|
||||
self.height == h && self.round == r && self.step == s && self.block_hash == block_hash
|
||||
}
|
||||
|
||||
pub fn is_aligned(&self, m: &ConsensusMessage) -> bool {
|
||||
self.is_block_hash(m.height, m.round, m.step, m.block_hash)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<Address, Error> {
|
||||
let full_rlp = ::rlp::encode(self);
|
||||
let block_info = Rlp::new(&full_rlp).at(1);
|
||||
@@ -97,16 +93,30 @@ impl ConsensusMessage {
|
||||
}
|
||||
|
||||
pub fn precommit_hash(&self) -> H256 {
|
||||
message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3()
|
||||
let mut vote_step = self.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
message_info_rlp(&vote_step, self.block_hash).sha3()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ConsensusMessage {
|
||||
fn partial_cmp(&self, m: &ConsensusMessage) -> Option<Ordering> {
|
||||
impl PartialOrd for VoteStep {
|
||||
fn partial_cmp(&self, m: &VoteStep) -> Option<Ordering> {
|
||||
Some(self.cmp(m))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VoteStep {
|
||||
fn cmp(&self, m: &VoteStep) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step {
|
||||
fn number(&self) -> u8 {
|
||||
match *self {
|
||||
@@ -118,20 +128,6 @@ impl Step {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ConsensusMessage {
|
||||
fn cmp(&self, m: &ConsensusMessage) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else if self.step != m.step {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
} else {
|
||||
self.signature.cmp(&m.signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Step {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
match decoder.as_rlp().as_val()? {
|
||||
@@ -149,42 +145,39 @@ impl Encodable for Step {
|
||||
}
|
||||
}
|
||||
|
||||
/// (signature, height, round, step, block_hash)
|
||||
/// (signature, (height, round, step, block_hash))
|
||||
impl Decodable for ConsensusMessage {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let rlp = decoder.as_rlp();
|
||||
let m = rlp.at(1)?;
|
||||
let block_message: H256 = m.val_at(3)?;
|
||||
Ok(ConsensusMessage {
|
||||
signature: rlp.val_at(0)?,
|
||||
height: m.val_at(0)?,
|
||||
round: m.val_at(1)?,
|
||||
step: m.val_at(2)?,
|
||||
vote_step: VoteStep::new(m.val_at(0)?, m.val_at(1)?, m.val_at(2)?),
|
||||
block_hash: match block_message.is_zero() {
|
||||
true => None,
|
||||
false => Some(block_message),
|
||||
}
|
||||
},
|
||||
signature: rlp.val_at(0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for ConsensusMessage {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
let info = message_info_rlp(self.height, self.round, self.step, self.block_hash);
|
||||
let info = message_info_rlp(&self.vote_step, self.block_hash);
|
||||
s.begin_list(2)
|
||||
.append(&self.signature)
|
||||
.append_raw(&info, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Bytes {
|
||||
pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes {
|
||||
// TODO: figure out whats wrong with nested list encoding
|
||||
let mut s = RlpStream::new_list(5);
|
||||
s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.append(&vote_step.height).append(&vote_step.round).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.out()
|
||||
}
|
||||
|
||||
|
||||
pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(signature).append_raw(vote_info, 1);
|
||||
@@ -199,14 +192,17 @@ mod tests {
|
||||
use super::*;
|
||||
use account_provider::AccountProvider;
|
||||
use header::Header;
|
||||
use ethkey::Secret;
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
vote_step: VoteStep {
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
},
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message).to_vec();
|
||||
@@ -215,9 +211,11 @@ mod tests {
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
vote_step: VoteStep {
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
},
|
||||
block_hash: None
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message);
|
||||
@@ -228,10 +226,10 @@ mod tests {
|
||||
#[test]
|
||||
fn generate_and_verify() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||
|
||||
let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default()));
|
||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
||||
|
||||
let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi);
|
||||
|
||||
@@ -254,9 +252,11 @@ mod tests {
|
||||
message,
|
||||
ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep {
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
},
|
||||
block_hash: Some(header.bare_hash())
|
||||
}
|
||||
);
|
||||
@@ -267,12 +267,10 @@ mod tests {
|
||||
let header = Header::default();
|
||||
let pro = ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep::new(0, 0, Step::Propose),
|
||||
block_hash: Some(header.bare_hash())
|
||||
};
|
||||
let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let pre = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
|
||||
assert_eq!(pro.precommit_hash(), pre.sha3());
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ use self::transition::TransitionHandler;
|
||||
use self::params::TendermintParams;
|
||||
use self::vote_collector::VoteCollector;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum Step {
|
||||
Propose,
|
||||
Prevote,
|
||||
@@ -163,13 +163,13 @@ impl Tendermint {
|
||||
let h = self.height.load(AtomicOrdering::SeqCst);
|
||||
let r = self.round.load(AtomicOrdering::SeqCst);
|
||||
let s = self.step.read();
|
||||
let vote_info = message_info_rlp(h, r, *s, block_hash);
|
||||
let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash);
|
||||
let authority = self.authority.read();
|
||||
match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) {
|
||||
Ok(signature) => {
|
||||
let message_rlp = message_full_rlp(&signature, &vote_info);
|
||||
let message = ConsensusMessage::new(signature, h, r, *s, block_hash);
|
||||
self.votes.vote(message.clone(), *authority);
|
||||
self.votes.vote(message.clone(), &*authority);
|
||||
debug!(target: "poa", "Generated {:?} as {}.", message, *authority);
|
||||
self.handle_valid_message(&message);
|
||||
|
||||
@@ -221,7 +221,7 @@ impl Tendermint {
|
||||
},
|
||||
Step::Prevote => {
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if !self.should_unlock(m.round) => m.block_hash,
|
||||
Some(ref m) if !self.should_unlock(m.vote_step.round) => m.block_hash,
|
||||
_ => self.proposal.read().clone(),
|
||||
};
|
||||
self.generate_and_broadcast_message(block_hash);
|
||||
@@ -230,8 +230,8 @@ impl Tendermint {
|
||||
trace!(target: "poa", "to_step: Precommit.");
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if self.is_round(m) && m.block_hash.is_some() => {
|
||||
trace!(target: "poa", "Setting last lock: {}", m.round);
|
||||
self.last_lock.store(m.round, AtomicOrdering::SeqCst);
|
||||
trace!(target: "poa", "Setting last lock: {}", m.vote_step.round);
|
||||
self.last_lock.store(m.vote_step.round, AtomicOrdering::SeqCst);
|
||||
m.block_hash
|
||||
},
|
||||
_ => None,
|
||||
@@ -246,7 +246,7 @@ impl Tendermint {
|
||||
if let Some(block_hash) = *self.proposal.read() {
|
||||
// Generate seal and remove old votes.
|
||||
if self.is_proposer(&*self.authority.read()).is_ok() {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, &block_hash) {
|
||||
trace!(target: "poa", "Collected seal: {:?}", seal);
|
||||
let seal = vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@@ -290,11 +290,11 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn is_height(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn is_round(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn increment_round(&self, n: Round) {
|
||||
@@ -302,20 +302,20 @@ impl Tendermint {
|
||||
self.round.fetch_add(n, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round
|
||||
&& lock_change_round < self.round.load(AtomicOrdering::SeqCst)
|
||||
}
|
||||
|
||||
|
||||
fn has_enough_any_votes(&self) -> bool {
|
||||
let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read());
|
||||
let step_votes = self.votes.count_step_votes(&VoteStep::new(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()));
|
||||
self.is_above_threshold(step_votes)
|
||||
}
|
||||
|
||||
fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool {
|
||||
if message.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(message.height, message.round, message.step);
|
||||
fn has_enough_future_step_votes(&self, vote_step: &VoteStep) -> bool {
|
||||
if vote_step.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(vote_step);
|
||||
self.is_above_threshold(step_votes)
|
||||
} else {
|
||||
false
|
||||
@@ -328,12 +328,13 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
||||
let ref vote_step = message.vote_step;
|
||||
let is_newer_than_lock = match *self.lock_change.read() {
|
||||
Some(ref lock) => message > lock,
|
||||
Some(ref lock) => vote_step > &lock.vote_step,
|
||||
None => true,
|
||||
};
|
||||
let lock_change = is_newer_than_lock
|
||||
&& message.step == Step::Prevote
|
||||
&& vote_step.step == Step::Prevote
|
||||
&& message.block_hash.is_some()
|
||||
&& self.has_enough_aligned_votes(message);
|
||||
if lock_change {
|
||||
@@ -351,15 +352,15 @@ impl Tendermint {
|
||||
Some(Step::Commit)
|
||||
}
|
||||
},
|
||||
Step::Precommit if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Precommit if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Precommit)
|
||||
},
|
||||
// Avoid counting twice.
|
||||
Step::Prevote if lock_change => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Prevote if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Prevote)
|
||||
},
|
||||
_ => None,
|
||||
@@ -390,8 +391,8 @@ impl Engine for Tendermint {
|
||||
let message = ConsensusMessage::new_proposal(header).expect("Invalid header.");
|
||||
map![
|
||||
"signature".into() => message.signature.to_string(),
|
||||
"height".into() => message.height.to_string(),
|
||||
"round".into() => message.round.to_string(),
|
||||
"height".into() => message.vote_step.height.to_string(),
|
||||
"round".into() => message.vote_step.round.to_string(),
|
||||
"block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into())
|
||||
]
|
||||
}
|
||||
@@ -431,11 +432,11 @@ impl Engine for Tendermint {
|
||||
let height = header.number() as Height;
|
||||
let round = self.round.load(AtomicOrdering::SeqCst);
|
||||
let bh = Some(header.bare_hash());
|
||||
let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone());
|
||||
let vote_info = message_info_rlp(&VoteStep::new(height, round, Step::Propose), bh.clone());
|
||||
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) {
|
||||
// Insert Propose vote.
|
||||
debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), author);
|
||||
// Remember proposal for later seal submission.
|
||||
*self.proposal.write() = bh;
|
||||
Seal::Proposal(vec![
|
||||
@@ -461,9 +462,11 @@ impl Engine for Tendermint {
|
||||
if !self.is_authority(&sender) {
|
||||
Err(EngineError::NotAuthorized(sender))?;
|
||||
}
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
if self.votes.vote(message.clone(), &sender).is_some() {
|
||||
Err(EngineError::DoubleVote(sender))?
|
||||
}
|
||||
trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender);
|
||||
self.votes.vote(message.clone(), sender);
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
self.handle_valid_message(&message);
|
||||
}
|
||||
Ok(())
|
||||
@@ -502,7 +505,7 @@ impl Engine for Tendermint {
|
||||
}
|
||||
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposer = proposal.verify()?;
|
||||
if !self.is_authority(&proposer) {
|
||||
Err(EngineError::NotAuthorized(proposer))?
|
||||
@@ -541,7 +544,7 @@ impl Engine for Tendermint {
|
||||
found: signatures_len
|
||||
}))?;
|
||||
}
|
||||
self.is_round_proposer(proposal.height, proposal.round, &proposer)?;
|
||||
self.is_round_proposer(proposal.vote_step.height, proposal.vote_step.round, &proposer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -607,16 +610,16 @@ impl Engine for Tendermint {
|
||||
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
||||
if signatures_len != 1 {
|
||||
// New Commit received, skip to next height.
|
||||
trace!(target: "poa", "Received a commit for height {}, round {}.", proposal.height, proposal.round);
|
||||
self.to_next_height(proposal.height);
|
||||
trace!(target: "poa", "Received a commit: {:?}.", proposal.vote_step);
|
||||
self.to_next_height(proposal.vote_step.height);
|
||||
return false;
|
||||
}
|
||||
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
||||
debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer);
|
||||
debug!(target: "poa", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
||||
if self.is_round(&proposal) {
|
||||
*self.proposal.write() = proposal.block_hash.clone();
|
||||
}
|
||||
self.votes.vote(proposal, proposer);
|
||||
self.votes.vote(proposal, &proposer);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -671,6 +674,7 @@ mod tests {
|
||||
use error::{Error, BlockError};
|
||||
use header::Header;
|
||||
use env_info::EnvInfo;
|
||||
use ethkey::Secret;
|
||||
use client::chain_notify::ChainNotify;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::*;
|
||||
@@ -703,7 +707,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn vote<F>(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
|
||||
let mi = message_info_rlp(height, round, step, block_hash);
|
||||
let mi = message_info_rlp(&VoteStep::new(height, round, step), block_hash);
|
||||
let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
|
||||
engine.handle_message(&m).unwrap();
|
||||
m
|
||||
@@ -711,7 +715,7 @@ mod tests {
|
||||
|
||||
fn proposal_seal(tap: &Arc<AccountProvider>, header: &Header, round: Round) -> Vec<Bytes> {
|
||||
let author = header.author();
|
||||
let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(header.number() as Height, round, Step::Propose), Some(header.bare_hash()));
|
||||
let signature = tap.sign(*author, None, vote_info.sha3()).unwrap();
|
||||
vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@@ -721,7 +725,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
||||
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&acc.sha3()).unwrap(), acc).unwrap();
|
||||
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||
addr
|
||||
}
|
||||
@@ -825,7 +829,7 @@ mod tests {
|
||||
header.set_author(proposer);
|
||||
let mut seal = proposal_seal(&tap, &header, 0);
|
||||
|
||||
let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||
|
||||
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
|
||||
@@ -886,7 +890,7 @@ mod tests {
|
||||
fn relays_messages() {
|
||||
let (spec, tap) = setup();
|
||||
let engine = spec.engine.clone();
|
||||
|
||||
|
||||
let v0 = insert_and_register(&tap, engine.as_ref(), "0");
|
||||
let v1 = insert_and_register(&tap, engine.as_ref(), "1");
|
||||
|
||||
|
||||
@@ -17,13 +17,50 @@
|
||||
//! Collects votes on hashes at each height and round.
|
||||
|
||||
use util::*;
|
||||
use super::message::ConsensusMessage;
|
||||
use super::{Height, Round, Step};
|
||||
use super::message::*;
|
||||
use super::{Height, Round, Step, BlockHash};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VoteCollector {
|
||||
/// Storing all Proposals, Prevotes and Precommits.
|
||||
votes: RwLock<BTreeMap<ConsensusMessage, Address>>,
|
||||
votes: RwLock<BTreeMap<VoteStep, StepCollector>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct StepCollector {
|
||||
voted: HashSet<Address>,
|
||||
pub block_votes: HashMap<Option<BlockHash>, HashMap<H520, Address>>,
|
||||
messages: HashSet<ConsensusMessage>,
|
||||
}
|
||||
|
||||
impl StepCollector {
|
||||
/// Returns Some(&Address) when validator is double voting.
|
||||
fn insert<'a>(&mut self, message: ConsensusMessage, address: &'a Address) -> Option<&'a Address> {
|
||||
// Do nothing when message was seen.
|
||||
if self.messages.insert(message.clone()) {
|
||||
if self.voted.insert(address.clone()) {
|
||||
self
|
||||
.block_votes
|
||||
.entry(message.block_hash)
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(message.signature, address.clone());
|
||||
} else {
|
||||
// Bad validator sent a different message.
|
||||
return Some(address);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Count all votes for the given block hash at this step.
|
||||
fn count_block(&self, block_hash: &Option<BlockHash>) -> usize {
|
||||
self.block_votes.get(block_hash).map_or(0, HashMap::len)
|
||||
}
|
||||
|
||||
/// Count all votes collected for the given step.
|
||||
fn count(&self) -> usize {
|
||||
self.block_votes.values().map(HashMap::len).sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -42,109 +79,105 @@ impl PartialEq for SealSignatures {
|
||||
impl Eq for SealSignatures {}
|
||||
|
||||
impl VoteCollector {
|
||||
pub fn new() -> VoteCollector {
|
||||
pub fn new() -> Self {
|
||||
let mut collector = BTreeMap::new();
|
||||
// Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
block_hash: None
|
||||
},
|
||||
Address::default());
|
||||
// Insert dummy entry to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(VoteStep::new(0, 0, Step::Propose), Default::default());
|
||||
VoteCollector { votes: RwLock::new(collector) }
|
||||
}
|
||||
|
||||
/// Insert vote if it is newer than the oldest one.
|
||||
pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option<Address> {
|
||||
self.votes.write().insert(message, voter)
|
||||
pub fn vote<'a>(&self, message: ConsensusMessage, voter: &'a Address) -> Option<&'a Address> {
|
||||
self
|
||||
.votes
|
||||
.write()
|
||||
.entry(message.vote_step.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.insert(message, voter)
|
||||
}
|
||||
|
||||
/// Checks if the message should be ignored.
|
||||
pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool {
|
||||
self.votes.read().get(message).map_or(false, |a| {
|
||||
trace!(target: "poa", "Known message from {}: {:?}.", a, message);
|
||||
true
|
||||
}) || {
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(false, |c| {
|
||||
let is_known = c.messages.contains(message);
|
||||
if is_known { trace!(target: "poa", "Known message: {:?}.", message); }
|
||||
is_known
|
||||
})
|
||||
|| {
|
||||
let guard = self.votes.read();
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message <= oldest);
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message.vote_step <= *oldest);
|
||||
if is_old { trace!(target: "poa", "Old message {:?}.", message); }
|
||||
is_old
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws out messages older than message, leaves message as marker for the oldest.
|
||||
pub fn throw_out_old(&self, message: &ConsensusMessage) {
|
||||
pub fn throw_out_old(&self, vote_step: &VoteStep) {
|
||||
let mut guard = self.votes.write();
|
||||
let new_collector = guard.split_off(message);
|
||||
let new_collector = guard.split_off(vote_step);
|
||||
*guard = new_collector;
|
||||
}
|
||||
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option<SealSignatures> {
|
||||
let bh = Some(block_hash);
|
||||
let (proposal, votes) = {
|
||||
/// Collects the signatures used to seal a block.
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: &H256) -> Option<SealSignatures> {
|
||||
let ref bh = Some(*block_hash);
|
||||
let precommit_step = VoteStep::new(height, round, Step::Precommit);
|
||||
let maybe_seal = {
|
||||
let guard = self.votes.read();
|
||||
let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh));
|
||||
let proposal = current_signatures.next().cloned();
|
||||
let votes = current_signatures
|
||||
.skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.filter(|m| m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
(proposal, votes)
|
||||
guard
|
||||
.get(&VoteStep::new(height, round, Step::Propose))
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.and_then(|proposals| proposals.keys().next())
|
||||
.map(|proposal| SealSignatures {
|
||||
proposal: proposal.clone(),
|
||||
votes: guard
|
||||
.get(&precommit_step)
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.map(|precommits| precommits.keys().cloned().collect())
|
||||
.unwrap_or_else(Vec::new),
|
||||
})
|
||||
.and_then(|seal| if seal.votes.is_empty() { None } else { Some(seal) })
|
||||
};
|
||||
if votes.is_empty() {
|
||||
return None;
|
||||
if maybe_seal.is_some() {
|
||||
// Remove messages that are no longer relevant.
|
||||
self.throw_out_old(&precommit_step);
|
||||
}
|
||||
// Remove messages that are no longer relevant.
|
||||
votes.last().map(|m| self.throw_out_old(m));
|
||||
let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect();
|
||||
votes_vec.sort();
|
||||
proposal.map(|p| SealSignatures {
|
||||
proposal: p.signature,
|
||||
votes: votes_vec,
|
||||
})
|
||||
maybe_seal
|
||||
}
|
||||
|
||||
/// Count votes which agree with the given message.
|
||||
pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize {
|
||||
let guard = self.votes.read();
|
||||
guard.keys()
|
||||
.skip_while(|m| !m.is_aligned(message))
|
||||
// sorted by signature so might not be continuous
|
||||
.filter(|m| m.is_aligned(message))
|
||||
.count()
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(0, |m| m.count_block(&message.block_hash))
|
||||
}
|
||||
|
||||
pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize {
|
||||
let guard = self.votes.read();
|
||||
let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step));
|
||||
let mut origins = HashSet::new();
|
||||
let mut n = 0;
|
||||
for (message, origin) in current {
|
||||
if message.is_step(height, round, step) {
|
||||
if origins.insert(origin) {
|
||||
n += 1;
|
||||
} else {
|
||||
warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
/// Count all votes collected for a given step.
|
||||
pub fn count_step_votes(&self, vote_step: &VoteStep) -> usize {
|
||||
self.votes.read().get(vote_step).map_or(0, StepCollector::count)
|
||||
}
|
||||
|
||||
/// Get all messages older than the height.
|
||||
pub fn get_up_to(&self, height: Height) -> Vec<Bytes> {
|
||||
let guard = self.votes.read();
|
||||
guard
|
||||
.keys()
|
||||
.filter(|m| m.step.is_pre())
|
||||
.take_while(|m| m.height <= height)
|
||||
.map(|m| ::rlp::encode(m).to_vec())
|
||||
.collect()
|
||||
.iter()
|
||||
.filter(|&(s, _)| s.step.is_pre())
|
||||
.take_while(|&(s, _)| s.height <= height)
|
||||
.map(|(_, c)| c.messages.iter().map(|m| ::rlp::encode(m).to_vec()).collect::<Vec<_>>())
|
||||
.fold(Vec::new(), |mut acc, mut messages| { acc.append(&mut messages); acc })
|
||||
}
|
||||
|
||||
/// Retrieve address from which the message was sent from cache.
|
||||
pub fn get(&self, message: &ConsensusMessage) -> Option<Address> {
|
||||
let guard = self.votes.read();
|
||||
guard.get(message).cloned()
|
||||
guard.get(&message.vote_step).and_then(|c| c.block_votes.get(&message.block_hash)).and_then(|origins| origins.get(&message.signature).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,15 +185,15 @@ impl VoteCollector {
|
||||
mod tests {
|
||||
use util::*;
|
||||
use super::*;
|
||||
use super::super::{Height, Round, BlockHash, Step};
|
||||
use super::super::message::ConsensusMessage;
|
||||
use super::super::{BlockHash, Step};
|
||||
use super::super::message::*;
|
||||
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>) -> Option<H160> {
|
||||
full_vote(collector, signature, h, r, step, block_hash, H160::random())
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>) -> bool {
|
||||
full_vote(collector, signature, vote_step, block_hash, &H160::random()).is_none()
|
||||
}
|
||||
|
||||
fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>, address: Address) -> Option<H160> {
|
||||
collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address)
|
||||
fn full_vote<'a>(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>, address: &'a Address) -> Option<&'a Address> {
|
||||
collector.vote(ConsensusMessage { signature: signature, vote_step: vote_step, block_hash: block_hash }, address)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -173,68 +206,71 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
signatures.push(H520::random());
|
||||
}
|
||||
let propose_step = VoteStep::new(h, r, Step::Propose);
|
||||
let prevote_step = VoteStep::new(h, r, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(h, r, Step::Precommit);
|
||||
// Wrong height proposal.
|
||||
random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h - 1, r, Step::Propose), bh.clone());
|
||||
// Good proposal
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), bh.clone());
|
||||
// Wrong block proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), Some("0".sha3()));
|
||||
// Wrong block precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[3].clone(), precommit_step.clone(), Some("0".sha3()));
|
||||
// Wrong round proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), VoteStep::new(h, r - 1, Step::Propose), bh.clone());
|
||||
// Prevote.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), prevote_step.clone(), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Replcated vote.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong height precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[3].clone(), VoteStep::new(h + 1, r, Step::Precommit), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit, same signature.
|
||||
random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r - 1, Step::Precommit), bh.clone());
|
||||
let seal = SealSignatures {
|
||||
proposal: signatures[0],
|
||||
votes: signatures[1..3].to_vec()
|
||||
};
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap());
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, &bh.unwrap()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_votes() {
|
||||
let collector = VoteCollector::new();
|
||||
let prevote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(3, 2, Step::Precommit);
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 1, Step::Prevote), Some("0".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 3, Step::Precommit), Some("0".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
let same_sig = H520::random();
|
||||
random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig.clone(), prevote_step.clone(), Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, prevote_step.clone(), Some("1".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(2, 2, Step::Precommit), Some("2".sha3()));
|
||||
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4);
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2);
|
||||
assert_eq!(collector.count_step_votes(&prevote_step), 4);
|
||||
assert_eq!(collector.count_step_votes(&precommit_step), 2);
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Prevote,
|
||||
vote_step: prevote_step,
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
assert_eq!(collector.count_aligned_votes(&message), 2);
|
||||
@@ -243,30 +279,29 @@ mod tests {
|
||||
#[test]
|
||||
fn remove_old() {
|
||||
let collector = VoteCollector::new();
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Precommit,
|
||||
block_hash: Some("1".sha3())
|
||||
let vote = |height, round, step, hash| {
|
||||
random_vote(&collector, H520::random(), VoteStep::new(height, round, step), hash);
|
||||
};
|
||||
collector.throw_out_old(&message);
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 1, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 3, Step::Precommit, Some("0".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
collector.throw_out_old(&VoteStep::new(3, 2, Step::Precommit));
|
||||
assert_eq!(collector.votes.read().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malicious_authority() {
|
||||
let collector = VoteCollector::new();
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default());
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default());
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1);
|
||||
let vote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
// Vote is inserted fine.
|
||||
assert!(full_vote(&collector, H520::random(), vote_step.clone(), Some("0".sha3()), &Address::default()).is_none());
|
||||
// Returns the double voting address.
|
||||
full_vote(&collector, H520::random(), vote_step.clone(), Some("1".sha3()), &Address::default()).unwrap();
|
||||
assert_eq!(collector.count_step_votes(&vote_step), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,17 +118,17 @@ mod provider {
|
||||
}
|
||||
}
|
||||
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
|
||||
|
||||
|
||||
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
||||
#[allow(dead_code)]
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
|
||||
let data = call.encode_call(
|
||||
vec![]
|
||||
).map_err(Self::as_string)?;
|
||||
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
||||
let mut result = output.into_iter().rev().collect::<Vec<_>>();
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,7 @@ mod tests {
|
||||
use account_provider::AccountProvider;
|
||||
use transaction::{Transaction, Action};
|
||||
use client::{BlockChainClient, EngineClient};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::generate_dummy_client_with_spec_and_data;
|
||||
use super::super::ValidatorSet;
|
||||
@@ -158,8 +159,9 @@ mod tests {
|
||||
#[test]
|
||||
fn changes_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let v0 = tap.insert_account("1".sha3(), "").unwrap();
|
||||
let v1 = tap.insert_account("0".sha3(), "").unwrap();
|
||||
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap();
|
||||
let spec_factory = || {
|
||||
let spec = Spec::new_validator_contract();
|
||||
spec.engine.register_account_provider(tap.clone());
|
||||
@@ -178,7 +180,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
@@ -190,7 +192,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// The transaction is not yet included so still unable to seal.
|
||||
@@ -209,7 +211,7 @@ mod tests {
|
||||
action: Action::Call(Address::default()),
|
||||
value: 0.into(),
|
||||
data: Vec::new(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// Able to seal again.
|
||||
|
||||
@@ -31,6 +31,9 @@ use ethjson;
|
||||
use rlp::{self, UntrustedRlp, View};
|
||||
use blockchain::extras::BlockDetails;
|
||||
|
||||
/// Parity tries to round block.gas_limit to multiple of this constant
|
||||
pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
|
||||
|
||||
/// Ethash params.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct EthashParams {
|
||||
@@ -180,14 +183,23 @@ impl Engine for Ethash {
|
||||
let bound_divisor = self.ethash_params.gas_limit_bound_divisor;
|
||||
let lower_limit = gas_limit - gas_limit / bound_divisor + 1.into();
|
||||
let upper_limit = gas_limit + gas_limit / bound_divisor - 1.into();
|
||||
if gas_limit < gas_floor_target {
|
||||
min(gas_floor_target, upper_limit)
|
||||
let gas_limit = if gas_limit < gas_floor_target {
|
||||
let gas_limit = min(gas_floor_target, upper_limit);
|
||||
round_block_gas_limit(gas_limit, lower_limit, upper_limit)
|
||||
} else if gas_limit > gas_ceil_target {
|
||||
max(gas_ceil_target, lower_limit)
|
||||
let gas_limit = max(gas_ceil_target, lower_limit);
|
||||
round_block_gas_limit(gas_limit, lower_limit, upper_limit)
|
||||
} else {
|
||||
max(gas_floor_target, min(min(gas_ceil_target, upper_limit),
|
||||
lower_limit + (header.gas_used().clone() * 6.into() / 5.into()) / bound_divisor))
|
||||
}
|
||||
let total_lower_limit = max(lower_limit, gas_floor_target);
|
||||
let total_upper_limit = min(upper_limit, gas_ceil_target);
|
||||
let gas_limit = max(gas_floor_target, min(total_upper_limit,
|
||||
lower_limit + (header.gas_used().clone() * 6.into() / 5.into()) / bound_divisor));
|
||||
round_block_gas_limit(gas_limit, total_lower_limit, total_upper_limit)
|
||||
};
|
||||
// ensure that we are not violating protocol limits
|
||||
debug_assert!(gas_limit >= lower_limit);
|
||||
debug_assert!(gas_limit <= upper_limit);
|
||||
gas_limit
|
||||
};
|
||||
header.set_difficulty(difficulty);
|
||||
header.set_gas_limit(gas_limit);
|
||||
@@ -338,6 +350,23 @@ pub fn is_new_best_block(best_total_difficulty: U256, parent_details: &BlockDeta
|
||||
parent_details.total_difficulty + new_header.difficulty() > best_total_difficulty
|
||||
}
|
||||
|
||||
// Try to round gas_limit a bit so that:
|
||||
// 1) it will still be in desired range
|
||||
// 2) it will be a nearest (with tendency to increase) multiple of PARITY_GAS_LIMIT_DETERMINANT
|
||||
fn round_block_gas_limit(gas_limit: U256, lower_limit: U256, upper_limit: U256) -> U256 {
|
||||
let increased_gas_limit = gas_limit + (PARITY_GAS_LIMIT_DETERMINANT - gas_limit % PARITY_GAS_LIMIT_DETERMINANT);
|
||||
if increased_gas_limit > upper_limit {
|
||||
let decreased_gas_limit = increased_gas_limit - PARITY_GAS_LIMIT_DETERMINANT;
|
||||
if decreased_gas_limit < lower_limit {
|
||||
gas_limit
|
||||
} else {
|
||||
decreased_gas_limit
|
||||
}
|
||||
} else {
|
||||
increased_gas_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
|
||||
impl Ethash {
|
||||
fn calculate_difficulty(&self, header: &Header, parent: &Header) -> U256 {
|
||||
@@ -435,11 +464,12 @@ mod tests {
|
||||
use util::*;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
use engines::Engine;
|
||||
use env_info::EnvInfo;
|
||||
use error::{BlockError, Error};
|
||||
use header::Header;
|
||||
use super::super::{new_morden, new_homestead_test};
|
||||
use super::{Ethash, EthashParams};
|
||||
use super::{Ethash, EthashParams, PARITY_GAS_LIMIT_DETERMINANT};
|
||||
use rlp;
|
||||
|
||||
#[test]
|
||||
@@ -777,4 +807,47 @@ mod tests {
|
||||
ethash.calculate_difficulty(&header, &parent_header)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gas_limit_is_multiple_of_determinant() {
|
||||
let spec = new_homestead_test();
|
||||
let ethash = Ethash::new(spec.params, get_default_ethash_params(), BTreeMap::new());
|
||||
let mut parent = Header::new();
|
||||
let mut header = Header::new();
|
||||
header.set_number(1);
|
||||
|
||||
// this test will work for this constant only
|
||||
assert_eq!(PARITY_GAS_LIMIT_DETERMINANT, U256::from(37));
|
||||
|
||||
// when parent.gas_limit < gas_floor_target:
|
||||
parent.set_gas_limit(U256::from(50_000));
|
||||
ethash.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(50_024));
|
||||
|
||||
// when parent.gas_limit > gas_ceil_target:
|
||||
parent.set_gas_limit(U256::from(250_000));
|
||||
ethash.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(249_787));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
ethash.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(150_035));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
// && we can NOT increase it to be multiple of constant
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
ethash.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(150_002));
|
||||
assert_eq!(*header.gas_limit(), U256::from(149_998));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
// && we can NOT increase it to be multiple of constant
|
||||
// && we can NOT decrease it to be multiple of constant
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
ethash.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002));
|
||||
assert_eq!(*header.gas_limit(), U256::from(150_002));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use action_params::ActionParams;
|
||||
use evm::Ext;
|
||||
|
||||
/// Evm errors.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Error {
|
||||
/// `OutOfGas` is returned when transaction execution runs out of gas.
|
||||
/// The state should be reverted to the state from before the
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
|
||||
}
|
||||
|
||||
/// Transaction execution options.
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct TransactOptions {
|
||||
/// Enable call tracing.
|
||||
pub tracing: bool,
|
||||
@@ -463,8 +463,9 @@ impl<'a> Executive<'a> {
|
||||
|
||||
match result {
|
||||
Err(evm::Error::Internal) => Err(ExecutionError::Internal),
|
||||
Err(_) => {
|
||||
Err(exception) => {
|
||||
Ok(Executed {
|
||||
exception: Some(exception),
|
||||
gas: t.gas,
|
||||
gas_used: t.gas,
|
||||
refunded: U256::zero(),
|
||||
@@ -479,6 +480,7 @@ impl<'a> Executive<'a> {
|
||||
},
|
||||
_ => {
|
||||
Ok(Executed {
|
||||
exception: None,
|
||||
gas: t.gas,
|
||||
gas_used: gas_used,
|
||||
refunded: refunded,
|
||||
|
||||
@@ -149,7 +149,8 @@ impl GasPriceCalibrator {
|
||||
if Instant::now() >= self.next_calibration {
|
||||
let usd_per_tx = self.options.usd_per_tx;
|
||||
trace!(target: "miner", "Getting price info");
|
||||
let price_info = PriceInfo::get(move |price: PriceInfo| {
|
||||
|
||||
PriceInfo::get(move |price: PriceInfo| {
|
||||
trace!(target: "miner", "Price info arrived: {:?}", price);
|
||||
let usd_per_eth = price.ethusd;
|
||||
let wei_per_usd: f32 = 1.0e18 / usd_per_eth;
|
||||
@@ -159,11 +160,7 @@ impl GasPriceCalibrator {
|
||||
set_price(U256::from(wei_per_gas as u64));
|
||||
});
|
||||
|
||||
if price_info.is_ok() {
|
||||
self.next_calibration = Instant::now() + self.options.recalibration_period;
|
||||
} else {
|
||||
warn!(target: "miner", "Unable to update Ether price.");
|
||||
}
|
||||
self.next_calibration = Instant::now() + self.options.recalibration_period;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,16 +304,6 @@ impl Miner {
|
||||
#[cfg_attr(feature="dev", allow(match_same_arms))]
|
||||
/// Prepares new block for sealing including top transactions from queue.
|
||||
fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option<H256>) {
|
||||
{
|
||||
trace!(target: "miner", "prepare_block: recalibrating...");
|
||||
let txq = self.transaction_queue.clone();
|
||||
self.gas_pricer.lock().recalibrate(move |price| {
|
||||
trace!(target: "miner", "prepare_block: Got gas price! {}", price);
|
||||
txq.lock().set_minimal_gas_price(price);
|
||||
});
|
||||
trace!(target: "miner", "prepare_block: done recalibration.");
|
||||
}
|
||||
|
||||
let _timer = PerfTimer::new("prepare_block");
|
||||
let chain_info = chain.chain_info();
|
||||
let (transactions, mut open_block, original_work_hash) = {
|
||||
@@ -431,6 +418,16 @@ impl Miner {
|
||||
(block, original_work_hash)
|
||||
}
|
||||
|
||||
/// Asynchronously updates minimal gas price for transaction queue
|
||||
pub fn recalibrate_minimal_gas_price(&self) {
|
||||
debug!(target: "miner", "minimal_gas_price: recalibrating...");
|
||||
let txq = self.transaction_queue.clone();
|
||||
self.gas_pricer.lock().recalibrate(move |price| {
|
||||
debug!(target: "miner", "minimal_gas_price: Got gas price! {}", price);
|
||||
txq.lock().set_minimal_gas_price(price);
|
||||
});
|
||||
}
|
||||
|
||||
/// Check is reseal is allowed and necessary.
|
||||
fn requires_reseal(&self, best_block: BlockNumber) -> bool {
|
||||
let has_local_transactions = self.transaction_queue.lock().has_local_pending_transactions();
|
||||
@@ -673,7 +670,7 @@ impl MinerService for Miner {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
|
||||
fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
|
||||
let sealing_work = self.sealing_work.lock();
|
||||
match sealing_work.queue.peek_last_ref() {
|
||||
Some(work) => {
|
||||
@@ -681,7 +678,7 @@ impl MinerService for Miner {
|
||||
|
||||
// TODO: merge this code with client.rs's fn call somwhow.
|
||||
let header = block.header();
|
||||
let last_hashes = Arc::new(chain.last_hashes());
|
||||
let last_hashes = Arc::new(client.last_hashes());
|
||||
let env_info = EnvInfo {
|
||||
number: header.number(),
|
||||
author: *header.author(),
|
||||
@@ -706,16 +703,14 @@ impl MinerService for Miner {
|
||||
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
|
||||
}
|
||||
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
|
||||
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)?;
|
||||
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, client.vm_factory()).transact(t, options)?;
|
||||
|
||||
// TODO gav move this into Executive.
|
||||
ret.state_diff = original_state.map(|original| state.diff_from(original));
|
||||
|
||||
Ok(ret)
|
||||
},
|
||||
None => {
|
||||
chain.call(t, BlockId::Latest, analytics)
|
||||
}
|
||||
None => client.call(t, BlockId::Latest, analytics)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,6 +1121,9 @@ impl MinerService for Miner {
|
||||
// First update gas limit in transaction queue
|
||||
self.update_gas_limit(chain);
|
||||
|
||||
// Update minimal gas price
|
||||
self.recalibrate_minimal_gas_price();
|
||||
|
||||
// Then import all transactions...
|
||||
{
|
||||
retracted.par_iter()
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::time::Duration;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use hyper::client::{Handler, Request, Response, Client};
|
||||
use hyper::{Next, Encoder, Decoder};
|
||||
use hyper::{Url, Next, Encoder, Decoder};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -29,12 +29,12 @@ pub struct PriceInfo {
|
||||
pub ethusd: f32,
|
||||
}
|
||||
|
||||
pub struct SetPriceHandler<F: Fn(PriceInfo) + Sync + Send + 'static> {
|
||||
pub struct SetPriceHandler<F> {
|
||||
set_price: F,
|
||||
channel: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
impl<F: Fn(PriceInfo) + Sync + Send + 'static> Drop for SetPriceHandler<F> {
|
||||
impl<F> Drop for SetPriceHandler<F> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.channel.send(());
|
||||
}
|
||||
@@ -47,42 +47,66 @@ impl<F: Fn(PriceInfo) + Sync + Send + 'static> Handler<HttpStream> for SetPriceH
|
||||
|
||||
fn on_response_readable(&mut self, r: &mut Decoder<HttpStream>) -> Next {
|
||||
let mut body = String::new();
|
||||
let _ = r.read_to_string(&mut body).ok()
|
||||
.and_then(|_| Json::from_str(&body).ok())
|
||||
.and_then(|json| json.find_path(&["result", "ethusd"])
|
||||
.and_then(|obj| match *obj {
|
||||
Json::String(ref s) => Some((self.set_price)(PriceInfo {
|
||||
ethusd: FromStr::from_str(s)
|
||||
.expect("Etherscan API will always return properly formatted price; qed")
|
||||
})),
|
||||
_ => None,
|
||||
}));
|
||||
let info = r.read_to_string(&mut body)
|
||||
.map_err(|e| format!("Unable to read response: {:?}", e))
|
||||
.and_then(|_| self.process_response(&body));
|
||||
|
||||
if let Err(e) = info {
|
||||
warn!("Failed to auto-update latest ETH price: {:?}", e);
|
||||
}
|
||||
Next::end()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl PriceInfo {
|
||||
pub fn get<F: Fn(PriceInfo) + Sync + Send + 'static>(set_price: F) -> Result<(), ()> {
|
||||
// TODO: Handle each error type properly
|
||||
let client = Client::new().map_err(|_| ())?;
|
||||
thread::spawn(move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice")
|
||||
.expect("string known to be a valid URL; qed");
|
||||
let _ = client.request(
|
||||
url,
|
||||
SetPriceHandler {
|
||||
set_price: set_price,
|
||||
channel: tx,
|
||||
}).ok().and_then(|_| rx.recv().ok());
|
||||
client.close();
|
||||
impl<F: Fn(PriceInfo) + Sync + Send + 'static> SetPriceHandler<F> {
|
||||
fn process_response(&self, body: &str) -> Result<(), String> {
|
||||
let json = Json::from_str(body).map_err(|e| format!("Invalid JSON returned: {:?}", e))?;
|
||||
let obj = json.find_path(&["result", "ethusd"]).ok_or("USD price not found".to_owned())?;
|
||||
let ethusd = match *obj {
|
||||
Json::String(ref s) => FromStr::from_str(s).ok(),
|
||||
_ => None,
|
||||
}.ok_or("Unexpected price format.".to_owned())?;
|
||||
|
||||
(self.set_price)(PriceInfo {
|
||||
ethusd: ethusd,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
impl PriceInfo {
|
||||
pub fn get<F: Fn(PriceInfo) + Sync + Send + 'static>(set_price: F) {
|
||||
thread::spawn(move || {
|
||||
let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice")
|
||||
.expect("string known to be a valid URL; qed");
|
||||
|
||||
if let Err(e) = Self::request(url, set_price) {
|
||||
warn!("Failed to auto-update latest ETH price: {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn request<F: Fn(PriceInfo) + Send + Sync + 'static>(url: Url, set_price: F) -> Result<(), String> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let client = Client::new().map_err(|e| format!("Unable to start client: {:?}", e))?;
|
||||
|
||||
client.request(
|
||||
url,
|
||||
SetPriceHandler {
|
||||
set_price: set_price,
|
||||
channel: tx,
|
||||
},
|
||||
).map_err(|_| "Request failed.".to_owned())?;
|
||||
|
||||
// Wait for exit
|
||||
let _ = rx.recv().map_err(|e| format!("Request interrupted: {:?}", e))?;
|
||||
client.close();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test] #[ignore]
|
||||
fn should_get_price_info() {
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -93,7 +117,7 @@ fn should_get_price_info() {
|
||||
let done = Arc::new((Mutex::new(PriceInfo { ethusd: 0f32 }), Condvar::new()));
|
||||
let rdone = done.clone();
|
||||
|
||||
PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); }).unwrap();
|
||||
PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); });
|
||||
let mut p = done.0.lock();
|
||||
let t = done.1.wait_for(&mut p, Duration::from_millis(10000));
|
||||
assert!(!t.timed_out());
|
||||
|
||||
@@ -844,6 +844,7 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use super::*;
|
||||
use ethkey::Secret;
|
||||
use util::{U256, H256, FixedHash, Address, Hashable};
|
||||
use tests::helpers::*;
|
||||
use devtools::*;
|
||||
@@ -854,6 +855,10 @@ mod tests {
|
||||
use trace::{FlatTrace, TraceError, trace};
|
||||
use types::executed::CallType;
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_apply_create_transaction() {
|
||||
init_log();
|
||||
@@ -872,7 +877,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -932,7 +937,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("5b600056").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -969,7 +974,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1012,7 +1017,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -1054,7 +1059,7 @@ mod tests {
|
||||
action: Action::Call(0x1.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
|
||||
@@ -1096,7 +1101,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap());
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
@@ -1139,7 +1144,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1201,7 +1206,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1260,7 +1265,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1300,7 +1305,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1360,7 +1365,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1415,7 +1420,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds.
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1458,7 +1463,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
@@ -1514,7 +1519,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap());
|
||||
@@ -1589,7 +1594,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap());
|
||||
@@ -1662,7 +1667,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap());
|
||||
state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty);
|
||||
|
||||
@@ -28,7 +28,7 @@ use rlp::View;
|
||||
use spec::Spec;
|
||||
use views::BlockView;
|
||||
use util::stats::Histogram;
|
||||
use ethkey::KeyPair;
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use transaction::{PendingTransaction, Transaction, Action};
|
||||
use miner::MinerService;
|
||||
|
||||
@@ -290,7 +290,7 @@ fn change_history_size() {
|
||||
|
||||
#[test]
|
||||
fn does_not_propagate_delayed_transactions() {
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret(Secret::from_slice(&"test".sha3()).unwrap()).unwrap();
|
||||
let secret = key.secret();
|
||||
let tx0 = PendingTransaction::new(Transaction {
|
||||
nonce: 0.into(),
|
||||
|
||||
@@ -163,7 +163,7 @@ pub fn generate_dummy_client_with_spec_and_data<F>(get_test_spec: F, block_numbe
|
||||
let mut last_hashes = vec![];
|
||||
let mut last_header = genesis_header.clone();
|
||||
|
||||
let kp = KeyPair::from_secret("".sha3()).unwrap();
|
||||
let kp = KeyPair::from_secret_slice(&"".sha3()).unwrap();
|
||||
let author = kp.address();
|
||||
|
||||
let mut n = 0;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
use util::{Bytes, U256, Address, U512};
|
||||
use rlp::*;
|
||||
use evm;
|
||||
use trace::{VMTrace, FlatTrace};
|
||||
use types::log_entry::LogEntry;
|
||||
use types::state_diff::StateDiff;
|
||||
@@ -65,6 +66,9 @@ impl Decodable for CallType {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct Executed {
|
||||
/// True if the outer call/create resulted in an exceptional exit.
|
||||
pub exception: Option<evm::Error>,
|
||||
|
||||
/// Gas paid up front for execution of transaction.
|
||||
pub gas: U256,
|
||||
|
||||
@@ -178,6 +182,8 @@ pub enum CallError {
|
||||
TransactionNotFound,
|
||||
/// Couldn't find requested block's state in the chain.
|
||||
StatePruned,
|
||||
/// Couldn't find an amount of gas that didn't result in an exception.
|
||||
Exceptional,
|
||||
/// Error executing.
|
||||
Execution(ExecutionError),
|
||||
}
|
||||
@@ -195,6 +201,7 @@ impl fmt::Display for CallError {
|
||||
let msg = match *self {
|
||||
TransactionNotFound => "Transaction couldn't be found in the chain".into(),
|
||||
StatePruned => "Couldn't find the transaction block's state in the chain".into(),
|
||||
Exceptional => "An exception happened in the execution".into(),
|
||||
Execution(ref e) => format!("{}", e),
|
||||
};
|
||||
|
||||
|
||||
@@ -391,6 +391,14 @@ impl Res {
|
||||
Res::Call(_) | Res::FailedCall(_) | Res::FailedCreate(_) | Res::None => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Did this call fail?
|
||||
pub fn succeeded(&self) -> bool {
|
||||
match *self {
|
||||
Res::Call(_) | Res::Create(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -561,4 +569,3 @@ impl Decodable for VMTrace {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! Transaction data structure.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::cell::*;
|
||||
use rlp::*;
|
||||
use util::sha3::Hashable;
|
||||
@@ -102,6 +102,7 @@ impl HeapSizeOf for Transaction {
|
||||
impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
fn from(t: ethjson::state::Transaction) -> Self {
|
||||
let to: Option<ethjson::hash::Address> = t.to.into();
|
||||
let secret = Secret::from_slice(&t.secret.0).expect("Valid secret expected.");
|
||||
Transaction {
|
||||
nonce: t.nonce.into(),
|
||||
gas_price: t.gas_price.into(),
|
||||
@@ -112,7 +113,7 @@ impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
},
|
||||
value: t.value.into(),
|
||||
data: t.data.into(),
|
||||
}.sign(&t.secret.into(), None)
|
||||
}.sign(&secret, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +240,12 @@ impl Deref for SignedTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SignedTransaction {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.unsigned
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for SignedTransaction {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let d = decoder.as_rlp();
|
||||
|
||||
@@ -166,7 +166,7 @@ pub mod aes {
|
||||
/// ECDH functions
|
||||
#[cfg_attr(feature="dev", allow(similar_names))]
|
||||
pub mod ecdh {
|
||||
use secp256k1::{ecdh, key};
|
||||
use secp256k1::{ecdh, key, Error as SecpError};
|
||||
use ethkey::{Secret, Public, SECP256K1};
|
||||
use Error;
|
||||
|
||||
@@ -180,13 +180,11 @@ pub mod ecdh {
|
||||
};
|
||||
|
||||
let publ = key::PublicKey::from_slice(context, &pdata)?;
|
||||
// no way to create SecretKey from raw byte array.
|
||||
let sec: &key::SecretKey = unsafe { ::std::mem::transmute(secret) };
|
||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, sec);
|
||||
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
||||
|
||||
let mut s = Secret::default();
|
||||
s.copy_from_slice(&shared[0..32]);
|
||||
Ok(s)
|
||||
Secret::from_slice(&shared[0..32])
|
||||
.map_err(|_| Error::Secp(SecpError::InvalidSecretKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use keccak::Keccak256;
|
||||
use super::{KeyPair, Error, Generator};
|
||||
use super::{KeyPair, Error, Generator, Secret};
|
||||
|
||||
/// Simple brainwallet.
|
||||
pub struct Brain(String);
|
||||
@@ -34,13 +34,15 @@ impl Generator for Brain {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
secret = secret.keccak256();
|
||||
|
||||
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
let result = KeyPair::from_secret(secret.clone().into());
|
||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||
return result;
|
||||
if let Ok(secret) = Secret::from_slice(&secret) {
|
||||
let result = KeyPair::from_secret(secret);
|
||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -60,11 +60,14 @@ impl KeyPair {
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
||||
Self::from_secret(Secret::from_slice(slice)?)
|
||||
}
|
||||
|
||||
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||
let context = &SECP256K1;
|
||||
let serialized = publ.serialize_vec(context, false);
|
||||
let mut secret = Secret::default();
|
||||
secret.copy_from_slice(&sec[0..32]);
|
||||
let secret = Secret::from(sec);
|
||||
let mut public = Public::default();
|
||||
public.copy_from_slice(&serialized[1..65]);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ mod keccak;
|
||||
mod prefix;
|
||||
mod random;
|
||||
mod signature;
|
||||
mod secret;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
||||
@@ -46,10 +47,10 @@ pub use self::keypair::{KeyPair, public_to_address};
|
||||
pub use self::prefix::Prefix;
|
||||
pub use self::random::Random;
|
||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
||||
pub use self::secret::Secret;
|
||||
|
||||
use bigint::hash::{H160, H256, H512};
|
||||
|
||||
pub type Address = H160;
|
||||
pub type Secret = H256;
|
||||
pub type Message = H256;
|
||||
pub type Public = H512;
|
||||
|
||||
69
ethkey/src/secret.rs
Normal file
69
ethkey/src/secret.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use secp256k1::key;
|
||||
use bigint::hash::H256;
|
||||
use {Error};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Secret {
|
||||
inner: H256,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Secret {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "Secret: 0x{:x}{:x}..{:x}{:x}", self.inner[0], self.inner[1], self.inner[30], self.inner[31])
|
||||
}
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
pub fn from_slice(key: &[u8]) -> Result<Self, Error> {
|
||||
if key.len() != 32 {
|
||||
return Err(Error::InvalidSecret);
|
||||
}
|
||||
|
||||
let mut h = H256::default();
|
||||
h.copy_from_slice(&key[0..32]);
|
||||
Ok(Secret { inner: h })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Secret {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let hash = H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
Self::from_slice(&hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<key::SecretKey> for Secret {
|
||||
fn from(key: key::SecretKey) -> Self {
|
||||
Self::from_slice(&key[0..32])
|
||||
.expect("`key::SecretKey` is valid (no way to construct invalid one); qed")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Secret {
|
||||
type Target = H256;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::cmp::PartialEq;
|
||||
use std::{mem, fmt};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
||||
@@ -169,9 +169,8 @@ impl DerefMut for Signature {
|
||||
|
||||
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||
let context = &SECP256K1;
|
||||
// no way to create from raw byte array.
|
||||
let sec: &SecretKey = unsafe { mem::transmute(secret) };
|
||||
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, sec)?;
|
||||
let sec = SecretKey::from_slice(context, &secret)?;
|
||||
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;
|
||||
let (rec_id, data) = s.serialize_compact(context);
|
||||
let mut data_arr = [0; 65];
|
||||
|
||||
|
||||
@@ -122,16 +122,14 @@ impl Crypto {
|
||||
return Err(Error::InvalidPassword);
|
||||
}
|
||||
|
||||
let mut secret = Secret::default();
|
||||
|
||||
match self.cipher {
|
||||
Cipher::Aes128Ctr(ref params) => {
|
||||
let from = 32 - self.ciphertext.len();
|
||||
crypto::aes::decrypt(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut (&mut *secret)[from..])
|
||||
let mut secret = [0; 32];
|
||||
crypto::aes::decrypt(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut secret[from..]);
|
||||
Ok(Secret::from_slice(&secret)?)
|
||||
},
|
||||
}
|
||||
|
||||
Ok(secret)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ impl PresaleWallet {
|
||||
let len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?;
|
||||
let unpadded = &key[..len];
|
||||
|
||||
let secret = Secret::from(unpadded.keccak256());
|
||||
let secret = Secret::from_slice(&unpadded.keccak256())?;
|
||||
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||
if kp.address() == self.address {
|
||||
return Ok(kp)
|
||||
|
||||
@@ -133,9 +133,9 @@ fn secret_store_load_pat_files() {
|
||||
#[test]
|
||||
fn test_decrypting_files_with_short_ciphertext() {
|
||||
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".into()).unwrap();
|
||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".parse().unwrap()).unwrap();
|
||||
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".into()).unwrap();
|
||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".parse().unwrap()).unwrap();
|
||||
let dir = DiskDirectory::at(ciphertext_path());
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
let accounts = store.accounts().unwrap();
|
||||
|
||||
@@ -189,6 +189,7 @@
|
||||
"valid-url": "1.0.9",
|
||||
"validator": "6.2.0",
|
||||
"web3": "0.17.0-beta",
|
||||
"whatwg-fetch": "2.0.1"
|
||||
"whatwg-fetch": "2.0.1",
|
||||
"zxcvbn": "4.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
13
js/src/3rdparty/email-verification/index.js
vendored
13
js/src/3rdparty/email-verification/index.js
vendored
@@ -16,6 +16,19 @@
|
||||
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
export const isServerRunning = (isTestnet = false) => {
|
||||
const port = isTestnet ? 28443 : 18443;
|
||||
return fetch(`https://email-verification.parity.io:${port}/health`, {
|
||||
mode: 'cors', cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const postToServer = (query, isTestnet = false) => {
|
||||
const port = isTestnet ? 28443 : 18443;
|
||||
query = stringify(query);
|
||||
|
||||
13
js/src/3rdparty/sms-verification/index.js
vendored
13
js/src/3rdparty/sms-verification/index.js
vendored
@@ -16,6 +16,19 @@
|
||||
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
export const isServerRunning = (isTestnet = false) => {
|
||||
const port = isTestnet ? 8443 : 443;
|
||||
return fetch(`https://sms-verification.parity.io:${port}/health`, {
|
||||
mode: 'cors', cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const postToServer = (query, isTestnet = false) => {
|
||||
const port = isTestnet ? 8443 : 443;
|
||||
query = stringify(query);
|
||||
|
||||
@@ -23,12 +23,12 @@ import { eventSignature } from '../../util/signature';
|
||||
|
||||
export default class Event {
|
||||
constructor (abi) {
|
||||
this._name = abi.name;
|
||||
this._inputs = EventParam.toEventParams(abi.inputs || []);
|
||||
this._anonymous = !!abi.anonymous;
|
||||
|
||||
const { id, signature } = eventSignature(this._name, this.inputParamTypes());
|
||||
const { id, name, signature } = eventSignature(abi.name, this.inputParamTypes());
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._signature = signature;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ import { methodSignature } from '../util/signature';
|
||||
export default class Func {
|
||||
constructor (abi) {
|
||||
this._abi = abi;
|
||||
this._name = abi.name;
|
||||
this._constant = !!abi.constant;
|
||||
this._payable = abi.payable;
|
||||
this._inputs = Param.toParams(abi.inputs || []);
|
||||
this._outputs = Param.toParams(abi.outputs || []);
|
||||
|
||||
const { id, signature } = methodSignature(this._name, this.inputParamTypes());
|
||||
const { id, name, signature } = methodSignature(abi.name, this.inputParamTypes());
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._signature = signature;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,18 @@ describe('abi/spec/Function', () => {
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('returns signature correctly if name already contains it', () => {
|
||||
const func = new Func({
|
||||
name: 'test(bool,string)',
|
||||
inputs: inputsArr,
|
||||
outputs: outputsArr
|
||||
});
|
||||
|
||||
expect(func.name).to.equal('test');
|
||||
expect(func.id).to.equal('test(bool,string)');
|
||||
expect(func.signature).to.equal('02356205');
|
||||
});
|
||||
|
||||
it('stores the parameters as received', () => {
|
||||
expect(func.name).to.equal('test');
|
||||
expect(func.constant).to.be.false;
|
||||
|
||||
@@ -17,15 +17,31 @@
|
||||
import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
|
||||
import { fromParamType } from '../spec/paramType/format';
|
||||
|
||||
export function eventSignature (name, params) {
|
||||
export function eventSignature (eventName, params) {
|
||||
const { strName, name } = parseName(eventName);
|
||||
const types = (params || []).map(fromParamType).join(',');
|
||||
const id = `${name || ''}(${types})`;
|
||||
const id = `${strName}(${types})`;
|
||||
|
||||
return { id, signature: keccak_256(id) };
|
||||
return { id, name, signature: keccak_256(id) };
|
||||
}
|
||||
|
||||
export function methodSignature (name, params) {
|
||||
const { id, signature } = eventSignature(name, params);
|
||||
export function methodSignature (methodName, params) {
|
||||
const { id, name, signature } = eventSignature(methodName, params);
|
||||
|
||||
return { id, signature: signature.substr(0, 8) };
|
||||
return { id, name, signature: signature.substr(0, 8) };
|
||||
}
|
||||
|
||||
function parseName (name) {
|
||||
const strName = `${name || ''}`;
|
||||
const idx = strName.indexOf('(');
|
||||
|
||||
if (idx === -1) {
|
||||
return { strName, name };
|
||||
}
|
||||
|
||||
const trimmedName = strName.slice(0, idx);
|
||||
return {
|
||||
strName: trimmedName,
|
||||
name: trimmedName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,50 +19,93 @@ import { eventSignature, methodSignature } from './signature';
|
||||
describe('abi/util/signature', () => {
|
||||
describe('eventSignature', () => {
|
||||
it('encodes signature baz() correctly', () => {
|
||||
expect(eventSignature('baz', []))
|
||||
.to.deep.equal({ id: 'baz()', signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf' });
|
||||
expect(eventSignature('baz', [])).to.deep.equal({
|
||||
id: 'baz()',
|
||||
name: 'baz',
|
||||
signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes signature baz(uint32) correctly', () => {
|
||||
expect(eventSignature('baz', [{ type: 'uint', length: 32 }]))
|
||||
.to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1' });
|
||||
expect(eventSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({
|
||||
id: 'baz(uint32)',
|
||||
name: 'baz',
|
||||
signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes signature baz(uint32, bool) correctly', () => {
|
||||
expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }]))
|
||||
.to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2' });
|
||||
expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
|
||||
id: 'baz(uint32,bool)',
|
||||
name: 'baz',
|
||||
signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes no-name signature correctly as ()', () => {
|
||||
expect(eventSignature(undefined, []))
|
||||
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
|
||||
expect(eventSignature(undefined, [])).to.deep.equal({
|
||||
id: '()',
|
||||
name: undefined,
|
||||
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes no-params signature correctly as ()', () => {
|
||||
expect(eventSignature(undefined, undefined))
|
||||
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
|
||||
expect(eventSignature(undefined, undefined)).to.deep.equal({
|
||||
id: '()',
|
||||
name: undefined,
|
||||
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methodSignature', () => {
|
||||
it('encodes signature baz() correctly', () => {
|
||||
expect(methodSignature('baz', [])).to.deep.equal({ id: 'baz()', signature: 'a7916fac' });
|
||||
expect(methodSignature('baz', [])).to.deep.equal({
|
||||
id: 'baz()',
|
||||
name: 'baz',
|
||||
signature: 'a7916fac'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes signature baz(uint32) correctly', () => {
|
||||
expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e' });
|
||||
expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({
|
||||
id: 'baz(uint32)',
|
||||
name: 'baz',
|
||||
signature: '7d68785e'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes signature baz(uint32, bool) correctly', () => {
|
||||
expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0' });
|
||||
expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
|
||||
id: 'baz(uint32,bool)',
|
||||
name: 'baz',
|
||||
signature: 'cdcd77c0'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes signature in name correctly', () => {
|
||||
expect(methodSignature('baz(uint32,bool)', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
|
||||
id: 'baz(uint32,bool)',
|
||||
name: 'baz',
|
||||
signature: 'cdcd77c0'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes no-name signature correctly as ()', () => {
|
||||
expect(methodSignature(undefined, [])).to.deep.equal({ id: '()', signature: '861731d5' });
|
||||
expect(methodSignature(undefined, [])).to.deep.equal({
|
||||
id: '()',
|
||||
name: undefined,
|
||||
signature: '861731d5'
|
||||
});
|
||||
});
|
||||
|
||||
it('encodes no-params signature correctly as ()', () => {
|
||||
expect(methodSignature(undefined, undefined)).to.deep.equal({ id: '()', signature: '861731d5' });
|
||||
expect(methodSignature(undefined, undefined)).to.deep.equal({
|
||||
id: '()',
|
||||
name: undefined,
|
||||
signature: '861731d5'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
import { Http, Ws } from './transport';
|
||||
import Contract from './contract';
|
||||
|
||||
@@ -22,8 +24,10 @@ import Subscriptions from './subscriptions';
|
||||
import util from './util';
|
||||
import { isFunction } from './util/types';
|
||||
|
||||
export default class Api {
|
||||
export default class Api extends EventEmitter {
|
||||
constructor (transport) {
|
||||
super();
|
||||
|
||||
if (!transport || !isFunction(transport.execute)) {
|
||||
throw new Error('EthApi needs transport with execute() function defined');
|
||||
}
|
||||
|
||||
@@ -248,18 +248,32 @@ export default class Contract {
|
||||
.call(callParams)
|
||||
.then((encoded) => func.decodeOutput(encoded))
|
||||
.then((tokens) => tokens.map((token) => token.value))
|
||||
.then((returns) => returns.length === 1 ? returns[0] : returns);
|
||||
.then((returns) => returns.length === 1 ? returns[0] : returns)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.call`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
if (!func.constant) {
|
||||
func.postTransaction = (options, values = []) => {
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.parity.postTransaction(_options);
|
||||
return this._api.parity
|
||||
.postTransaction(_options)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.postTransaction`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
func.estimateGas = (options, values = []) => {
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.eth.estimateGas(_options);
|
||||
return this._api.eth
|
||||
.estimateGas(_options)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.estimateGas`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -385,6 +399,10 @@ export default class Contract {
|
||||
this._subscribeToChanges();
|
||||
return subscriptionId;
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('subscribe', event, _options, error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export const ERROR_CODES = {
|
||||
UNKNOWN_ERROR: -32009,
|
||||
TRANSACTION_ERROR: -32010,
|
||||
EXECUTION_ERROR: -32015,
|
||||
EXCEPTION_ERROR: -32016,
|
||||
ACCOUNT_LOCKED: -32020,
|
||||
PASSWORD_INVALID: -32021,
|
||||
ACCOUNT_ERROR: -32023,
|
||||
|
||||
@@ -51,14 +51,14 @@ export default class Http extends JsonRpcBase {
|
||||
|
||||
return fetch(this._url, request)
|
||||
.catch((error) => {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
throw error;
|
||||
})
|
||||
.then((response) => {
|
||||
this._connected = true;
|
||||
this._setConnected();
|
||||
|
||||
if (response.status !== 200) {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
this.error(JSON.stringify({ status: response.status, statusText: response.statusText }));
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${response.status}: ${response.statusText}`);
|
||||
|
||||
|
||||
@@ -37,6 +37,26 @@ describe('api/transport/Http', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport emitter', () => {
|
||||
it('emits close event', (done) => {
|
||||
transport.once('close', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
transport.execute('eth_call');
|
||||
});
|
||||
|
||||
it('emits open event', (done) => {
|
||||
mockHttp([{ method: 'eth_call', reply: { result: '' } }]);
|
||||
|
||||
transport.once('open', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
transport.execute('eth_call');
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport', () => {
|
||||
const RESULT = ['this is some result'];
|
||||
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default class JsonRpcBase {
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export default class JsonRpcBase extends EventEmitter {
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this._id = 1;
|
||||
this._debug = false;
|
||||
this._connected = false;
|
||||
@@ -32,6 +36,20 @@ export default class JsonRpcBase {
|
||||
return json;
|
||||
}
|
||||
|
||||
_setConnected () {
|
||||
if (!this._connected) {
|
||||
this._connected = true;
|
||||
this.emit('open');
|
||||
}
|
||||
}
|
||||
|
||||
_setDisconnected () {
|
||||
if (this._connected) {
|
||||
this._connected = false;
|
||||
this.emit('close');
|
||||
}
|
||||
}
|
||||
|
||||
get id () {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import TransportError from '../error';
|
||||
|
||||
/* global WebSocket */
|
||||
export default class Ws extends JsonRpcBase {
|
||||
constructor (url, token, connect = true) {
|
||||
constructor (url, token, autoconnect = true) {
|
||||
super();
|
||||
|
||||
this._url = url;
|
||||
@@ -32,14 +32,14 @@ export default class Ws extends JsonRpcBase {
|
||||
this._connecting = false;
|
||||
this._connected = false;
|
||||
this._lastError = null;
|
||||
this._autoConnect = false;
|
||||
this._autoConnect = autoconnect;
|
||||
this._retries = 0;
|
||||
this._reconnectTimeoutId = null;
|
||||
|
||||
this._connectPromise = null;
|
||||
this._connectPromiseFunctions = {};
|
||||
|
||||
if (connect) {
|
||||
if (autoconnect) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
@@ -124,11 +124,8 @@ export default class Ws extends JsonRpcBase {
|
||||
}
|
||||
|
||||
_onOpen = (event) => {
|
||||
console.log('ws:onOpen');
|
||||
|
||||
this._connected = true;
|
||||
this._setConnected();
|
||||
this._connecting = false;
|
||||
this._autoConnect = true;
|
||||
this._retries = 0;
|
||||
|
||||
Object.keys(this._messages)
|
||||
@@ -142,7 +139,7 @@ export default class Ws extends JsonRpcBase {
|
||||
}
|
||||
|
||||
_onClose = (event) => {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
this._connecting = false;
|
||||
|
||||
event.timestamp = Date.now();
|
||||
@@ -209,8 +206,8 @@ export default class Ws extends JsonRpcBase {
|
||||
if (result.error) {
|
||||
this.error(event.data);
|
||||
|
||||
// Don't print error if request rejected...
|
||||
if (!/rejected/.test(result.error.message)) {
|
||||
// Don't print error if request rejected or not is not yet up...
|
||||
if (!/(rejected|not yet up)/.test(result.error.message)) {
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,37 @@ describe('api/transport/Ws', () => {
|
||||
let transport;
|
||||
let scope;
|
||||
|
||||
describe('transport emitter', () => {
|
||||
const connect = () => {
|
||||
const scope = mockWs();
|
||||
const transport = new Ws(TEST_WS_URL);
|
||||
|
||||
return { transport, scope };
|
||||
};
|
||||
|
||||
it('emits open event', (done) => {
|
||||
const { transport, scope } = connect();
|
||||
|
||||
transport.once('open', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
it('emits close event', (done) => {
|
||||
const { transport, scope } = connect();
|
||||
|
||||
transport.once('open', () => {
|
||||
scope.server.close();
|
||||
});
|
||||
|
||||
transport.once('close', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport', () => {
|
||||
let result;
|
||||
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
import { keccak_256 } from 'js-sha3'; // eslint-disable-line
|
||||
|
||||
import { hexToBytes } from './format';
|
||||
import { isHex } from './types';
|
||||
|
||||
export function sha3 (value, options) {
|
||||
if (options && options.encoding === 'hex') {
|
||||
const forceHex = options && options.encoding === 'hex';
|
||||
|
||||
if (forceHex || (!options && isHex(value))) {
|
||||
const bytes = hexToBytes(value);
|
||||
return sha3(bytes);
|
||||
}
|
||||
@@ -28,3 +31,5 @@ export function sha3 (value, options) {
|
||||
|
||||
return `0x${hash}`;
|
||||
}
|
||||
|
||||
sha3.text = (val) => sha3(val, { encoding: 'raw' });
|
||||
|
||||
@@ -32,5 +32,14 @@ describe('api/util/sha3', () => {
|
||||
expect(sha3('01020304', { encoding: 'hex' })).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
|
||||
expect(sha3(Uint8Array.from([1, 2, 3, 4]))).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
|
||||
});
|
||||
|
||||
it('should interpret as bytes by default', () => {
|
||||
expect(sha3('0x01020304')).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
|
||||
});
|
||||
|
||||
it('should force text if option is passed', () => {
|
||||
expect(sha3('0x01020304', { encoding: 'raw' })).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
|
||||
expect(sha3.text('0x01020304')).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,10 @@ export function isFunction (test) {
|
||||
}
|
||||
|
||||
export function isHex (_test) {
|
||||
if (!isString(_test)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_test.substr(0, 2) === '0x') {
|
||||
return isHex(_test.slice(2));
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ describe('api/util/types', () => {
|
||||
it('correctly identifies non-hex values', () => {
|
||||
expect(isHex('123j')).to.be.false;
|
||||
});
|
||||
|
||||
it('correctly indentifies non-string values', () => {
|
||||
expect(isHex(false)).to.be.false;
|
||||
expect(isHex()).to.be.false;
|
||||
expect(isHex([1, 2, 3])).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInstanceOf', () => {
|
||||
|
||||
@@ -17,12 +17,20 @@
|
||||
import LogLevel from 'loglevel';
|
||||
|
||||
export const LOG_KEYS = {
|
||||
Balances: {
|
||||
key: 'balances',
|
||||
desc: 'Balances fetching'
|
||||
},
|
||||
TransferModalStore: {
|
||||
path: 'modals/Transfer/store',
|
||||
desc: 'Transfer Modal MobX Store'
|
||||
key: 'modalsTransferStore',
|
||||
desc: 'Transfer modal MobX store'
|
||||
},
|
||||
Signer: {
|
||||
key: 'secureApi',
|
||||
desc: 'The Signer and the Secure API'
|
||||
}
|
||||
};
|
||||
|
||||
export const getLogger = (LOG_KEY) => {
|
||||
return LogLevel.getLogger(LOG_KEY.path);
|
||||
return LogLevel.getLogger(LOG_KEY.key);
|
||||
};
|
||||
|
||||
@@ -73,6 +73,26 @@ export default class BadgeReg {
|
||||
});
|
||||
}
|
||||
|
||||
fetchCertifierByName (name) {
|
||||
return this
|
||||
.getContract()
|
||||
.then((badgeReg) => {
|
||||
return badgeReg.instance.fromName.call({}, [ name ]);
|
||||
})
|
||||
.then(([ id, address, owner ]) => {
|
||||
if (address === ZERO20) {
|
||||
throw new Error(`Certifier ${name} does not exist.`);
|
||||
}
|
||||
|
||||
return this.fetchMeta(id)
|
||||
.then(({ title, icon }) => {
|
||||
const data = { address, id, name, title, icon };
|
||||
this.certifiers[id] = data;
|
||||
return data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchMeta (id) {
|
||||
return this
|
||||
.getContract()
|
||||
|
||||
@@ -91,7 +91,7 @@ export default class Registry {
|
||||
|
||||
lookupAddress (_name) {
|
||||
const name = _name.toLowerCase();
|
||||
const sha3 = this._api.util.sha3(name);
|
||||
const sha3 = this._api.util.sha3.text(name);
|
||||
|
||||
return this.getInstance().then((instance) => {
|
||||
return instance.getAddress.call({}, [sha3, 'A']);
|
||||
|
||||
@@ -39,7 +39,7 @@ export const lookup = (name, key) => (dispatch, getState) => {
|
||||
name = name.toLowerCase();
|
||||
dispatch(lookupStart(name, key));
|
||||
|
||||
getAddress.call({}, [ sha3(name), key ])
|
||||
getAddress.call({}, [ sha3.text(name), key ])
|
||||
.then((address) => dispatch(success('lookup', address)))
|
||||
.catch((err) => {
|
||||
console.error(`could not lookup ${key} for ${name}`);
|
||||
|
||||
@@ -62,7 +62,7 @@ export const reserve = (name) => (dispatch, getState) => {
|
||||
value: fee
|
||||
};
|
||||
const values = [
|
||||
sha3(name)
|
||||
sha3.text(name)
|
||||
];
|
||||
|
||||
return postTx(api, reserve, options, values);
|
||||
@@ -116,7 +116,7 @@ export const drop = (name) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const values = [
|
||||
sha3(name)
|
||||
sha3.text(name)
|
||||
];
|
||||
|
||||
return postTx(api, drop, options, values);
|
||||
|
||||
@@ -54,7 +54,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const values = [
|
||||
sha3(name),
|
||||
sha3.text(name),
|
||||
key,
|
||||
value
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
export const getOwner = (contract, name) => {
|
||||
const { address, api } = contract;
|
||||
|
||||
const key = api.util.sha3(name) + '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const key = api.util.sha3.text(name) + '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const position = api.util.sha3(key, { encoding: 'hex' });
|
||||
|
||||
return api
|
||||
|
||||
221
js/src/dapps/static/console.css
Executable file
221
js/src/dapps/static/console.css
Executable file
@@ -0,0 +1,221 @@
|
||||
#full-screen {
|
||||
}
|
||||
|
||||
textarea:focus, input:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
* { padding: 0; margin: 0; }
|
||||
|
||||
html, body, #full-screen {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#full-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#history-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#history {
|
||||
flex: 1 1 auto;
|
||||
|
||||
display: flex;
|
||||
min-height: min-content;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.entry, #input {
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type {
|
||||
color: #ccc;
|
||||
font-weight: bold;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 11pt;
|
||||
padding: 0 0.5em 0 0.25em;
|
||||
}
|
||||
|
||||
.command .type {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.result .type {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
#input .type {
|
||||
color: #59f;
|
||||
}
|
||||
|
||||
.addwatch {
|
||||
background-color: #cfc;
|
||||
border-top: 1px solid #6e6;
|
||||
}
|
||||
.addwatch .type {
|
||||
color: #040;
|
||||
}
|
||||
.addwatch .text {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
.log.logLevel .type {
|
||||
color: blue;
|
||||
}
|
||||
.log.debugLevel .text {
|
||||
color: blue;
|
||||
}
|
||||
.log.infoLevel .type {
|
||||
color: #00c;
|
||||
}
|
||||
.log.warnLevel {
|
||||
background-color: #fff8dd;
|
||||
border-top: 1px solid #fe8;
|
||||
border-bottom: 1px solid #fe8;
|
||||
}
|
||||
.log.warnLevel .text {
|
||||
color: #440;
|
||||
}
|
||||
.log.warnLevel .type {
|
||||
color: #880;
|
||||
}
|
||||
.log.errorLevel, .error {
|
||||
background-color: #fee;
|
||||
border-top: 1px solid #fbb;
|
||||
border-bottom: 1px solid #fbb;
|
||||
}
|
||||
.log.errorLevel .text, .error .text {
|
||||
color: #c00;
|
||||
}
|
||||
.log.errorLevel .type, .error .type {
|
||||
color: #800;
|
||||
}
|
||||
|
||||
|
||||
span.text {
|
||||
color: black;
|
||||
}
|
||||
.text {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: black;
|
||||
font-weight: 1000;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 11pt;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
div.error {
|
||||
background-color: #fee;
|
||||
border-top: 1px solid #fbb;
|
||||
color: red;
|
||||
}
|
||||
div.command {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
div#input {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#status {
|
||||
background: #eee;
|
||||
padding: 0.25em;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 8pt;
|
||||
color: #888;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#status {
|
||||
display: flex;
|
||||
}
|
||||
.watch {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.watch:not(:first-child) {
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
.expr {
|
||||
color: #888;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.res {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.stringType {
|
||||
color: #c00;
|
||||
}
|
||||
.numberType {
|
||||
color: #00f;
|
||||
}
|
||||
.eObject {
|
||||
color: #00f;
|
||||
}
|
||||
.objectType {
|
||||
font-style: italic;
|
||||
}
|
||||
.fieldType {
|
||||
color: #808;
|
||||
}
|
||||
.undefinedType {
|
||||
color: #888;
|
||||
}
|
||||
.functionType {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#autocomplete-anchor {
|
||||
position: relative;
|
||||
}
|
||||
#autocomplete {
|
||||
position: absolute;
|
||||
left: 1.5em;
|
||||
bottom: 0;
|
||||
background: #f8f8f8;
|
||||
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
|
||||
max-height: 20em;
|
||||
overflow: scroll;
|
||||
}
|
||||
#autocomplete div {
|
||||
font-size: small;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
padding: 0.1em 1em 0.1em 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.ac-already {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ac-new {
|
||||
color: #666;
|
||||
}
|
||||
.ac-selected {
|
||||
background-color: #ccddff;
|
||||
}
|
||||
20
js/src/dapps/static/console.html
Executable file
20
js/src/dapps/static/console.html
Executable file
@@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JS Console dapp</title>
|
||||
<link href="console.css" rel='stylesheet' type='text/css'>
|
||||
<script src="/parity-utils/parity.js"></script>
|
||||
<script src="/parity-utils/web3.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="full-screen">
|
||||
<div id="eval"></div>
|
||||
<div id="history-wrap"><div id="history"></div></div>
|
||||
<div id="autocomplete-anchor"><div id="autocomplete"></div></div>
|
||||
<div id="input"><span class="type">></span><input type="text" class="text" id="command" list="input-datalist"></div>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
<script src="console.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
602
js/src/dapps/static/console.js
Executable file
602
js/src/dapps/static/console.js
Executable file
@@ -0,0 +1,602 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable */
|
||||
// TODO: Fix linting issues
|
||||
|
||||
if (typeof(window.parity) == 'object')
|
||||
window.api = window.parent.secureApi;
|
||||
window.parity.api.subscribe('eth_blockNumber', function (error, blockNumber) {
|
||||
if (error) {
|
||||
console.log('error', error);
|
||||
return;
|
||||
}
|
||||
refreshWatches();
|
||||
});
|
||||
|
||||
function escapeHtml(str) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function getAllPropertyNames(obj) {
|
||||
var props = {};
|
||||
do {
|
||||
Object.getOwnPropertyNames(obj).forEach(n => props[n] = true);
|
||||
} while (obj = Object.getPrototypeOf(obj));
|
||||
return Object.keys(props);
|
||||
}
|
||||
|
||||
function htmlToElement(html) {
|
||||
var template = document.createElement('template');
|
||||
template.innerHTML = html;
|
||||
return template.content.firstChild;
|
||||
}
|
||||
|
||||
function evaluate(x) {
|
||||
try {
|
||||
return eval(x);
|
||||
}
|
||||
catch (err) {
|
||||
return eval('(()=>{var x = ' + x + "; return x;})()")
|
||||
}
|
||||
}
|
||||
|
||||
function displayReady(x) {
|
||||
if (x === undefined)
|
||||
return '<span class="undefinedType">undefined</span>';
|
||||
if (x === null)
|
||||
return '<span class="undefinedType">null</span>';
|
||||
if (typeof(x) == "string")
|
||||
return `"<span class="${typeof(x)}Type">${escapeHtml(x)}</span>"`;
|
||||
if (Object.prototype.toString.call(x) === '[object Array]')
|
||||
return `[${x.map(displayReady).join(', ')}]`;
|
||||
if (typeof(x) == "function")
|
||||
return `<span class="${typeof(x)}Type">function () { /* ... */ }</span>`;
|
||||
if (typeof(x) == "object") {
|
||||
if (x.toString().indexOf('[object ') != 0)
|
||||
return `<span class="${x.constructor.name}Object">${escapeHtml(x.toString())}</span>`;
|
||||
return `<span class="objectType ${x.constructor.name}Object">${x.constructor.name} {${Object.keys(x).map(f => `<span class="fieldType">${escapeHtml(f)}</span>: ${displayReady(x[f])}`).join(', ')}}</span>`;
|
||||
}
|
||||
return `<span class="${typeof(x)}Type">${escapeHtml(JSON.stringify(x))}</span>`;
|
||||
}
|
||||
|
||||
if (!localStorage.history)
|
||||
localStorage.history = "[]";
|
||||
window.historyData = JSON.parse(localStorage.history);
|
||||
window.historyIndex = window.historyData.length;
|
||||
if (!localStorage.watches)
|
||||
localStorage.watches = "[]";
|
||||
window.watches = {};
|
||||
|
||||
function watch(name, f) {
|
||||
let status = document.getElementById("status");
|
||||
let cleanName = name.replace(/[^a-zA-Z0-9]/, '');
|
||||
status.innerHTML += `<div class="watch" id="watch_${cleanName}"><span class="expr" id="expr_${cleanName}">${escapeHtml(name)}</span><span class="res" id="res_${cleanName}"></span></div>`;
|
||||
window.watches[name] = f;
|
||||
}
|
||||
|
||||
var savedWatches = JSON.parse(localStorage.watches);
|
||||
savedWatches.forEach(w => watch(w[1], () => evaluate(w[0])));
|
||||
|
||||
if (typeof(window.web3) == 'object' && window.watches.latest == undefined)
|
||||
watch('latest', () => window.web3.eth.blockNumber);
|
||||
|
||||
|
||||
function refreshWatches() {
|
||||
for (n in window.watches) {
|
||||
let r = window.watches[n]();
|
||||
let cn = n.replace(/[^a-zA-Z0-9]/, '');
|
||||
let e = document.getElementById(`res_${cn}`);
|
||||
if (typeof(r) == 'object' && r.constructor.name == "Promise")
|
||||
r.then(r => e.innerHTML = displayReady(r));
|
||||
else
|
||||
e.innerHTML = displayReady(r);
|
||||
}
|
||||
}
|
||||
|
||||
function removeWatch(name) {
|
||||
let e = document.getElementById(`watch_${name}`);
|
||||
e.parentNode.removeChild(e);
|
||||
delete window.watches[name];
|
||||
}
|
||||
|
||||
function newLog(level, text) {
|
||||
let icon = {
|
||||
debug: " ",
|
||||
log: " ",
|
||||
warn: "⚠",
|
||||
error: "✖",
|
||||
info: "ℹ"
|
||||
};
|
||||
pushLine('<div class="entry log ' + level + 'Level"><span class="type">' + icon[level] + '</span><span class="text">' + escapeHtml(text) + '</span></div>');
|
||||
}
|
||||
|
||||
function exec() {
|
||||
let command = document.getElementById("command");
|
||||
let c = command.value;
|
||||
|
||||
if (c != '') {
|
||||
command.value = "";
|
||||
window.historyData.push(c);
|
||||
while (window.historyData.length > 1000)
|
||||
window.historyData.shift;
|
||||
localStorage.history = JSON.stringify(window.historyData);
|
||||
window.historyIndex = window.historyData.length;
|
||||
|
||||
var html = '';
|
||||
if (c.indexOf("//") == 0) {
|
||||
let n = c.substr(2);
|
||||
savedWatches = savedWatches.filter(x => x[1] != n);
|
||||
localStorage.watches = JSON.stringify(savedWatches);
|
||||
removeWatch(n);
|
||||
}
|
||||
else if (c.indexOf("//") != -1) {
|
||||
x = c.split("//");
|
||||
let e = x[0];
|
||||
savedWatches.push(x);
|
||||
localStorage.watches = JSON.stringify(savedWatches);
|
||||
watch(x[1], () => evaluate(e));
|
||||
pushLine('<div class="entry command"><span class="type">></span><span class="text">' + escapeHtml(c) + '</span></div>');
|
||||
pushLine('<div class="entry addwatch"><span class="type">✓</span><span class="text">Watch added</span></div>');
|
||||
}
|
||||
else {
|
||||
pushLine('<div class="entry command"><span class="type">></span><span class="text">' + escapeHtml(c) + '</span></div>');
|
||||
let res;
|
||||
try {
|
||||
res = evaluate(c);
|
||||
if (typeof(res) == 'object' && res !== null && res.constructor.name == "Promise") {
|
||||
let id = window.historyData.length;
|
||||
pushLine('<div class="entry result"><span class="type"><</span><span class="text" id="pending' + id + '">...</span></div>');
|
||||
res.then(r => document.getElementById('pending' + id).innerHTML = displayReady(r));
|
||||
} else {
|
||||
pushLine('<div class="entry result"><span class="type"><</span><span class="text">' + displayReady(res) + '</span></div>');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
pushLine('<div class="entry error"><span class="type">✖</span><span class="text">Unhandled exception: ' + escapeHtml(err.message) + '</span></div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshWatches();
|
||||
}
|
||||
|
||||
function pushLine(l) {
|
||||
document.getElementById("history").innerHTML += l
|
||||
var h = document.getElementById("history-wrap");
|
||||
h.scrollTop = h.scrollHeight;
|
||||
}
|
||||
|
||||
var autocompletes = [];
|
||||
var currentAuto = null;
|
||||
var currentPots = [];
|
||||
var currentStem = null;
|
||||
|
||||
function updateAutocomplete() {
|
||||
let v = document.getElementById("command").value;
|
||||
if (v.length == 0) {
|
||||
cancelAutocomplete();
|
||||
return;
|
||||
}
|
||||
let t = v.split('.');
|
||||
let last = t.pop();
|
||||
let tj = t.join('.');
|
||||
let ex = t.length > 0 ? tj : 'window';
|
||||
if (currentStem != tj) {
|
||||
autocompletes = eval('getAllPropertyNames('+ex+')');
|
||||
currentStem = tj;
|
||||
}
|
||||
let dl = document.getElementById("autocomplete");
|
||||
currentPots = autocompletes.filter(n => n.startsWith(last));
|
||||
if (currentPots.length > 0) {
|
||||
if (currentPots.indexOf(currentAuto) == -1)
|
||||
currentAuto = currentPots[0];
|
||||
dl.innerHTML = currentPots
|
||||
// .map(n => `${tj != '' ? tj + '.' : ''}${n}`)
|
||||
.map((n, i) => `<div id="pot${i}" class="${currentAuto == n ? 'ac-selected' : 'ac-unselected'}"><span class="ac-already">${escapeHtml(last)}</span><span class="ac-new">${escapeHtml(n.substr(last.length))}</div>`)
|
||||
.join('');
|
||||
dl.hidden = false;
|
||||
} else {
|
||||
cancelAutocomplete();
|
||||
}
|
||||
}
|
||||
|
||||
function enactAutocomplete() {
|
||||
if (currentAuto != null) {
|
||||
document.getElementById("command").value = (currentStem != '' ? currentStem + '.' : '') + currentAuto;
|
||||
cancelAutocomplete();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAutocomplete() {
|
||||
document.getElementById("autocomplete").hidden = true;
|
||||
currentAuto = null;
|
||||
}
|
||||
|
||||
function scrollAutocomplete(positive) {
|
||||
if (currentAuto != null) {
|
||||
var i = currentPots.indexOf(currentAuto);
|
||||
document.getElementById('pot' + i).classList = ['ac-unselected'];
|
||||
if (positive && i < currentPots.length - 1)
|
||||
++i;
|
||||
else if (!positive && i > 0)
|
||||
--i;
|
||||
currentAuto = currentPots[i];
|
||||
let sel = document.getElementById('pot' + i);
|
||||
sel.classList = ['ac-selected'];
|
||||
sel.scrollIntoViewIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("command").addEventListener("paste", updateAutocomplete);
|
||||
document.getElementById("command").addEventListener("input", updateAutocomplete);
|
||||
document.getElementById("command").addEventListener("focusout", cancelAutocomplete);
|
||||
document.getElementById("command").addEventListener("blur", cancelAutocomplete);
|
||||
|
||||
document.getElementById("command").addEventListener("keydown", function(event) {
|
||||
let el = document.getElementById("command");
|
||||
if (currentAuto != null) {
|
||||
if (event.keyCode == 38 || event.keyCode == 40) {
|
||||
event.preventDefault();
|
||||
scrollAutocomplete(event.keyCode == 40);
|
||||
}
|
||||
else if ((event.keyCode == 39 || event.keyCode == 9 || event.keyCode == 13) && el.selectionStart == el.value.length) {
|
||||
event.preventDefault();
|
||||
enactAutocomplete();
|
||||
}
|
||||
else if (event.keyCode == 27) {
|
||||
event.preventDefault();
|
||||
cancelAutocomplete();
|
||||
}
|
||||
} else {
|
||||
let command = document.getElementById("command");
|
||||
if (event.keyCode == 38 && window.historyIndex > 0) {
|
||||
event.preventDefault();
|
||||
window.historyIndex--;
|
||||
command.value = window.historyData[window.historyIndex];
|
||||
}
|
||||
if (event.keyCode == 40 && window.historyIndex < window.historyData.length) {
|
||||
event.preventDefault();
|
||||
window.historyIndex++;
|
||||
command.value = window.historyIndex < window.historyData.length ? window.historyData[window.historyIndex] : "";
|
||||
}
|
||||
}
|
||||
if (event.keyCode >= 48 || event.keyCode == 8) {
|
||||
let t = document.getElementById("command").value;
|
||||
setTimeout(() => {
|
||||
if (t != document.getElementById("command").value)
|
||||
updateAutocomplete();
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
setTimeout(() => {
|
||||
if (el.selectionStart != el.value.length)
|
||||
cancelAutocomplete();
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("command").addEventListener("keyup", function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
exec();
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof(window.parity) == 'object') {
|
||||
document.getElementById("command").focus();
|
||||
window.web3 = web3;
|
||||
window.parity = parity;
|
||||
}
|
||||
refreshWatches();
|
||||
|
||||
["debug", "error", "info", "log", "warn"].forEach(n => { let old = window.console[n]; window.console[n] = x => { old(x); newLog(n, x); }; });
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///// Home comforts.
|
||||
|
||||
|
||||
// Usage example:
|
||||
// web3.eth.traceCall({
|
||||
// to: theChicken.address,
|
||||
// data: theChicken.withdraw.getData(100000000000000000),
|
||||
// gas: 100000
|
||||
// },
|
||||
// `["trace", "vmTrace", "stateDiff"]
|
||||
// )
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'traceCall',
|
||||
call: 'trace_call',
|
||||
params: 2,
|
||||
inputFormatter: [web3._extend.formatters.inputCallFormatter, null]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'traceSendRawTransaction',
|
||||
call: 'trace_rawTransaction',
|
||||
params: 2,
|
||||
inputFormatter: [null, null]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'traceReplayTransaction',
|
||||
call: 'trace_replayTransaction',
|
||||
params: 2,
|
||||
inputFormatter: [null, null]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'setMode',
|
||||
call: 'ethcore_setMode',
|
||||
params: 1,
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'mode',
|
||||
call: 'ethcore_mode',
|
||||
params: 0,
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'traceTransaction',
|
||||
call: 'trace_Transaction',
|
||||
params: 1,
|
||||
inputFormatter: [null]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'gasPriceStatistics',
|
||||
call: 'ethcore_gasPriceStatistics',
|
||||
params: 0,
|
||||
outputFormatter: function(a) { return a.map(web3.toBigNumber); }
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'registryAddress',
|
||||
call: 'ethcore_registryAddress',
|
||||
params: 0
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'accountsInfo',
|
||||
call: 'personal_accountsInfo',
|
||||
outputFormatter: function(m) { Object.keys(m).forEach(k => {
|
||||
m[k].meta = JSON.parse(m[k].meta);
|
||||
m[k].meta.name = m[k].name;
|
||||
m[k].meta.uuid = m[k].uuid;
|
||||
m[k] = m[k].meta;
|
||||
}); return m; },
|
||||
params: 0
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'setAccountName',
|
||||
call: 'personal_setAccountName',
|
||||
params: 2,
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'setAccountMeta',
|
||||
call: 'personal_setAccountMeta',
|
||||
params: 2,
|
||||
inputFormatter: [a => a, JSON.stringify]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'postTransaction',
|
||||
call: 'eth_postTransaction',
|
||||
params: 1,
|
||||
inputFormatter: [web3._extend.formatters.inputCallFormatter]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'postSign',
|
||||
call: 'eth_postSign',
|
||||
params: 1
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'encryptMessage',
|
||||
call: 'ethcore_encryptMessage',
|
||||
params: 2
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'checkRequest',
|
||||
call: 'eth_checkRequest',
|
||||
params: 1
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
web3._extend({
|
||||
property: 'eth',
|
||||
methods: [
|
||||
new web3._extend.Method({
|
||||
name: 'listAccounts',
|
||||
call: 'ethcore_listAccounts',
|
||||
params: 0
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
{
|
||||
var postTransaction = web3.eth.postTransaction.bind(web3.eth);
|
||||
var sendTransaction = web3.eth.sendTransaction.bind(web3.eth);
|
||||
web3.eth.sendTransaction = function(options, f) {
|
||||
// No callback - do sync API.
|
||||
if (typeof f != "function")
|
||||
return sendTransaction(options);
|
||||
// Callback - use async API.
|
||||
var id = postTransaction(options);
|
||||
console.log("Posted trasaction id=" + id);
|
||||
var timerId = window.setInterval(check, 500);
|
||||
function check() {
|
||||
try {
|
||||
let r = web3.eth.checkRequest(id);
|
||||
if (typeof r == 'string') {
|
||||
clearInterval(timerId);
|
||||
if (r == "0x0000000000000000000000000000000000000000000000000000000000000000")
|
||||
f("Rejected", r);
|
||||
else
|
||||
f(null, r);
|
||||
} else if (r !== null) {
|
||||
console.log("checkRequest returned: " + r);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
clearInterval(timerId);
|
||||
f("Rejected", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
web3.eth.installInterceptor = function(interceptor) {
|
||||
var oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth);
|
||||
web3.eth.sendTransaction = function(options, f) {
|
||||
if (interceptor(options) == false)
|
||||
return "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
return oldSendTransaction(options, f);
|
||||
};
|
||||
}
|
||||
|
||||
web3.eth.reporter = function(e, r) {
|
||||
if (e) {
|
||||
console.log("Error confirming transaction: " + e);
|
||||
} else {
|
||||
var addr = r;
|
||||
var confirmed = false;
|
||||
var timer_id = window.setInterval(check, 500);
|
||||
function check() {
|
||||
var receipt = web3.eth.getTransactionReceipt(addr);
|
||||
if (receipt != null) {
|
||||
if (!confirmed) {
|
||||
console.log("Transaction confirmed (" + r + "); used " + receipt.gasUsed + " gas; left " + receipt.logs.length + " logs; mining...");
|
||||
confirmed = true;
|
||||
}
|
||||
if (typeof receipt.blockHash == 'string') {
|
||||
clearInterval(timer_id);
|
||||
console.log("Mined into block " + receipt.blockNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var oldSha3 = web3.sha3
|
||||
web3.sha3 = function(data, format) {
|
||||
if (typeof format !== 'string' || (format != 'hex' && format != 'bin'))
|
||||
format = data.startsWith('0x') ? 'hex' : 'bin';
|
||||
return oldSha3(data, {encoding: format});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var Registry = web3.eth.contract([{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"key","type":"string"},{"indexed":false,"name":"plainKey","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]);
|
||||
web3.eth.registry = Registry.at(web3.eth.registryAddress());
|
||||
web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field);
|
||||
web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field);
|
||||
web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field);
|
||||
|
||||
var TokenReg = web3.eth.contract([{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"token","outputs":[{"name":"addr","type":"address"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"}],"name":"register","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_tla","type":"string"}],"name":"fromTLA","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"tokenCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"},{"indexed":false,"name":"name","type":"string"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]);
|
||||
web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A'));
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import { IconButton } from 'material-ui';
|
||||
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||
import ActionAutorenew from 'material-ui/svg-icons/action/autorenew';
|
||||
|
||||
|
||||
@@ -88,12 +88,13 @@ export default class RecoveryPhrase extends Component {
|
||||
value={ password2 }
|
||||
onChange={ this.onEditPassword2 } />
|
||||
</div>
|
||||
<Checkbox
|
||||
className={ styles.checkbox }
|
||||
label='Key was created with Parity <1.4.5 on Windows'
|
||||
checked={ windowsPhrase }
|
||||
onCheck={ this.onToggleWindowsPhrase } />
|
||||
</div>
|
||||
<Checkbox
|
||||
className={ styles.checkbox }
|
||||
label='Key was created with Parity <1.4.5 on Windows'
|
||||
checked={ windowsPhrase }
|
||||
onCheck={ this.onToggleWindowsPhrase }
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ActionDone from 'material-ui/svg-icons/action/done';
|
||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
@@ -100,44 +101,45 @@ export default class CreateAccount extends Component {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
return (
|
||||
<CreationType
|
||||
onChange={ this.onChangeType } />
|
||||
<CreationType onChange={ this.onChangeType } />
|
||||
);
|
||||
|
||||
case 1:
|
||||
if (createType === 'fromNew') {
|
||||
return (
|
||||
<NewAccount
|
||||
onChange={ this.onChangeDetails } />
|
||||
<NewAccount onChange={ this.onChangeDetails } />
|
||||
);
|
||||
} else if (createType === 'fromGeth') {
|
||||
}
|
||||
|
||||
if (createType === 'fromGeth') {
|
||||
return (
|
||||
<NewGeth
|
||||
accounts={ accounts }
|
||||
onChange={ this.onChangeGeth } />
|
||||
onChange={ this.onChangeGeth }
|
||||
/>
|
||||
);
|
||||
} else if (createType === 'fromPhrase') {
|
||||
}
|
||||
|
||||
if (createType === 'fromPhrase') {
|
||||
return (
|
||||
<RecoveryPhrase
|
||||
onChange={ this.onChangeDetails } />
|
||||
<RecoveryPhrase onChange={ this.onChangeDetails } />
|
||||
);
|
||||
} else if (createType === 'fromRaw') {
|
||||
}
|
||||
|
||||
if (createType === 'fromRaw') {
|
||||
return (
|
||||
<RawKey
|
||||
onChange={ this.onChangeDetails } />
|
||||
<RawKey onChange={ this.onChangeDetails } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NewImport
|
||||
onChange={ this.onChangeWallet } />
|
||||
<NewImport onChange={ this.onChangeWallet } />
|
||||
);
|
||||
|
||||
case 2:
|
||||
if (createType === 'fromGeth') {
|
||||
return (
|
||||
<AccountDetailsGeth
|
||||
addresses={ this.state.gethAddresses } />
|
||||
<AccountDetailsGeth addresses={ this.state.gethAddresses } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +147,8 @@ export default class CreateAccount extends Component {
|
||||
<AccountDetails
|
||||
address={ this.state.address }
|
||||
name={ this.state.name }
|
||||
phrase={ this.state.phrase } />
|
||||
phrase={ this.state.phrase }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -210,11 +213,14 @@ export default class CreateAccount extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Warning warning={
|
||||
<FormattedMessage
|
||||
id='createAccount.warning.insecurePassword'
|
||||
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.' />
|
||||
} />
|
||||
<Warning
|
||||
warning={
|
||||
<FormattedMessage
|
||||
id='createAccount.warning.insecurePassword'
|
||||
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -371,7 +377,7 @@ export default class CreateAccount extends Component {
|
||||
}
|
||||
|
||||
onChangeDetails = (canCreate, { name, passwordHint, address, password, phrase, rawKey, windowsPhrase }) => {
|
||||
this.setState({
|
||||
const nextState = {
|
||||
canCreate,
|
||||
name,
|
||||
passwordHint,
|
||||
@@ -380,7 +386,9 @@ export default class CreateAccount extends Component {
|
||||
phrase,
|
||||
windowsPhrase: windowsPhrase || false,
|
||||
rawKey
|
||||
});
|
||||
};
|
||||
|
||||
this.setState(nextState);
|
||||
}
|
||||
|
||||
onChangeRaw = (canCreate, rawKey) => {
|
||||
|
||||
@@ -391,6 +391,10 @@ class DeployContract extends Component {
|
||||
.then(([gasEst, gas]) => {
|
||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('estimateGas', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { toWei } from '~/api/util/wei';
|
||||
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash, Warning } from '~/ui';
|
||||
@@ -156,8 +155,7 @@ class ExecuteContract extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Warning
|
||||
warning={ errorEstimated } />
|
||||
<Warning warning={ errorEstimated } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -379,6 +377,7 @@ class ExecuteContract extends Component {
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('estimateGas', error);
|
||||
});
|
||||
}
|
||||
@@ -470,11 +469,7 @@ function mapStateToProps (initState, initProps) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(ExecuteContract);
|
||||
|
||||
@@ -68,13 +68,13 @@ const STORE = {
|
||||
};
|
||||
|
||||
function createApi (result = true) {
|
||||
const sha3 = sinon.stub().resolves('0x0000000000000000000000000000000000000000');
|
||||
sha3.text = sha3;
|
||||
return {
|
||||
parity: {
|
||||
registryAddress: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
|
||||
},
|
||||
util: {
|
||||
sha3: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
|
||||
}
|
||||
util: { sha3 }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { newError, openSnackbar } from '~/redux/actions';
|
||||
import { Button, Modal, IdentityName, IdentityIcon } from '~/ui';
|
||||
import { Button, Modal, IdentityName, IdentityIcon, PasswordStrength } from '~/ui';
|
||||
import Form, { Input } from '~/ui/Form';
|
||||
import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons';
|
||||
|
||||
@@ -120,7 +120,7 @@ class PasswordManager extends Component {
|
||||
}
|
||||
|
||||
renderPage () {
|
||||
const { busy, isRepeatValid, passwordHint } = this.store;
|
||||
const { busy, isRepeatValid, newPassword, passwordHint } = this.store;
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
@@ -176,8 +176,6 @@ class PasswordManager extends Component {
|
||||
defaultMessage='current password' />
|
||||
}
|
||||
onChange={ this.onEditCurrentPassword }
|
||||
onSubmit={ this.changePassword }
|
||||
submitOnBlur={ false }
|
||||
type='password' />
|
||||
<Input
|
||||
disabled={ busy }
|
||||
@@ -192,8 +190,6 @@ class PasswordManager extends Component {
|
||||
defaultMessage='(optional) new password hint' />
|
||||
}
|
||||
onChange={ this.onEditNewPasswordHint }
|
||||
onSubmit={ this.changePassword }
|
||||
submitOnBlur={ false }
|
||||
value={ passwordHint } />
|
||||
<div className={ styles.passwords }>
|
||||
<div className={ styles.password }>
|
||||
@@ -240,6 +236,8 @@ class PasswordManager extends Component {
|
||||
type='password' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PasswordStrength input={ newPassword } />
|
||||
</div>
|
||||
</Form>
|
||||
</Tab>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { bytesToHex } from '~/api/util/format';
|
||||
import Contract from '~/api/contract';
|
||||
import ERRORS from './errors';
|
||||
import { ERROR_CODES } from '~/api/transport/error';
|
||||
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
import GasPriceStore from '~/ui/GasPriceEditor/store';
|
||||
import { getLogger, LOG_KEYS } from '~/config';
|
||||
|
||||
@@ -220,13 +220,27 @@ export default class TransferStore {
|
||||
}
|
||||
|
||||
@action _attachWalletOperation = (txhash) => {
|
||||
if (!txhash || /^(0x)?0*$/.test(txhash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ethSubscriptionId = null;
|
||||
|
||||
// Number of blocks left to look-up (unsub after 15 blocks if nothing)
|
||||
let nBlocksLeft = 15;
|
||||
|
||||
return this.api.subscribe('eth_blockNumber', () => {
|
||||
this.api.eth
|
||||
.getTransactionReceipt(txhash)
|
||||
.then((tx) => {
|
||||
if (nBlocksLeft <= 0) {
|
||||
this.api.unsubscribe(ethSubscriptionId);
|
||||
ethSubscriptionId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tx) {
|
||||
nBlocksLeft--;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -239,6 +253,10 @@ export default class TransferStore {
|
||||
this.operation = operations[0];
|
||||
}
|
||||
|
||||
this.api.unsubscribe(ethSubscriptionId);
|
||||
ethSubscriptionId = null;
|
||||
})
|
||||
.catch(() => {
|
||||
this.api.unsubscribe(ethSubscriptionId);
|
||||
ethSubscriptionId = null;
|
||||
});
|
||||
@@ -357,6 +375,7 @@ export default class TransferStore {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('etimateGas', error);
|
||||
this.recalculate(redo);
|
||||
});
|
||||
@@ -441,6 +460,8 @@ export default class TransferStore {
|
||||
const gasTotal = new BigNumber(_gasTotal || 0);
|
||||
const { valueAll, isEth, isWallet } = this;
|
||||
|
||||
log.debug('@getValues', 'gas', gasTotal.toFormat());
|
||||
|
||||
if (!valueAll) {
|
||||
const value = this.getTokenValue();
|
||||
|
||||
@@ -568,6 +589,7 @@ export default class TransferStore {
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
}
|
||||
@@ -626,7 +648,8 @@ export default class TransferStore {
|
||||
options.gas = MAX_GAS_ESTIMATION;
|
||||
}
|
||||
|
||||
const { token } = this.getValues(options.gas);
|
||||
const gasTotal = new BigNumber(options.gas || DEFAULT_GAS).mul(options.gasPrice || DEFAULT_GASPRICE);
|
||||
const { token } = this.getValues(gasTotal);
|
||||
|
||||
if (isEth && !isWallet && !forceToken) {
|
||||
options.value = token;
|
||||
|
||||
@@ -27,7 +27,7 @@ const STEP_UPDATING = 1;
|
||||
const STEP_COMPLETED = 2;
|
||||
const STEP_ERROR = 2;
|
||||
|
||||
const CHECK_INTERVAL = 1 * A_MINUTE;
|
||||
let instance = null;
|
||||
|
||||
export default class Store {
|
||||
@observable available = null;
|
||||
@@ -44,8 +44,6 @@ export default class Store {
|
||||
|
||||
this.loadStorage();
|
||||
this.checkUpgrade();
|
||||
|
||||
setInterval(this.checkUpgrade, CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
@computed get isVisible () {
|
||||
@@ -119,10 +117,10 @@ export default class Store {
|
||||
|
||||
checkUpgrade = () => {
|
||||
if (!this._api) {
|
||||
return;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
Promise
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.upgradeReady(),
|
||||
this._api.parity.consensusCapability(),
|
||||
@@ -134,11 +132,23 @@ export default class Store {
|
||||
}
|
||||
|
||||
this.setVersions(available, version, consensusCapability);
|
||||
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('checkUpgrade', error);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static get (api) {
|
||||
if (!instance) {
|
||||
instance = new Store(api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Checkbox } from 'material-ui';
|
||||
import InfoIcon from 'material-ui/svg-icons/action/info-outline';
|
||||
@@ -33,10 +34,11 @@ import styles from './gatherData.css';
|
||||
export default class GatherData extends Component {
|
||||
static propTypes = {
|
||||
fee: React.PropTypes.instanceOf(BigNumber),
|
||||
method: PropTypes.string.isRequired,
|
||||
fields: PropTypes.array.isRequired,
|
||||
isVerified: nullableProptype(PropTypes.bool.isRequired),
|
||||
hasRequested: nullableProptype(PropTypes.bool.isRequired),
|
||||
isServerRunning: nullableProptype(PropTypes.bool.isRequired),
|
||||
isVerified: nullableProptype(PropTypes.bool.isRequired),
|
||||
method: PropTypes.string.isRequired,
|
||||
setConsentGiven: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
@@ -48,13 +50,19 @@ export default class GatherData extends Component {
|
||||
return (
|
||||
<Form>
|
||||
{ howItWorks }
|
||||
{ this.renderServerRunning() }
|
||||
{ this.renderFee() }
|
||||
{ this.renderCertified() }
|
||||
{ this.renderRequested() }
|
||||
{ this.renderFields() }
|
||||
<Checkbox
|
||||
className={ styles.spacing }
|
||||
label={ 'I agree to the terms and conditions below.' }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.termsOfService'
|
||||
defaultMessage='I agree to the terms and conditions below.'
|
||||
/>
|
||||
}
|
||||
disabled={ isVerified }
|
||||
onCheck={ this.consentOnChange }
|
||||
/>
|
||||
@@ -63,16 +71,75 @@ export default class GatherData extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderServerRunning () {
|
||||
const { isServerRunning } = this.props;
|
||||
|
||||
if (isServerRunning) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<SuccessIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.true'
|
||||
defaultMessage='The verification server is running.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (isServerRunning === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.false'
|
||||
defaultMessage='The verification server is not running.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.pending'
|
||||
defaultMessage='Checking if the verification server is running…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderFee () {
|
||||
const { fee } = this.props;
|
||||
|
||||
if (!fee) {
|
||||
return (<p>Fetching the fee…</p>);
|
||||
}
|
||||
if (fee.eq(0)) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.nofee'
|
||||
defaultMessage='There is no additional fee.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>The fee is { fromWei(fee).toFixed(3) } ETH.</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.fee'
|
||||
defaultMessage='The additional fee is {amount} ETH.'
|
||||
values={ {
|
||||
amount: fromWei(fee).toFixed(3)
|
||||
} }
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -84,19 +151,34 @@ export default class GatherData extends Component {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>Your account is already verified.</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.true'
|
||||
defaultMessage='Your account is already verified.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (isVerified === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<SuccessIcon />
|
||||
<p className={ styles.message }>Your account is not verified yet.</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.false'
|
||||
defaultMessage='Your account is not verified yet.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>Checking if your account is verified…</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.pending'
|
||||
defaultMessage='Checking if your account is verified…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,19 +194,34 @@ export default class GatherData extends Component {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>You already requested verification.</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.true'
|
||||
defaultMessage='You already requested verification.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (hasRequested === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<SuccessIcon />
|
||||
<p className={ styles.message }>You did not request verification yet.</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.false'
|
||||
defaultMessage='You did not request verification yet.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>Checking if you requested verification…</p>
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.pending'
|
||||
defaultMessage='Checking if you requested verification…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,10 @@ import EmailVerificationABI from '~/contracts/abi/email-verification.json';
|
||||
import VerificationStore, {
|
||||
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
|
||||
} from './store';
|
||||
import { postToServer } from '../../3rdparty/email-verification';
|
||||
import { isServerRunning, postToServer } from '../../3rdparty/email-verification';
|
||||
|
||||
const EMAIL_VERIFICATION = 7; // id in the `BadgeReg.sol` contract
|
||||
// name in the `BadgeReg.sol` contract
|
||||
const EMAIL_VERIFICATION = 'emailverification';
|
||||
|
||||
export default class EmailVerificationStore extends VerificationStore {
|
||||
@observable email = '';
|
||||
@@ -59,7 +60,11 @@ export default class EmailVerificationStore extends VerificationStore {
|
||||
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
|
||||
}
|
||||
|
||||
requestValues = () => [ sha3(this.email) ]
|
||||
isServerRunning = () => {
|
||||
return isServerRunning(this.isTestnet);
|
||||
}
|
||||
|
||||
requestValues = () => [ sha3.text(this.email) ]
|
||||
|
||||
@action setEmail = (email) => {
|
||||
this.email = email;
|
||||
|
||||
@@ -21,9 +21,10 @@ import SMSVerificationABI from '~/contracts/abi/sms-verification.json';
|
||||
import VerificationStore, {
|
||||
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
|
||||
} from './store';
|
||||
import { postToServer } from '../../3rdparty/sms-verification';
|
||||
import { isServerRunning, postToServer } from '../../3rdparty/sms-verification';
|
||||
|
||||
const SMS_VERIFICATION = 0; // id in the `BadgeReg.sol` contract
|
||||
// name in the `BadgeReg.sol` contract
|
||||
const SMS_VERIFICATION = 'smsverification';
|
||||
|
||||
export default class SMSVerificationStore extends VerificationStore {
|
||||
@observable number = '';
|
||||
@@ -58,6 +59,10 @@ export default class SMSVerificationStore extends VerificationStore {
|
||||
super(api, SMSVerificationABI, SMS_VERIFICATION, account, isTestnet);
|
||||
}
|
||||
|
||||
isServerRunning = () => {
|
||||
return isServerRunning(this.isTestnet);
|
||||
}
|
||||
|
||||
@action setNumber = (number) => {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@@ -40,19 +40,20 @@ export default class VerificationStore {
|
||||
@observable fee = null;
|
||||
@observable isVerified = null;
|
||||
@observable hasRequested = null;
|
||||
@observable isServerRunning = null;
|
||||
@observable consentGiven = false;
|
||||
@observable requestTx = null;
|
||||
@observable code = '';
|
||||
@observable isCodeValid = null;
|
||||
@observable confirmationTx = null;
|
||||
|
||||
constructor (api, abi, certifierId, account, isTestnet) {
|
||||
constructor (api, abi, certifierName, account, isTestnet) {
|
||||
this.api = api;
|
||||
this.account = account;
|
||||
this.isTestnet = isTestnet;
|
||||
|
||||
this.step = LOADING;
|
||||
Contracts.get().badgeReg.fetchCertifier(certifierId)
|
||||
Contracts.get().badgeReg.fetchCertifierByName(certifierName)
|
||||
.then(({ address }) => {
|
||||
this.contract = new Contract(api, abi).at(address);
|
||||
this.load();
|
||||
@@ -73,6 +74,14 @@ export default class VerificationStore {
|
||||
const { contract, account } = this;
|
||||
this.step = LOADING;
|
||||
|
||||
const isServerRunning = this.isServerRunning()
|
||||
.then((isRunning) => {
|
||||
this.isServerRunning = isRunning;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if server is running: ' + err.message;
|
||||
});
|
||||
|
||||
const fee = contract.instance.fee.call()
|
||||
.then((fee) => {
|
||||
this.fee = fee;
|
||||
@@ -101,7 +110,7 @@ export default class VerificationStore {
|
||||
});
|
||||
|
||||
Promise
|
||||
.all([ fee, isVerified, hasRequested ])
|
||||
.all([ isServerRunning, fee, isVerified, hasRequested ])
|
||||
.then(() => {
|
||||
this.step = QUERY_DATA;
|
||||
});
|
||||
@@ -120,7 +129,7 @@ export default class VerificationStore {
|
||||
|
||||
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
|
||||
const options = { from: account };
|
||||
const values = [ sha3(code) ];
|
||||
const values = [ sha3.text(code) ];
|
||||
|
||||
this.code = code;
|
||||
this.isCodeValid = null;
|
||||
@@ -192,7 +201,7 @@ export default class VerificationStore {
|
||||
|
||||
@action sendConfirmation = () => {
|
||||
const { api, account, contract, code } = this;
|
||||
const token = sha3(code);
|
||||
const token = sha3.text(code);
|
||||
|
||||
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
|
||||
const options = { from: account };
|
||||
|
||||
@@ -94,10 +94,10 @@ class Verification extends Component {
|
||||
return (
|
||||
<Modal
|
||||
actions={ this.renderDialogActions(phase, error, isStepValid) }
|
||||
title='verify your account'
|
||||
visible
|
||||
current={ phase }
|
||||
steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
|
||||
title='verify your account'
|
||||
visible
|
||||
waiting={ error ? [] : [ 2, 4 ] }
|
||||
>
|
||||
{ this.renderStep(phase, error) }
|
||||
@@ -111,8 +111,9 @@ class Verification extends Component {
|
||||
|
||||
const cancel = (
|
||||
<Button
|
||||
key='cancel' label='Cancel'
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
label='Cancel'
|
||||
onClick={ onClose }
|
||||
/>
|
||||
);
|
||||
@@ -125,9 +126,10 @@ class Verification extends Component {
|
||||
<div>
|
||||
{ cancel }
|
||||
<Button
|
||||
key='done' label='Done'
|
||||
disabled={ !isStepValid }
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label='Done'
|
||||
onClick={ onClose }
|
||||
/>
|
||||
</div>
|
||||
@@ -160,9 +162,15 @@ class Verification extends Component {
|
||||
<div>
|
||||
{ cancel }
|
||||
<Button
|
||||
key='next' label='Next'
|
||||
disabled={ !isStepValid }
|
||||
icon={ <IdentityIcon address={ account } button /> }
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ account }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='next'
|
||||
label='Next'
|
||||
onClick={ action }
|
||||
/>
|
||||
</div>
|
||||
@@ -180,16 +188,16 @@ class Verification extends Component {
|
||||
const value = values.findIndex((v) => v.value === method);
|
||||
return (
|
||||
<RadioButtons
|
||||
onChange={ this.selectMethod }
|
||||
value={ value < 0 ? 0 : value }
|
||||
values={ values }
|
||||
onChange={ this.selectMethod }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
step,
|
||||
fee, isVerified, hasRequested,
|
||||
isServerRunning, fee, isVerified, hasRequested,
|
||||
requestTx, isCodeValid, confirmationTx,
|
||||
setCode
|
||||
} = this.store;
|
||||
@@ -223,15 +231,21 @@ class Verification extends Component {
|
||||
|
||||
return (
|
||||
<GatherData
|
||||
fee={ fee }
|
||||
hasRequested={ hasRequested }
|
||||
isServerRunning={ isServerRunning }
|
||||
isVerified={ isVerified }
|
||||
method={ method } fields={ fields }
|
||||
fee={ fee } isVerified={ isVerified } hasRequested={ hasRequested }
|
||||
setConsentGiven={ setConsentGiven }
|
||||
/>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<SendRequest step={ step } tx={ requestTx } />
|
||||
<SendRequest
|
||||
step={ step }
|
||||
tx={ requestTx }
|
||||
/>
|
||||
);
|
||||
|
||||
case 3:
|
||||
@@ -245,16 +259,19 @@ class Verification extends Component {
|
||||
}
|
||||
return (
|
||||
<QueryCode
|
||||
receiver={ receiver }
|
||||
hint={ hint }
|
||||
isCodeValid={ isCodeValid }
|
||||
receiver={ receiver }
|
||||
setCode={ setCode }
|
||||
/>
|
||||
);
|
||||
|
||||
case 4:
|
||||
return (
|
||||
<SendConfirmation step={ step } tx={ confirmationTx } />
|
||||
<SendConfirmation
|
||||
step={ step }
|
||||
tx={ confirmationTx }
|
||||
/>
|
||||
);
|
||||
|
||||
case 5:
|
||||
|
||||
@@ -21,51 +21,164 @@ import { padRight } from '~/api/util/format';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class Balances {
|
||||
constructor (store, api) {
|
||||
this._api = api;
|
||||
this._store = store;
|
||||
|
||||
this._tokenregSubId = null;
|
||||
this._tokenregMetaSubId = null;
|
||||
this._tokenreg = null;
|
||||
this._tokenregSID = null;
|
||||
this._tokenMetaSID = null;
|
||||
|
||||
// Throttled `retrieveTokens` function
|
||||
this._blockNumberSID = null;
|
||||
this._accountsInfoSID = null;
|
||||
|
||||
// Throtthled load tokens (no more than once
|
||||
// every minute)
|
||||
this.loadTokens = throttle(
|
||||
this._loadTokens,
|
||||
60 * 1000,
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
|
||||
// Throttled `_fetchBalances` function
|
||||
// that gets called max once every 40s
|
||||
this.longThrottledFetch = throttle(
|
||||
this.fetchBalances,
|
||||
this._fetchBalances,
|
||||
40 * 1000,
|
||||
{ trailing: true }
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
this.shortThrottledFetch = throttle(
|
||||
this.fetchBalances,
|
||||
this._fetchBalances,
|
||||
2 * 1000,
|
||||
{ trailing: true }
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
// Fetch all tokens every 2 minutes
|
||||
this.throttledTokensFetch = throttle(
|
||||
this.fetchTokens,
|
||||
60 * 1000,
|
||||
{ trailing: true }
|
||||
this._fetchTokens,
|
||||
2 * 60 * 1000,
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
// Unsubscribe previous instance if it exists
|
||||
if (instance) {
|
||||
Balances.stop();
|
||||
}
|
||||
}
|
||||
|
||||
start () {
|
||||
this.subscribeBlockNumber();
|
||||
this.subscribeAccountsInfo();
|
||||
static get (store = {}) {
|
||||
if (!instance && store) {
|
||||
const { api } = store.getState();
|
||||
return Balances.instantiate(store, api);
|
||||
}
|
||||
|
||||
this.loadTokens();
|
||||
return instance;
|
||||
}
|
||||
|
||||
static instantiate (store, api) {
|
||||
if (!instance) {
|
||||
instance = new Balances(store, api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static start () {
|
||||
if (!instance) {
|
||||
return Promise.reject('BalancesProvider has not been intiated yet');
|
||||
}
|
||||
|
||||
const self = instance;
|
||||
|
||||
// Unsubscribe from previous subscriptions
|
||||
return Balances
|
||||
.stop()
|
||||
.then(() => self.loadTokens())
|
||||
.then(() => {
|
||||
const promises = [
|
||||
self.subscribeBlockNumber(),
|
||||
self.subscribeAccountsInfo()
|
||||
];
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
static stop () {
|
||||
if (!instance) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const self = instance;
|
||||
const promises = [];
|
||||
|
||||
if (self._blockNumberSID) {
|
||||
const p = self._api
|
||||
.unsubscribe(self._blockNumberSID)
|
||||
.then(() => {
|
||||
self._blockNumberSID = null;
|
||||
});
|
||||
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
if (self._accountsInfoSID) {
|
||||
const p = self._api
|
||||
.unsubscribe(self._accountsInfoSID)
|
||||
.then(() => {
|
||||
self._accountsInfoSID = null;
|
||||
});
|
||||
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
// Unsubscribe without adding the promises
|
||||
// to the result, since it would have to wait for a
|
||||
// reconnection to resolve if the Node is disconnected
|
||||
if (self._tokenreg) {
|
||||
if (self._tokenregSID) {
|
||||
const tokenregSID = self._tokenregSID;
|
||||
|
||||
self._tokenreg
|
||||
.unsubscribe(tokenregSID)
|
||||
.then(() => {
|
||||
if (self._tokenregSID === tokenregSID) {
|
||||
self._tokenregSID = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (self._tokenMetaSID) {
|
||||
const tokenMetaSID = self._tokenMetaSID;
|
||||
|
||||
self._tokenreg
|
||||
.unsubscribe(tokenMetaSID)
|
||||
.then(() => {
|
||||
if (self._tokenMetaSID === tokenMetaSID) {
|
||||
self._tokenMetaSID = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
subscribeAccountsInfo () {
|
||||
this._api
|
||||
return this._api
|
||||
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchBalances();
|
||||
this.fetchAllBalances();
|
||||
})
|
||||
.then((accountsInfoSID) => {
|
||||
this._accountsInfoSID = accountsInfoSID;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeAccountsInfo', error);
|
||||
@@ -73,49 +186,97 @@ export default class Balances {
|
||||
}
|
||||
|
||||
subscribeBlockNumber () {
|
||||
this._api
|
||||
return this._api
|
||||
.subscribe('eth_blockNumber', (error) => {
|
||||
if (error) {
|
||||
return console.warn('_subscribeBlockNumber', error);
|
||||
}
|
||||
|
||||
const { syncing } = this._store.getState().nodeStatus;
|
||||
|
||||
this.throttledTokensFetch();
|
||||
|
||||
// If syncing, only retrieve balances once every
|
||||
// few seconds
|
||||
if (syncing) {
|
||||
this.shortThrottledFetch.cancel();
|
||||
return this.longThrottledFetch();
|
||||
}
|
||||
|
||||
this.longThrottledFetch.cancel();
|
||||
return this.shortThrottledFetch();
|
||||
return this.fetchAllBalances();
|
||||
})
|
||||
.then((blockNumberSID) => {
|
||||
this._blockNumberSID = blockNumberSID;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeBlockNumber', error);
|
||||
});
|
||||
}
|
||||
|
||||
fetchBalances () {
|
||||
this._store.dispatch(fetchBalances());
|
||||
fetchAllBalances (options = {}) {
|
||||
// If it's a network change, reload the tokens
|
||||
// ( and then fetch the tokens balances ) and fetch
|
||||
// the accounts balances
|
||||
if (options.changedNetwork) {
|
||||
this.loadTokens({ skipNotifications: true });
|
||||
this.loadTokens.flush();
|
||||
|
||||
this.fetchBalances({
|
||||
force: true,
|
||||
skipNotifications: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchTokensBalances(options);
|
||||
this.fetchBalances(options);
|
||||
}
|
||||
|
||||
fetchTokens () {
|
||||
this._store.dispatch(fetchTokensBalances());
|
||||
fetchTokensBalances (options) {
|
||||
const { skipNotifications = false, force = false } = options;
|
||||
|
||||
this.throttledTokensFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.throttledTokensFetch.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fetchBalances (options) {
|
||||
const { skipNotifications = false, force = false } = options;
|
||||
const { syncing } = this._store.getState().nodeStatus;
|
||||
|
||||
// If syncing, only retrieve balances once every
|
||||
// few seconds
|
||||
if (syncing) {
|
||||
this.shortThrottledFetch.cancel();
|
||||
this.longThrottledFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.longThrottledFetch.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.longThrottledFetch.cancel();
|
||||
this.shortThrottledFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.shortThrottledFetch.flush();
|
||||
}
|
||||
}
|
||||
|
||||
_fetchBalances (skipNotifications = false) {
|
||||
this._store.dispatch(fetchBalances(null, skipNotifications));
|
||||
}
|
||||
|
||||
_fetchTokens (skipNotifications = false) {
|
||||
this._store.dispatch(fetchTokensBalances(null, null, skipNotifications));
|
||||
}
|
||||
|
||||
getTokenRegistry () {
|
||||
return Contracts.get().tokenReg.getContract();
|
||||
}
|
||||
|
||||
loadTokens () {
|
||||
this
|
||||
_loadTokens (options = {}) {
|
||||
return this
|
||||
.getTokenRegistry()
|
||||
.then((tokenreg) => {
|
||||
this._tokenreg = tokenreg;
|
||||
|
||||
this._store.dispatch(setTokenReg(tokenreg));
|
||||
this._store.dispatch(loadTokens());
|
||||
this._store.dispatch(loadTokens(options));
|
||||
|
||||
return this.attachToTokens(tokenreg);
|
||||
})
|
||||
@@ -133,7 +294,7 @@ export default class Balances {
|
||||
}
|
||||
|
||||
attachToNewToken (tokenreg) {
|
||||
if (this._tokenregSubId) {
|
||||
if (this._tokenregSID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -149,13 +310,13 @@ export default class Balances {
|
||||
|
||||
this.handleTokensLogs(logs);
|
||||
})
|
||||
.then((tokenregSubId) => {
|
||||
this._tokenregSubId = tokenregSubId;
|
||||
.then((tokenregSID) => {
|
||||
this._tokenregSID = tokenregSID;
|
||||
});
|
||||
}
|
||||
|
||||
attachToTokenMetaChange (tokenreg) {
|
||||
if (this._tokenregMetaSubId) {
|
||||
if (this._tokenMetaSID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -172,8 +333,8 @@ export default class Balances {
|
||||
|
||||
this.handleTokensLogs(logs);
|
||||
})
|
||||
.then((tokenregMetaSubId) => {
|
||||
this._tokenregMetaSubId = tokenregMetaSubId;
|
||||
.then((tokenMetaSID) => {
|
||||
this._tokenMetaSID = tokenMetaSID;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,27 @@ import { setAddressImage } from './imagesActions';
|
||||
|
||||
import * as ABIS from '~/contracts/abi';
|
||||
import { notifyTransaction } from '~/util/notifications';
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Balances);
|
||||
|
||||
const ETH = {
|
||||
name: 'Ethereum',
|
||||
tag: 'ETH',
|
||||
image: imagesEthereum
|
||||
};
|
||||
|
||||
function setBalances (_balances) {
|
||||
function setBalances (_balances, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const currentTokens = Object.values(state.balances.tokens || {});
|
||||
const currentTags = [ 'eth' ]
|
||||
.concat(currentTokens.map((token) => token.tag))
|
||||
.filter((tag) => tag)
|
||||
.map((tag) => tag.toLowerCase());
|
||||
|
||||
const accounts = state.personal.accounts;
|
||||
const nextBalances = _balances;
|
||||
const prevBalances = state.balances.balances;
|
||||
@@ -48,38 +57,55 @@ function setBalances (_balances) {
|
||||
|
||||
const balance = Object.assign({}, balances[address]);
|
||||
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
||||
const nextTokens = balance.tokens.slice();
|
||||
|
||||
tokens.forEach((t) => {
|
||||
const { token, value } = t;
|
||||
const { tag } = token;
|
||||
const prevTokens = balance.tokens.slice();
|
||||
const nextTokens = [];
|
||||
|
||||
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
||||
currentTags
|
||||
.forEach((tag) => {
|
||||
const prevToken = prevTokens.find((tok) => tok.token.tag.toLowerCase() === tag);
|
||||
const nextToken = tokens.find((tok) => tok.token.tag.toLowerCase() === tag);
|
||||
|
||||
if (tokenIndex === -1) {
|
||||
nextTokens.push({
|
||||
token,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
const oldValue = nextTokens[tokenIndex].value;
|
||||
// If the given token is not in the current tokens, skip
|
||||
if (!nextToken && !prevToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No updates
|
||||
if (!nextToken) {
|
||||
return nextTokens.push(prevToken);
|
||||
}
|
||||
|
||||
const { token, value } = nextToken;
|
||||
|
||||
// If it's a new token, push it
|
||||
if (!prevToken) {
|
||||
return nextTokens.push({
|
||||
token, value
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise, update the value
|
||||
const prevValue = prevToken.value;
|
||||
|
||||
// If received a token/eth (old value < new value), notify
|
||||
if (oldValue.lt(value) && accounts[address]) {
|
||||
if (prevValue.lt(value) && accounts[address] && !skipNotifications) {
|
||||
const account = accounts[address];
|
||||
const txValue = value.minus(oldValue);
|
||||
const txValue = value.minus(prevValue);
|
||||
|
||||
const redirectToAccount = () => {
|
||||
const route = `/account/${account.address}`;
|
||||
const route = `/accounts/${account.address}`;
|
||||
dispatch(push(route));
|
||||
};
|
||||
|
||||
notifyTransaction(account, token, txValue, redirectToAccount);
|
||||
}
|
||||
|
||||
nextTokens[tokenIndex] = { token, value };
|
||||
}
|
||||
});
|
||||
return nextTokens.push({
|
||||
...prevToken,
|
||||
value
|
||||
});
|
||||
});
|
||||
|
||||
balances[address] = { txCount: txCount || new BigNumber(0), tokens: nextTokens };
|
||||
});
|
||||
@@ -123,7 +149,9 @@ export function setTokenImage (tokenAddress, image) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadTokens () {
|
||||
export function loadTokens (options = {}) {
|
||||
log.debug('loading tokens', Object.keys(options).length ? options : '');
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const { tokenreg } = getState().balances;
|
||||
|
||||
@@ -131,7 +159,7 @@ export function loadTokens () {
|
||||
.call()
|
||||
.then((numTokens) => {
|
||||
const tokenIds = range(numTokens.toNumber());
|
||||
dispatch(fetchTokens(tokenIds));
|
||||
dispatch(fetchTokens(tokenIds, options));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::loadTokens', error);
|
||||
@@ -139,8 +167,9 @@ export function loadTokens () {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchTokens (_tokenIds) {
|
||||
export function fetchTokens (_tokenIds, options = {}) {
|
||||
const tokenIds = uniq(_tokenIds || []);
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const { api, images, balances } = getState();
|
||||
const { tokenreg } = balances;
|
||||
@@ -161,8 +190,9 @@ export function fetchTokens (_tokenIds) {
|
||||
dispatch(setAddressImage(address, image, true));
|
||||
});
|
||||
|
||||
log.debug('fetched token', tokens);
|
||||
dispatch(setTokens(tokens));
|
||||
dispatch(fetchBalances());
|
||||
dispatch(updateTokensFilter(null, null, options));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchTokens', error);
|
||||
@@ -170,7 +200,7 @@ export function fetchTokens (_tokenIds) {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBalances (_addresses) {
|
||||
export function fetchBalances (_addresses, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@@ -192,8 +222,7 @@ export function fetchBalances (_addresses) {
|
||||
balances[addr] = accountsBalances[idx];
|
||||
});
|
||||
|
||||
dispatch(setBalances(balances));
|
||||
updateTokensFilter(addresses)(dispatch, getState);
|
||||
dispatch(setBalances(balances, skipNotifications));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchBalances', error);
|
||||
@@ -201,7 +230,7 @@ export function fetchBalances (_addresses) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTokensFilter (_addresses, _tokens) {
|
||||
export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, balances, personal } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@@ -214,27 +243,32 @@ export function updateTokensFilter (_addresses, _tokens) {
|
||||
const tokenAddresses = tokens.map((t) => t.address).sort();
|
||||
|
||||
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
||||
// Has the tokens addresses changed (eg. a network change)
|
||||
const sameTokens = isEqual(tokenAddresses, tokensFilter.tokenAddresses);
|
||||
const sameAddresses = isEqual(addresses, tokensFilter.addresses);
|
||||
|
||||
if (sameTokens && sameAddresses) {
|
||||
// Addresses that are not in the current filter (omit those
|
||||
// that the filter includes)
|
||||
const newAddresses = addresses.filter((address) => !tokensFilter.addresses.includes(address));
|
||||
|
||||
// If no new addresses and the same tokens, don't change the filter
|
||||
if (sameTokens && newAddresses.length === 0) {
|
||||
log.debug('no need to update token filter', addresses, tokenAddresses, tokensFilter);
|
||||
return queryTokensFilter(tokensFilter)(dispatch, getState);
|
||||
}
|
||||
}
|
||||
|
||||
let promise = Promise.resolve();
|
||||
log.debug('updating the token filter', addresses, tokenAddresses);
|
||||
const promises = [];
|
||||
|
||||
if (tokensFilter.filterFromId) {
|
||||
promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterFromId));
|
||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterFromId));
|
||||
}
|
||||
|
||||
if (tokensFilter.filterToId) {
|
||||
promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterToId));
|
||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterToId));
|
||||
}
|
||||
|
||||
if (tokenAddresses.length === 0 || addresses.length === 0) {
|
||||
return promise;
|
||||
}
|
||||
const promise = Promise.all(promises);
|
||||
|
||||
const TRANSFER_SIGNATURE = api.util.sha3('Transfer(address,address,uint256)');
|
||||
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
||||
@@ -269,8 +303,10 @@ export function updateTokensFilter (_addresses, _tokens) {
|
||||
addresses, tokenAddresses
|
||||
};
|
||||
|
||||
const { skipNotifications } = options;
|
||||
|
||||
dispatch(setTokensFilter(nextTokensFilter));
|
||||
fetchTokensBalances(addresses, tokens)(dispatch, getState);
|
||||
fetchTokensBalances(addresses, tokens, skipNotifications)(dispatch, getState);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::updateTokensFilter', error);
|
||||
@@ -326,7 +362,7 @@ export function queryTokensFilter (tokensFilter) {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||
export function fetchTokensBalances (_addresses = null, _tokens = null, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal, balances } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@@ -348,7 +384,7 @@ export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||
balances[addr] = tokensBalances[idx];
|
||||
});
|
||||
|
||||
dispatch(setBalances(balances));
|
||||
dispatch(setBalances(balances, skipNotifications));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchTokensBalances', error);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BalancesProvider from './balances';
|
||||
import { showSnackbar } from './snackbarActions';
|
||||
import { DEFAULT_NETCHAIN } from './statusReducer';
|
||||
|
||||
@@ -29,6 +30,11 @@ export default class ChainMiddleware {
|
||||
|
||||
if (newChain !== nodeStatus.netChain && nodeStatus.netChain !== DEFAULT_NETCHAIN) {
|
||||
store.dispatch(showSnackbar(`Switched to ${newChain}. Please reload the page.`, 60000));
|
||||
|
||||
// Fetch the new balances without notifying the user of any change
|
||||
BalancesProvider.get(store).fetchAllBalances({
|
||||
changedNetwork: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
import { initialState as defaultNodeStatusState } from './statusReducer';
|
||||
import ChainMiddleware from './chainMiddleware';
|
||||
import { createWsApi } from '~/../test/e2e/ethapi';
|
||||
|
||||
let middleware;
|
||||
let next;
|
||||
let store;
|
||||
|
||||
const api = createWsApi();
|
||||
Contracts.create(api);
|
||||
|
||||
function createMiddleware (collection = {}) {
|
||||
middleware = new ChainMiddleware().toMiddleware();
|
||||
next = sinon.stub();
|
||||
@@ -30,6 +35,7 @@ function createMiddleware (collection = {}) {
|
||||
dispatch: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
api: api,
|
||||
nodeStatus: Object.assign({}, defaultNodeStatusState, collection)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import { isEqual, intersection } from 'lodash';
|
||||
|
||||
import { fetchBalances } from './balancesActions';
|
||||
import BalancesProvider from './balances';
|
||||
import { updateTokensFilter } from './balancesActions';
|
||||
import { attachWallets } from './walletActions';
|
||||
|
||||
import Contract from '~/api/contract';
|
||||
@@ -75,6 +76,9 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
return wallet;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
return [];
|
||||
})
|
||||
.then((_wallets) => {
|
||||
_wallets.forEach((wallet) => {
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
@@ -95,7 +99,14 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
|
||||
dispatch(_personalAccountsInfo(data));
|
||||
dispatch(attachWallets(wallets));
|
||||
dispatch(fetchBalances());
|
||||
|
||||
BalancesProvider.get().fetchAllBalances({
|
||||
force: true
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('personalAccountsInfo', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -123,6 +134,18 @@ export function setVisibleAccounts (addresses) {
|
||||
}
|
||||
|
||||
dispatch(_setVisibleAccounts(addresses));
|
||||
dispatch(fetchBalances(addresses));
|
||||
|
||||
// Don't update the balances if no new addresses displayed
|
||||
if (addresses.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Tokens filter to take into account the new
|
||||
// addresses
|
||||
dispatch(updateTokensFilter());
|
||||
|
||||
BalancesProvider.get().fetchBalances({
|
||||
force: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,33 +14,110 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { statusBlockNumber, statusCollection, statusLogs } from './statusActions';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
import UpgradeStore from '~/modals/UpgradeParity/store';
|
||||
|
||||
import BalancesProvider from './balances';
|
||||
import { statusBlockNumber, statusCollection, statusLogs } from './statusActions';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Signer);
|
||||
let instance = null;
|
||||
|
||||
export default class Status {
|
||||
_apiStatus = {};
|
||||
_status = {};
|
||||
_longStatus = {};
|
||||
_minerSettings = {};
|
||||
_timeoutIds = {};
|
||||
_blockNumberSubscriptionId = null;
|
||||
_timestamp = Date.now();
|
||||
|
||||
constructor (store, api) {
|
||||
this._api = api;
|
||||
this._store = store;
|
||||
this._upgradeStore = UpgradeStore.get(api);
|
||||
|
||||
this._apiStatus = {};
|
||||
this._status = {};
|
||||
this._longStatus = {};
|
||||
this._minerSettings = {};
|
||||
// On connecting, stop all subscriptions
|
||||
api.on('connecting', this.stop, this);
|
||||
|
||||
this._longStatusTimeoutId = null;
|
||||
// On connected, start the subscriptions
|
||||
api.on('connected', this.start, this);
|
||||
|
||||
this._timestamp = Date.now();
|
||||
// On disconnected, stop all subscriptions
|
||||
api.on('disconnected', this.stop, this);
|
||||
|
||||
this.updateApiStatus();
|
||||
}
|
||||
|
||||
static instantiate (store, api) {
|
||||
if (!instance) {
|
||||
instance = new Status(store, api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
start () {
|
||||
this._subscribeBlockNumber();
|
||||
this._pollStatus();
|
||||
this._pollLongStatus();
|
||||
this._pollLogs();
|
||||
log.debug('status::start');
|
||||
|
||||
Promise
|
||||
.all([
|
||||
this._subscribeBlockNumber(),
|
||||
|
||||
this._pollLogs(),
|
||||
this._pollLongStatus(),
|
||||
this._pollStatus()
|
||||
])
|
||||
.then(() => {
|
||||
return BalancesProvider.start();
|
||||
});
|
||||
}
|
||||
|
||||
_subscribeBlockNumber () {
|
||||
this._api
|
||||
stop () {
|
||||
log.debug('status::stop');
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (this._blockNumberSubscriptionId) {
|
||||
const promise = this._api
|
||||
.unsubscribe(this._blockNumberSubscriptionId)
|
||||
.then(() => {
|
||||
this._blockNumberSubscriptionId = null;
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
Object.values(this._timeoutIds).forEach((timeoutId) => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
const promise = BalancesProvider.stop();
|
||||
promises.push(promise);
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('status::stop', error);
|
||||
return true;
|
||||
})
|
||||
.then(() => this.updateApiStatus());
|
||||
}
|
||||
|
||||
updateApiStatus () {
|
||||
const apiStatus = this.getApiStatus();
|
||||
log.debug('status::updateApiStatus', apiStatus);
|
||||
|
||||
if (!isEqual(apiStatus, this._apiStatus)) {
|
||||
this._store.dispatch(statusCollection(apiStatus));
|
||||
this._apiStatus = apiStatus;
|
||||
}
|
||||
}
|
||||
|
||||
_subscribeBlockNumber = () => {
|
||||
return this._api
|
||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
||||
if (error) {
|
||||
return;
|
||||
@@ -51,6 +128,10 @@ export default class Status {
|
||||
this._api.eth
|
||||
.getBlockByNumber(blockNumber)
|
||||
.then((block) => {
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._store.dispatch(statusCollection({
|
||||
blockTimestamp: block.timestamp,
|
||||
gasLimit: block.gasLimit
|
||||
@@ -59,6 +140,9 @@ export default class Status {
|
||||
.catch((error) => {
|
||||
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
|
||||
});
|
||||
})
|
||||
.then((blockNumberSubscriptionId) => {
|
||||
this._blockNumberSubscriptionId = blockNumberSubscriptionId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,11 +156,7 @@ export default class Status {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
_pollStatus = () => {
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
setTimeout(() => this._pollStatus(), timeout);
|
||||
};
|
||||
|
||||
getApiStatus = () => {
|
||||
const { isConnected, isConnecting, needsToken, secureToken } = this._api;
|
||||
|
||||
const apiStatus = {
|
||||
@@ -86,19 +166,23 @@ export default class Status {
|
||||
secureToken
|
||||
};
|
||||
|
||||
const gotConnected = !this._apiStatus.isConnected && apiStatus.isConnected;
|
||||
return apiStatus;
|
||||
}
|
||||
|
||||
if (gotConnected) {
|
||||
this._pollLongStatus();
|
||||
}
|
||||
_pollStatus = () => {
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
if (this._timeoutIds.status) {
|
||||
clearTimeout(this._timeoutIds.status);
|
||||
}
|
||||
|
||||
if (!isEqual(apiStatus, this._apiStatus)) {
|
||||
this._store.dispatch(statusCollection(apiStatus));
|
||||
this._apiStatus = apiStatus;
|
||||
}
|
||||
this._timeoutIds.status = setTimeout(() => this._pollStatus(), timeout);
|
||||
};
|
||||
|
||||
if (!isConnected) {
|
||||
return nextTimeout(250);
|
||||
this.updateApiStatus();
|
||||
|
||||
if (!this._api.isConnected) {
|
||||
nextTimeout(250);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const { refreshStatus } = this._store.getState().nodeStatus;
|
||||
@@ -110,7 +194,7 @@ export default class Status {
|
||||
statusPromises.push(this._api.eth.hashrate());
|
||||
}
|
||||
|
||||
Promise
|
||||
return Promise
|
||||
.all(statusPromises)
|
||||
.then(([ syncing, ...statusResults ]) => {
|
||||
const status = statusResults.length === 0
|
||||
@@ -125,11 +209,11 @@ export default class Status {
|
||||
this._store.dispatch(statusCollection(status));
|
||||
this._status = status;
|
||||
}
|
||||
|
||||
nextTimeout();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollStatus', error);
|
||||
})
|
||||
.then(() => {
|
||||
nextTimeout();
|
||||
});
|
||||
}
|
||||
@@ -140,7 +224,7 @@ export default class Status {
|
||||
* from the UI
|
||||
*/
|
||||
_pollMinerSettings = () => {
|
||||
Promise
|
||||
return Promise
|
||||
.all([
|
||||
this._api.eth.coinbase(),
|
||||
this._api.parity.extraData(),
|
||||
@@ -175,21 +259,21 @@ export default class Status {
|
||||
*/
|
||||
_pollLongStatus = () => {
|
||||
if (!this._api.isConnected) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const nextTimeout = (timeout = 30000) => {
|
||||
if (this._longStatusTimeoutId) {
|
||||
clearTimeout(this._longStatusTimeoutId);
|
||||
if (this._timeoutIds.longStatus) {
|
||||
clearTimeout(this._timeoutIds.longStatus);
|
||||
}
|
||||
|
||||
this._longStatusTimeoutId = setTimeout(this._pollLongStatus, timeout);
|
||||
this._timeoutIds.longStatus = setTimeout(() => this._pollLongStatus(), timeout);
|
||||
};
|
||||
|
||||
// Poll Miner settings just in case
|
||||
this._pollMinerSettings();
|
||||
const minerPromise = this._pollMinerSettings();
|
||||
|
||||
Promise
|
||||
const mainPromise = Promise
|
||||
.all([
|
||||
this._api.parity.netPeers(),
|
||||
this._api.web3.clientVersion(),
|
||||
@@ -198,10 +282,11 @@ export default class Status {
|
||||
this._api.parity.netChain(),
|
||||
this._api.parity.netPort(),
|
||||
this._api.parity.rpcSettings(),
|
||||
this._api.parity.enode()
|
||||
this._api.parity.enode(),
|
||||
this._upgradeStore.checkUpgrade()
|
||||
])
|
||||
.then(([
|
||||
netPeers, clientVersion, netVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
|
||||
netPeers, clientVersion, netVersion, defaultExtraData, netChain, netPort, rpcSettings, enode, upgradeStatus
|
||||
]) => {
|
||||
const isTest =
|
||||
netVersion === '2' || // morden
|
||||
@@ -225,21 +310,31 @@ export default class Status {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollLongStatus', error);
|
||||
})
|
||||
.then(() => {
|
||||
nextTimeout(60000);
|
||||
});
|
||||
|
||||
nextTimeout(60000);
|
||||
return Promise.all([ minerPromise, mainPromise ]);
|
||||
}
|
||||
|
||||
_pollLogs = () => {
|
||||
const nextTimeout = (timeout = 1000) => setTimeout(this._pollLogs, timeout);
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
if (this._timeoutIds.logs) {
|
||||
clearTimeout(this._timeoutIds.logs);
|
||||
}
|
||||
|
||||
this._timeoutIds.logs = setTimeout(this._pollLogs, timeout);
|
||||
};
|
||||
|
||||
const { devLogsEnabled } = this._store.getState().nodeStatus;
|
||||
|
||||
if (!devLogsEnabled) {
|
||||
nextTimeout();
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
Promise
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.devLogs(),
|
||||
this._api.parity.devLogsLevels()
|
||||
@@ -249,11 +344,12 @@ export default class Status {
|
||||
devLogs: devLogs.slice(-1024),
|
||||
devLogsLevels
|
||||
}));
|
||||
nextTimeout();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollLogs', error);
|
||||
nextTimeout();
|
||||
})
|
||||
.then(() => {
|
||||
return nextTimeout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ export default function (api, browserHistory) {
|
||||
const middleware = initMiddleware(api, browserHistory);
|
||||
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
||||
|
||||
new BalancesProvider(store, api).start();
|
||||
BalancesProvider.instantiate(store, api);
|
||||
StatusProvider.instantiate(store, api);
|
||||
new PersonalProvider(store, api).start();
|
||||
new SignerProvider(store, api).start();
|
||||
new StatusProvider(store, api).start();
|
||||
|
||||
store.dispatch(loadWallet(api));
|
||||
setupWorker(store);
|
||||
|
||||
@@ -17,133 +17,34 @@
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import Api from './api';
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Signer);
|
||||
const sysuiToken = window.localStorage.getItem('sysuiToken');
|
||||
|
||||
export default class SecureApi extends Api {
|
||||
_isConnecting = false;
|
||||
_needsToken = false;
|
||||
_tokens = [];
|
||||
|
||||
_dappsInterface = null;
|
||||
_dappsPort = 8080;
|
||||
_signerPort = 8180;
|
||||
|
||||
constructor (url, nextToken) {
|
||||
super(new Api.Transport.Ws(url, sysuiToken, false));
|
||||
const transport = new Api.Transport.Ws(url, sysuiToken, false);
|
||||
super(transport);
|
||||
|
||||
this._url = url;
|
||||
this._isConnecting = true;
|
||||
this._needsToken = false;
|
||||
|
||||
this._dappsPort = 8080;
|
||||
this._dappsInterface = null;
|
||||
this._signerPort = 8180;
|
||||
|
||||
// Try tokens from localstorage, then from hash
|
||||
// Try tokens from localstorage, from hash and 'initial'
|
||||
this._tokens = uniq([sysuiToken, nextToken, 'initial'])
|
||||
.filter((token) => token)
|
||||
.map((token) => ({ value: token, tried: false }));
|
||||
|
||||
this._tryNextToken();
|
||||
}
|
||||
|
||||
saveToken = () => {
|
||||
window.localStorage.setItem('sysuiToken', this._transport.token);
|
||||
// DEBUG: console.log('SecureApi:saveToken', this._transport.token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that gets resolved with
|
||||
* a boolean: `true` if the node is up, `false`
|
||||
* otherwise
|
||||
*/
|
||||
_checkNodeUp () {
|
||||
const url = this._url.replace(/wss?/, 'http');
|
||||
return fetch(url, { method: 'HEAD' })
|
||||
.then(
|
||||
(r) => r.status === 200,
|
||||
() => false
|
||||
)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
_setManual () {
|
||||
this._needsToken = true;
|
||||
this._isConnecting = false;
|
||||
}
|
||||
|
||||
_tryNextToken () {
|
||||
const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
|
||||
|
||||
if (nextTokenIndex < 0) {
|
||||
return this._setManual();
|
||||
}
|
||||
|
||||
const nextToken = this._tokens[nextTokenIndex];
|
||||
nextToken.tried = true;
|
||||
|
||||
this.updateToken(nextToken.value);
|
||||
}
|
||||
|
||||
_followConnection = () => {
|
||||
const token = this.transport.token;
|
||||
|
||||
return this
|
||||
.transport
|
||||
.connect()
|
||||
.then(() => {
|
||||
if (token === 'initial') {
|
||||
return this.signer
|
||||
.generateAuthorizationToken()
|
||||
.then((token) => {
|
||||
return this.updateToken(token);
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
this.connectSuccess();
|
||||
return true;
|
||||
})
|
||||
.catch((e) => {
|
||||
this
|
||||
._checkNodeUp()
|
||||
.then((isNodeUp) => {
|
||||
// Try again in a few...
|
||||
if (!isNodeUp) {
|
||||
this._isConnecting = false;
|
||||
const timeout = this.transport.retryTimeout;
|
||||
|
||||
window.setTimeout(() => {
|
||||
this._followConnection();
|
||||
}, timeout);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._tryNextToken();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
connectSuccess () {
|
||||
this._isConnecting = false;
|
||||
this._needsToken = false;
|
||||
|
||||
this.saveToken();
|
||||
|
||||
Promise
|
||||
.all([
|
||||
this.parity.dappsPort(),
|
||||
this.parity.dappsInterface(),
|
||||
this.parity.signerPort()
|
||||
])
|
||||
.then(([dappsPort, dappsInterface, signerPort]) => {
|
||||
this._dappsPort = dappsPort.toNumber();
|
||||
this._dappsInterface = dappsInterface;
|
||||
this._signerPort = signerPort.toNumber();
|
||||
});
|
||||
|
||||
// DEBUG: console.log('SecureApi:connectSuccess', this._transport.token);
|
||||
}
|
||||
|
||||
updateToken (token) {
|
||||
this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, ''), false);
|
||||
return this._followConnection();
|
||||
// DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState);
|
||||
// When the transport is closed, try to reconnect
|
||||
transport.on('close', this.connect, this);
|
||||
this.connect();
|
||||
}
|
||||
|
||||
get dappsPort () {
|
||||
@@ -151,17 +52,19 @@ export default class SecureApi extends Api {
|
||||
}
|
||||
|
||||
get dappsUrl () {
|
||||
let hostname;
|
||||
return `http://${this.hostname}:${this.dappsPort}`;
|
||||
}
|
||||
|
||||
get hostname () {
|
||||
if (window.location.hostname === 'home.parity') {
|
||||
hostname = 'dapps.parity';
|
||||
} else if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
|
||||
hostname = window.location.hostname;
|
||||
} else {
|
||||
hostname = this._dappsInterface;
|
||||
return 'dapps.parity';
|
||||
}
|
||||
|
||||
return `http://${hostname}:${this._dappsPort}`;
|
||||
if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
return this._dappsInterface;
|
||||
}
|
||||
|
||||
get signerPort () {
|
||||
@@ -183,4 +86,279 @@ export default class SecureApi extends Api {
|
||||
get secureToken () {
|
||||
return this._transport.token;
|
||||
}
|
||||
|
||||
connect () {
|
||||
if (this._isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('trying to connect...');
|
||||
|
||||
this._isConnecting = true;
|
||||
|
||||
this.emit('connecting');
|
||||
|
||||
// Reset the tested Tokens
|
||||
this._resetTokens();
|
||||
|
||||
// Try to connect
|
||||
return this._connect()
|
||||
.then((connected) => {
|
||||
this._isConnecting = false;
|
||||
|
||||
if (connected) {
|
||||
const token = this.secureToken;
|
||||
log.debug('got connected ; saving token', token);
|
||||
|
||||
// Save the sucessful token
|
||||
this._saveToken(token);
|
||||
this._needsToken = false;
|
||||
|
||||
// Emit the connected event
|
||||
return this.emit('connected');
|
||||
}
|
||||
|
||||
// If not connected, we need a new token
|
||||
log.debug('needs a token');
|
||||
this._needsToken = true;
|
||||
|
||||
return this.emit('disconnected');
|
||||
})
|
||||
.catch((error) => {
|
||||
this._isConnecting = false;
|
||||
|
||||
log.debug('emitting "disconnected"');
|
||||
this.emit('disconnected');
|
||||
console.error('unhandled error in secureApi', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that gets resolved with
|
||||
* a boolean: `true` if the node is up, `false`
|
||||
* otherwise (HEAD request to the Node)
|
||||
*/
|
||||
isNodeUp () {
|
||||
const url = this._url.replace(/wss?/, 'http');
|
||||
return fetch(url, { method: 'HEAD' })
|
||||
.then(
|
||||
(r) => r.status === 200,
|
||||
() => false
|
||||
)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given token, ie. add it to the token
|
||||
* list, and then try to connect (if not already connecting)
|
||||
*/
|
||||
updateToken (_token) {
|
||||
const token = this._sanitiseToken(_token);
|
||||
log.debug('updating token', token);
|
||||
|
||||
// Update the tokens list: put the new one on first position
|
||||
this._tokens = [ { value: token, tried: false } ].concat(this._tokens);
|
||||
|
||||
// Try to connect with the new token added
|
||||
return this.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to connect to the Node with the next Token in
|
||||
* the list
|
||||
*/
|
||||
_connect () {
|
||||
log.debug('trying next token');
|
||||
|
||||
// Get the first not-tried token
|
||||
const nextToken = this._getNextToken();
|
||||
|
||||
// If no more tokens to try, user has to enter a new one
|
||||
if (!nextToken) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
nextToken.tried = true;
|
||||
|
||||
return this._connectWithToken(nextToken.value)
|
||||
.then((validToken) => {
|
||||
// If not valid, try again with the next token in the list
|
||||
if (!validToken) {
|
||||
return this._connect();
|
||||
}
|
||||
|
||||
// If correct and valid token, wait until the Node is ready
|
||||
// and resolve as connected
|
||||
return this._waitUntilNodeReady()
|
||||
.then(() => this._fetchSettings())
|
||||
.then(() => true);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error('unkown error in _connect', error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with the given token.
|
||||
* It returns a Promise that gets resolved
|
||||
* with `validToken` as argument, whether the given token
|
||||
* is valid or not
|
||||
*/
|
||||
_connectWithToken (_token) {
|
||||
// Sanitize the token first
|
||||
const token = this._sanitiseToken(_token);
|
||||
|
||||
// Update the token in the transport layer
|
||||
this.transport.updateToken(token, false);
|
||||
log.debug('connecting with token', token);
|
||||
|
||||
return this.transport.connect()
|
||||
.then(() => {
|
||||
log.debug('connected with', token);
|
||||
|
||||
if (token === 'initial') {
|
||||
return this._generateAuthorizationToken();
|
||||
}
|
||||
|
||||
// The token is valid !
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
// Log if it's not a close error (ie. wrong token)
|
||||
if (error && error.type !== 'close') {
|
||||
log.debug('did not connect ; error', error);
|
||||
}
|
||||
|
||||
// Check if the Node is up
|
||||
return this.isNodeUp()
|
||||
.then((isNodeUp) => {
|
||||
// If it's not up, try again in a few...
|
||||
if (!isNodeUp) {
|
||||
const timeout = this.transport.retryTimeout;
|
||||
|
||||
log.debug('node is not up ; will try again in', timeout, 'ms');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.setTimeout(() => {
|
||||
this._connectWithToken(token).then(resolve).catch(reject);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
// The token is invalid
|
||||
log.debug('tried with a wrong token', token);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct ports from the Node
|
||||
*/
|
||||
_fetchSettings () {
|
||||
return Promise
|
||||
.all([
|
||||
this.parity.dappsPort(),
|
||||
this.parity.dappsInterface(),
|
||||
this.parity.signerPort()
|
||||
])
|
||||
.then(([dappsPort, dappsInterface, signerPort]) => {
|
||||
this._dappsPort = dappsPort.toNumber();
|
||||
this._dappsInterface = dappsInterface;
|
||||
this._signerPort = signerPort.toNumber();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to generate an Authorization Token.
|
||||
* Then try to connect with the new token.
|
||||
*/
|
||||
_generateAuthorizationToken () {
|
||||
return this.signer
|
||||
.generateAuthorizationToken()
|
||||
.then((token) => this._connectWithToken(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next token to try, if any left
|
||||
*/
|
||||
_getNextToken () {
|
||||
// Get the first not-tried token
|
||||
const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
|
||||
|
||||
// If no more tokens to try, user has to enter a new one
|
||||
if (nextTokenIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextToken = this._tokens[nextTokenIndex];
|
||||
return nextToken;
|
||||
}
|
||||
|
||||
_resetTokens () {
|
||||
this._tokens = this._tokens.map((token) => ({
|
||||
...token,
|
||||
tried: false
|
||||
}));
|
||||
}
|
||||
|
||||
_sanitiseToken (token) {
|
||||
return token.replace(/[^a-zA-Z0-9]/g, '');
|
||||
}
|
||||
|
||||
_saveToken (token) {
|
||||
window.localStorage.setItem('sysuiToken', token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise gets resolved when the node is up
|
||||
* and running (it might take some time before
|
||||
* the node is actually ready even when the client
|
||||
* is connected).
|
||||
*
|
||||
* We check that the `parity_enode` RPC calls
|
||||
* returns successfully
|
||||
*/
|
||||
_waitUntilNodeReady (_timeleft) {
|
||||
// Default timeout to 30 seconds
|
||||
const timeleft = Number.isFinite(_timeleft)
|
||||
? _timeleft
|
||||
: 30 * 1000;
|
||||
|
||||
// After timeout, just resolve the promise...
|
||||
if (timeleft <= 0) {
|
||||
console.warn('node is still not ready after 30 seconds...');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
return this
|
||||
.parity.enode()
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
if (!error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (error.type !== 'NETWORK_DISABLED') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Timeout between 250ms and 750ms
|
||||
const timeout = Math.floor(250 + (500 * Math.random()));
|
||||
|
||||
log.debug('waiting until node is ready', 'retry in', timeout, 'ms');
|
||||
|
||||
// Retry in a few...
|
||||
return new Promise((resolve, reject) => {
|
||||
window.setTimeout(() => {
|
||||
const duration = Date.now() - start;
|
||||
|
||||
this._waitUntilNodeReady(timeleft - duration).then(resolve).catch(reject);
|
||||
}, timeout);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
transition: transform ease-out 0.1s;
|
||||
transform: scale(1);
|
||||
overflow: hidden;
|
||||
|
||||
&.copied {
|
||||
animation-duration: 0.25s;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import unknownImage from '../../../assets/images/contracts/unknown-64x64.png';
|
||||
import styles from './balance.css';
|
||||
@@ -63,11 +62,15 @@ class Balance extends Component {
|
||||
value = api.util.fromWei(balance.value).toFormat(3);
|
||||
}
|
||||
|
||||
let imagesrc = token.image;
|
||||
if (!imagesrc) {
|
||||
imagesrc = images[token.address]
|
||||
? `${api.dappsUrl}${images[token.address]}`
|
||||
: unknownImage;
|
||||
const imageurl = token.image || images[token.address];
|
||||
let imagesrc = unknownImage;
|
||||
|
||||
if (imageurl) {
|
||||
const host = /^(\/)?api/.test(imageurl)
|
||||
? api.dappsUrl
|
||||
: '';
|
||||
|
||||
imagesrc = `${host}${imageurl}`;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -107,11 +110,7 @@ function mapStateToProps (state) {
|
||||
return { images };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Balance);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import styles from './blockStatus.css';
|
||||
|
||||
@@ -113,11 +112,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(BlockStatus);
|
||||
|
||||
@@ -15,6 +15,22 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.outerInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
appearance: textfield;
|
||||
@@ -58,13 +74,13 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 1rem 2.5rem 0.25em;
|
||||
margin: 1rem 0.5rem 0.25em;
|
||||
color: rgba(255, 255, 255, 0.498039);
|
||||
}
|
||||
|
||||
.underline {
|
||||
position: relative;
|
||||
margin: 0 9rem 0 2.5rem;
|
||||
margin: 0 0.5rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@@ -78,7 +94,7 @@
|
||||
|
||||
.input {
|
||||
font-size: 1.5em;
|
||||
padding: 0 9rem 0.5em 2.5rem;
|
||||
padding: 0 9rem 0.5em 0.5rem;
|
||||
display: block;
|
||||
|
||||
padding-right: 6rem;
|
||||
@@ -92,7 +108,7 @@
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
margin: 2rem 2rem 0;
|
||||
margin: 2rem 0 0;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
@@ -107,8 +123,11 @@
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5em;
|
||||
font-color: white;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
|
||||
@@ -25,6 +25,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
|
||||
|
||||
import AccountCard from '~/ui/AccountCard';
|
||||
import InputAddress from '~/ui/Form/InputAddress';
|
||||
import Loading from '~/ui/Loading';
|
||||
import Portal from '~/ui/Portal';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
@@ -130,7 +131,7 @@ class AddressSelect extends Component {
|
||||
const input = (
|
||||
<InputAddress
|
||||
accountsInfo={ accountsInfo }
|
||||
allowCopy={ allowCopy }
|
||||
allowCopy={ (disabled || readOnly) && allowCopy ? allowCopy : false }
|
||||
className={ className }
|
||||
disabled={ disabled || readOnly }
|
||||
error={ error }
|
||||
@@ -182,17 +183,18 @@ class AddressSelect extends Component {
|
||||
<label className={ styles.label } htmlFor={ id }>
|
||||
{ label }
|
||||
</label>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
<div className={ styles.outerInput }>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
{ this.renderLoader() }
|
||||
</div>
|
||||
|
||||
<div className={ styles.underline }>
|
||||
<TextFieldUnderline
|
||||
@@ -210,6 +212,19 @@ class AddressSelect extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderLoader () {
|
||||
if (!this.store.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Loading
|
||||
className={ styles.loader }
|
||||
size={ 0.5 }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderCurrentInput () {
|
||||
const { inputValue } = this.state;
|
||||
|
||||
@@ -304,7 +319,9 @@ class AddressSelect extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.category } key={ `${key}_${index}` }>
|
||||
<div className={ styles.title }>{ label }</div>
|
||||
<div className={ styles.title }>
|
||||
<h3>{ label }</h3>
|
||||
</div>
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import { observable, action } from 'mobx';
|
||||
import { observable, action, transaction } from 'mobx';
|
||||
import { flatMap, uniqBy } from 'lodash';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -26,6 +26,7 @@ const ZERO = /^(0x)?0*$/;
|
||||
|
||||
export default class AddressSelectStore {
|
||||
|
||||
@observable loading = false;
|
||||
@observable values = [];
|
||||
@observable registryValues = [];
|
||||
|
||||
@@ -85,7 +86,7 @@ export default class AddressSelectStore {
|
||||
return emailVerification
|
||||
.instance
|
||||
.reverse
|
||||
.call({}, [ sha3(email) ])
|
||||
.call({}, [ sha3.text(email) ])
|
||||
.then((address) => {
|
||||
return {
|
||||
address,
|
||||
@@ -109,7 +110,7 @@ export default class AddressSelectStore {
|
||||
this.regLookups.push((name) => {
|
||||
return registryInstance
|
||||
.getAddress
|
||||
.call({}, [ sha3(name), 'A' ])
|
||||
.call({}, [ sha3.text(name), 'A' ])
|
||||
.then((address) => {
|
||||
return {
|
||||
address,
|
||||
@@ -224,21 +225,28 @@ export default class AddressSelectStore {
|
||||
};
|
||||
});
|
||||
|
||||
// Registries Lookup
|
||||
this.registryValues = [];
|
||||
// Clear the previous results after 50ms
|
||||
// if still fetching
|
||||
const timeoutId = setTimeout(() => {
|
||||
transaction(() => {
|
||||
this.registryValues = [];
|
||||
this.loading = true;
|
||||
});
|
||||
}, 50);
|
||||
|
||||
const lookups = this.regLookups.map((regLookup) => regLookup(value));
|
||||
|
||||
Promise
|
||||
// Registries Lookup
|
||||
return Promise
|
||||
.all(lookups)
|
||||
.then((results) => {
|
||||
return results
|
||||
.filter((result) => result && !ZERO.test(result.address));
|
||||
})
|
||||
.then((results) => {
|
||||
results = uniqBy(results, (result) => result.address);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
this.registryValues = results
|
||||
const registryValues = uniqBy(results, (result) => result.address)
|
||||
.map((result) => {
|
||||
const lowercaseAddress = result.address.toLowerCase();
|
||||
|
||||
@@ -253,6 +261,11 @@ export default class AddressSelectStore {
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
transaction(() => {
|
||||
this.loading = false;
|
||||
this.registryValues = registryValues;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import util from '~/api/util';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
@@ -58,7 +57,7 @@ class InputAddress extends Component {
|
||||
|
||||
render () {
|
||||
const { accountsInfo, allowCopy, className, disabled, error, focused, hint } = this.props;
|
||||
const { hideUnderline, label, onClick, onFocus, onSubmit, readOnly, small } = this.props;
|
||||
const { hideUnderline, label, onClick, onFocus, readOnly, small } = this.props;
|
||||
const { tabIndex, text, tokens, value } = this.props;
|
||||
|
||||
const account = value && (accountsInfo[value] || tokens[value]);
|
||||
@@ -77,7 +76,7 @@ class InputAddress extends Component {
|
||||
|
||||
const props = {};
|
||||
|
||||
if (!readOnly && !disabled) {
|
||||
if (!disabled) {
|
||||
props.focused = focused;
|
||||
}
|
||||
|
||||
@@ -91,10 +90,10 @@ class InputAddress extends Component {
|
||||
hideUnderline={ hideUnderline }
|
||||
hint={ hint }
|
||||
label={ label }
|
||||
onChange={ this.handleInputChange }
|
||||
onChange={ this.onChange }
|
||||
onClick={ onClick }
|
||||
onFocus={ onFocus }
|
||||
onSubmit={ onSubmit }
|
||||
onSubmit={ this.onSubmit }
|
||||
readOnly={ readOnly }
|
||||
tabIndex={ tabIndex }
|
||||
value={
|
||||
@@ -133,22 +132,34 @@ class InputAddress extends Component {
|
||||
return (
|
||||
<div className={ classes.join(' ') }>
|
||||
<IdentityIcon
|
||||
inline center
|
||||
address={ value } />
|
||||
address={ value }
|
||||
center
|
||||
inline />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleInputChange = (event, value) => {
|
||||
const isEmpty = (value.length === 0);
|
||||
onChange = (event, _value) => {
|
||||
let address = _value.trim();
|
||||
const isEmpty = (address.length === 0);
|
||||
|
||||
this.setState({ isEmpty });
|
||||
|
||||
if (!/^0x/.test(value) && util.isAddressValid(`0x${value}`)) {
|
||||
return this.props.onChange(event, `0x${value}`);
|
||||
}
|
||||
if (this.props.onChange) {
|
||||
if (!/^0x/.test(address) && util.isAddressValid(`0x${address}`)) {
|
||||
address = `0x${address}`;
|
||||
}
|
||||
|
||||
this.props.onChange(event, value);
|
||||
this.props.onChange(event, address);
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit = (_value) => {
|
||||
const address = _value.trim();
|
||||
|
||||
if (this.props.onSubmit) {
|
||||
this.props.onSubmit(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,11 +173,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(InputAddress);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import AddressSelect from '../AddressSelect';
|
||||
|
||||
@@ -68,11 +67,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(InputAddressSelect);
|
||||
|
||||
17
js/src/ui/Form/PasswordStrength/index.js
Normal file
17
js/src/ui/Form/PasswordStrength/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './passwordStrength';
|
||||
31
js/src/ui/Form/PasswordStrength/passwordStrength.css
Normal file
31
js/src/ui/Form/PasswordStrength/passwordStrength.css
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.strength {
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.label {
|
||||
user-select: none;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.498039);
|
||||
}
|
||||
125
js/src/ui/Form/PasswordStrength/passwordStrength.js
Normal file
125
js/src/ui/Form/PasswordStrength/passwordStrength.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { LinearProgress } from 'material-ui';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import zxcvbn from 'zxcvbn';
|
||||
|
||||
import styles from './passwordStrength.css';
|
||||
|
||||
const BAR_STYLE = {
|
||||
borderRadius: 1,
|
||||
height: 7,
|
||||
marginTop: '0.5em'
|
||||
};
|
||||
|
||||
export default class PasswordStrength extends Component {
|
||||
static propTypes = {
|
||||
input: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
strength: null
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.updateStrength = debounce(this._updateStrength, 50, { leading: true });
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.updateStrength(this.props.input);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.input !== this.props.input) {
|
||||
this.updateStrength(nextProps.input);
|
||||
}
|
||||
}
|
||||
|
||||
_updateStrength (input = '') {
|
||||
const strength = zxcvbn(input);
|
||||
this.setState({ strength });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { strength } = this.state;
|
||||
|
||||
if (!strength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { score, feedback } = strength;
|
||||
|
||||
// Score is between 0 and 4
|
||||
const value = score * 100 / 5 + 20;
|
||||
const color = this.getStrengthBarColor(score);
|
||||
|
||||
return (
|
||||
<div className={ styles.strength }>
|
||||
<label className={ styles.label }>
|
||||
<FormattedMessage
|
||||
id='ui.passwordStrength.label'
|
||||
defaultMessage='password strength'
|
||||
/>
|
||||
</label>
|
||||
<LinearProgress
|
||||
color={ color }
|
||||
mode='determinate'
|
||||
style={ BAR_STYLE }
|
||||
value={ value }
|
||||
/>
|
||||
<div className={ styles.feedback }>
|
||||
{ this.renderFeedback(feedback) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Note that the suggestions are in english, thus it wouldn't
|
||||
// make sense to add translations to surrounding words
|
||||
renderFeedback (feedback = {}) {
|
||||
const { suggestions = [] } = feedback;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
{ suggestions.join(' ') }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getStrengthBarColor (score) {
|
||||
switch (score) {
|
||||
case 4:
|
||||
case 3:
|
||||
return 'lightgreen';
|
||||
|
||||
case 2:
|
||||
return 'yellow';
|
||||
|
||||
case 1:
|
||||
return 'orange';
|
||||
|
||||
default:
|
||||
return 'red';
|
||||
}
|
||||
}
|
||||
}
|
||||
62
js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
Normal file
62
js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import PasswordStrength from './passwordStrength';
|
||||
|
||||
const INPUT_A = 'l33t_test';
|
||||
const INPUT_B = 'Fu£dk;s$%kdlaOe9)_';
|
||||
const INPUT_NULL = '';
|
||||
|
||||
function render (props) {
|
||||
return shallow(
|
||||
<PasswordStrength { ...props } />
|
||||
).shallow();
|
||||
}
|
||||
|
||||
describe('ui/Form/PasswordStrength', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders', () => {
|
||||
expect(render({ input: INPUT_A })).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders a linear progress', () => {
|
||||
expect(render({ input: INPUT_A }).find('LinearProgress')).to.be.ok;
|
||||
});
|
||||
|
||||
describe('compute strength', () => {
|
||||
it('has low score with empty input', () => {
|
||||
expect(
|
||||
render({ input: INPUT_NULL }).find('LinearProgress').props().value
|
||||
).to.equal(20);
|
||||
});
|
||||
|
||||
it('has medium score', () => {
|
||||
expect(
|
||||
render({ input: INPUT_A }).find('LinearProgress').props().value
|
||||
).to.equal(60);
|
||||
});
|
||||
|
||||
it('has high score', () => {
|
||||
expect(
|
||||
render({ input: INPUT_B }).find('LinearProgress').props().value
|
||||
).to.equal(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -71,7 +71,9 @@ export default class TypedInput extends Component {
|
||||
const { isEth, value } = this.props;
|
||||
|
||||
if (typeof isEth === 'boolean' && value) {
|
||||
const ethValue = isEth ? fromWei(value) : value;
|
||||
// Remove formatting commas
|
||||
const sanitizedValue = typeof value === 'string' ? value.replace(/,/g, '') : value;
|
||||
const ethValue = isEth ? fromWei(sanitizedValue) : value;
|
||||
this.setState({ isEth, ethValue });
|
||||
}
|
||||
}
|
||||
@@ -393,7 +395,9 @@ export default class TypedInput extends Component {
|
||||
return this.setState({ isEth: !isEth });
|
||||
}
|
||||
|
||||
const value = isEth ? toWei(ethValue) : fromWei(ethValue);
|
||||
// Remove formatting commas
|
||||
const sanitizedValue = typeof ethValue === 'string' ? ethValue.replace(/,/g, '') : ethValue;
|
||||
const value = isEth ? toWei(sanitizedValue) : fromWei(sanitizedValue);
|
||||
this.setState({ isEth: !isEth, ethValue: value }, () => {
|
||||
this.onEthValueChange(null, value);
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user