Merge branch 'master' into on-demand-les-request

This commit is contained in:
Robert Habermeier 2017-01-11 11:39:43 +01:00
commit 5b8a7259c1
205 changed files with 5363 additions and 2026 deletions

View File

@ -38,7 +38,8 @@ 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://icarus.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
- rust-stable
@ -106,7 +107,8 @@ 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://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
- rust-centos
@ -144,7 +146,8 @@ 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://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
- rust-i686
@ -189,7 +192,8 @@ 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://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
- rust-arm
@ -234,7 +238,8 @@ 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://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:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
@ -272,7 +277,8 @@ 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://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
- rust-arm
@ -316,7 +322,8 @@ 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://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
- rust-arm
@ -352,7 +359,8 @@ 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://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:
- osx
artifacts:
@ -413,7 +421,8 @@ 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://icarus.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
artifacts:
@ -526,6 +535,7 @@ push-release:
- triggers
image: ethcore/rust:stable
script:
- curl --data "secret=$RELEASES_SECRET" http://icarus.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

236
Cargo.lock generated
View File

@ -1,6 +1,6 @@
[root]
name = "parity"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -9,22 +9,22 @@ dependencies = [
"daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.5.0",
"ethcore-dapps 1.5.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore 1.6.0",
"ethcore-dapps 1.6.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-hypervisor 1.2.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-ipc-tests 0.1.0",
"ethcore-light 1.5.0",
"ethcore-logger 1.5.0",
"ethcore-rpc 1.5.0",
"ethcore-signer 1.5.0",
"ethcore-stratum 1.5.0",
"ethcore-util 1.5.0",
"ethsync 1.5.0",
"ethcore-light 1.6.0",
"ethcore-logger 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-signer 1.6.0",
"ethcore-stratum 1.6.0",
"ethcore-util 1.6.0",
"ethsync 1.6.0",
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -32,10 +32,10 @@ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.5.0",
"parity-hash-fetch 1.6.0",
"parity-reactor 0.1.0",
"parity-rpc-client 1.4.0",
"parity-updater 1.5.0",
"parity-updater 1.6.0",
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -336,7 +336,7 @@ dependencies = [
[[package]]
name = "ethash"
version = "1.5.0"
version = "1.6.0"
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)",
@ -346,7 +346,7 @@ dependencies = [
[[package]]
name = "ethcore"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -355,18 +355,18 @@ dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethash 1.5.0",
"ethash 1.6.0",
"ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.0",
"ethjson 0.1.0",
"ethkey 0.2.0",
"ethstore 0.1.0",
"evmjit 1.5.0",
"evmjit 1.6.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -403,13 +403,13 @@ dependencies = [
[[package]]
name = "ethcore-dapps"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.5.0",
"ethcore-rpc 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-util 1.6.0",
"fetch 0.1.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
@ -420,9 +420,9 @@ dependencies = [
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.5.0",
"parity-hash-fetch 1.6.0",
"parity-reactor 0.1.0",
"parity-ui 1.5.0",
"parity-ui 1.6.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -436,14 +436,14 @@ dependencies = [
[[package]]
name = "ethcore-devtools"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-io"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -454,17 +454,17 @@ dependencies = [
[[package]]
name = "ethcore-ipc"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore-devtools 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-util 1.6.0",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-ipc-codegen"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -477,9 +477,9 @@ dependencies = [
name = "ethcore-ipc-hypervisor"
version = "1.2.0"
dependencies = [
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -488,9 +488,9 @@ dependencies = [
[[package]]
name = "ethcore-ipc-nano"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore-ipc 1.5.0",
"ethcore-ipc 1.6.0",
"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)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
@ -500,11 +500,11 @@ dependencies = [
name = "ethcore-ipc-tests"
version = "0.1.0"
dependencies = [
"ethcore-devtools 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -512,14 +512,14 @@ dependencies = [
[[package]]
name = "ethcore-light"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-network 1.5.0",
"ethcore-util 1.5.0",
"ethcore 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-network 1.6.0",
"ethcore-util 1.6.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
@ -530,10 +530,10 @@ dependencies = [
[[package]]
name = "ethcore-logger"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-util 1.5.0",
"ethcore-util 1.6.0",
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -543,13 +543,13 @@ dependencies = [
[[package]]
name = "ethcore-network"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-util 1.6.0",
"ethcrypto 0.1.0",
"ethkey 0.2.0",
"igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -568,20 +568,20 @@ dependencies = [
[[package]]
name = "ethcore-rpc"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"ethash 1.5.0",
"ethcore 1.5.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-util 1.5.0",
"ethash 1.6.0",
"ethcore 1.6.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-util 1.6.0",
"ethcrypto 0.1.0",
"ethjson 0.1.0",
"ethkey 0.2.0",
"ethstore 0.1.0",
"ethsync 1.5.0",
"ethsync 1.6.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)",
@ -590,7 +590,7 @@ dependencies = [
"jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-reactor 0.1.0",
"parity-updater 1.5.0",
"parity-updater 1.6.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -604,18 +604,18 @@ dependencies = [
[[package]]
name = "ethcore-signer"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-rpc 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-util 1.6.0",
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"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",
"parity-ui 1.6.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)",
@ -623,14 +623,14 @@ dependencies = [
[[package]]
name = "ethcore-stratum"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.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)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -641,7 +641,7 @@ dependencies = [
[[package]]
name = "ethcore-util"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -651,7 +651,7 @@ dependencies = [
"eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)",
"ethcore-bigint 0.1.2",
"ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.5.0",
"ethcore-devtools 1.6.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -690,7 +690,7 @@ dependencies = [
name = "ethjson"
version = "0.1.0"
dependencies = [
"ethcore-util 1.5.0",
"ethcore-util 1.6.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -734,19 +734,19 @@ dependencies = [
[[package]]
name = "ethsync"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.5.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-light 1.5.0",
"ethcore-network 1.5.0",
"ethcore-util 1.5.0",
"ethcore 1.6.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-light 1.6.0",
"ethcore-network 1.6.0",
"ethcore-util 1.6.0",
"ethkey 0.2.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -759,7 +759,7 @@ dependencies = [
[[package]]
name = "evmjit"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -921,11 +921,11 @@ dependencies = [
[[package]]
name = "ipc-common-types"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-util 1.5.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-util 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1442,10 +1442,10 @@ dependencies = [
[[package]]
name = "parity-hash-fetch"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-util 1.5.0",
"ethcore-util 1.6.0",
"fetch 0.1.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1468,9 +1468,9 @@ dependencies = [
name = "parity-rpc-client"
version = "1.4.0"
dependencies = [
"ethcore-rpc 1.5.0",
"ethcore-signer 1.5.0",
"ethcore-util 1.5.0",
"ethcore-rpc 1.6.0",
"ethcore-signer 1.6.0",
"ethcore-util 1.6.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)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1486,7 +1486,7 @@ dependencies = [
[[package]]
name = "parity-ui"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"parity-ui-dev 1.4.0",
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)",
@ -1503,24 +1503,24 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#257b3ce8aaa6797507592200dc78b29b8a305c3f"
source = "git+https://github.com/ethcore/js-precompiled.git#e21ae69190fa390f5550e5cf17f5ea362ba4db41"
dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-updater"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-util 1.5.0",
"ethsync 1.5.0",
"ipc-common-types 1.5.0",
"ethcore 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-util 1.6.0",
"ethsync 1.6.0",
"ipc-common-types 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.5.0",
"parity-hash-fetch 1.6.0",
"parity-reactor 0.1.0",
"target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1795,8 +1795,8 @@ name = "rpc-cli"
version = "1.4.0"
dependencies = [
"ethcore-bigint 0.1.2",
"ethcore-rpc 1.5.0",
"ethcore-util 1.5.0",
"ethcore-rpc 1.6.0",
"ethcore-util 1.6.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-rpc-client 1.4.0",
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -1,7 +1,7 @@
[package]
description = "Parity Ethereum client"
name = "parity"
version = "1.5.0"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -1,7 +1,7 @@
[package]
description = "Parity Dapps crate"
name = "ethcore-dapps"
version = "1.5.0"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -1,7 +1,7 @@
[package]
description = "Base Package for all Parity built-in dapps"
name = "parity-dapps-glue"
version = "1.5.0"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -17,7 +17,7 @@
use std::io;
use std::io::Read;
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
@ -28,10 +28,79 @@ struct LocalDapp {
info: EndpointInfo,
}
fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path.as_str());
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())
))
})
})
}
fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
}
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}
fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path);
if let Err(e) = files {
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path.display(), e);
return vec![];
}
@ -59,51 +128,6 @@ fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
}
m.ok()
})
.map(|(name, path)| {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
})
.map(|(name, path)| local_dapp(name, path))
.collect()
}
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}

View File

@ -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/>.
use std::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
@ -43,7 +44,8 @@ pub fn utils() -> Box<Endpoint> {
}
pub fn all_endpoints<F: Fetch>(
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
@ -51,6 +53,13 @@ pub fn all_endpoints<F: Fetch>(
) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));

View File

@ -88,6 +88,7 @@ mod web;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
@ -123,7 +124,8 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
@ -141,9 +143,10 @@ impl<T: Fetch> Extendable for ServerBuilder<T> {
impl ServerBuilder {
/// Construct new dapps server
pub fn new(dapps_path: String, registrar: Arc<ContractClient>, remote: Remote) -> Self {
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
ServerBuilder {
dapps_path: dapps_path,
dapps_path: dapps_path.as_ref().to_owned(),
extra_dapps: vec![],
handler: Arc::new(IoHandler::new()),
registrar: registrar,
sync_status: Arc::new(|| false),
@ -160,6 +163,7 @@ impl<T: Fetch> ServerBuilder<T> {
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
extra_dapps: vec![],
handler: self.handler,
registrar: self.registrar,
sync_status: self.sync_status,
@ -188,6 +192,12 @@ impl<T: Fetch> ServerBuilder<T> {
self
}
/// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
@ -197,6 +207,7 @@ impl<T: Fetch> ServerBuilder<T> {
NoAuth,
self.handler.clone(),
self.dapps_path.clone(),
self.extra_dapps.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
@ -215,6 +226,7 @@ impl<T: Fetch> ServerBuilder<T> {
HttpBasicAuth::single_user(username, password),
self.handler.clone(),
self.dapps_path.clone(),
self.extra_dapps.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
@ -270,7 +282,8 @@ impl Server {
hosts: Option<Vec<String>>,
authorization: A,
handler: Arc<IoHandler>,
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
@ -287,7 +300,14 @@ impl Server {
remote.clone(),
fetch.clone(),
));
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone(), web_proxy_tokens, remote.clone(), fetch.clone()));
let endpoints = Arc::new(apps::all_endpoints(
dapps_path,
extra_dapps,
signer_address.clone(),
web_proxy_tokens,
remote.clone(),
fetch.clone(),
));
let cors_domains = Self::cors_domains(signer_address.clone());
let special = Arc::new({

View File

@ -51,7 +51,7 @@ pub fn init_server<F, B>(hosts: Option<Vec<String>>, process: F, remote: Remote)
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let server = process(ServerBuilder::new(
dapps_path.to_str().unwrap().into(), registrar.clone(), remote,
&dapps_path, registrar.clone(), remote,
))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap();
@ -66,7 +66,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync())
ServerBuilder::new(&dapps_path, registrar.clone(), Remote::new_sync())
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
}

View File

@ -3,7 +3,7 @@ description = "Ethcore Parity UI"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "parity-ui"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[build-dependencies]

View File

@ -3,7 +3,7 @@ description = "Ethcore Database"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "ethcore-db"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "ethcore-devtools"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "ethash"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[lib]

View File

@ -3,7 +3,7 @@ description = "Ethcore library"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "ethcore"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -3,7 +3,7 @@ description = "Parity LES primitives"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "ethcore-light"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -6,12 +6,14 @@
"gasLimitBoundDivisor": "0x0400",
"stepDuration": 1,
"startStep": 2,
"authorities" : [
"validators": {
"list": [
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
]
}
}
}
},
"params": {
"accountStartNonce": "0x0",

View File

@ -5,7 +5,9 @@
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"]
"validators": {
"list": ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"]
}
}
}
},
@ -17,7 +19,7 @@
},
"genesis": {
"seal": {
"generic": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
"generic": "0xc180"
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",

View File

@ -4,12 +4,14 @@
"tendermint": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"authorities" : [
"validators" : {
"list": [
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
]
}
}
}
},
"params": {
"accountStartNonce": "0x0",

View File

@ -0,0 +1,42 @@
{
"name": "TestValidatorContract",
"engine": {
"basicAuthority": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"validators": {
"contract": "0x0000000000000000000000000000000000000005"
}
}
}
},
"params": {
"accountStartNonce": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x69"
},
"genesis": {
"seal": {
"generic": "0xc180"
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0000000000000000000000000000000000000005": {
"balance": "1",
"constructor": "0x60a06040819052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60609081527382a978b3f5962a5b0957d9ee9eef472ee55b42f1608052600080546002825581805290927f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639182019291905b828111156100a25782518254600160a060020a031916600160a060020a0390911617825560209092019160019091019061006d565b5b506100cd9291505b808211156100c9578054600160a060020a03191681556001016100ab565b5090565b505034610000575b610332806100e46000396000f300606060405263ffffffff60e060020a60003504166335aa2e4481146100455780634d238c8e14610071578063b7ab4db51461008c578063f94e1867146100f4575b610000565b3461000057610055600435610106565b60408051600160a060020a039092168252519081900360200190f35b346100005761008a600160a060020a0360043516610136565b005b34610000576100996101ad565b60408051602080825283518183015283519192839290830191858101910280838382156100e1575b8051825260208311156100e157601f1990920191602091820191016100c1565b5050509050019250505060405180910390f35b346100005761008a600435610217565b005b600081815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b60008054806001018281815481835581811511610178576000838152602090206101789181019083015b808211156101745760008155600101610160565b5090565b5b505050916000526020600020900160005b8154600160a060020a038086166101009390930a92830292021916179055505b50565b604080516020818101835260008083528054845181840281018401909552808552929392909183018282801561020c57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116101ee575b505050505090505b90565b6000805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600082815481101561000057906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a031602179055506000600160008054905003815481101561000057906000526020600020900160005b6101000a815490600160a060020a03021916905560008054809190600190038154818355818115116102fd576000838152602090206102fd9181019083015b808211156101745760008155600101610160565b5090565b5b505050505b505600a165627a7a72305820d742dd391941c1c255f0e1187ffa5b1e783219264fb10196018aefa379f5638b0029"
},
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
}
}

View File

@ -16,6 +16,7 @@
//! Blockchain block.
use std::cmp;
use std::sync::Arc;
use std::collections::HashSet;
@ -266,8 +267,9 @@ impl<'x> OpenBlock<'x> {
r.block.base.header.set_extra_data(extra_data);
r.block.base.header.note_dirty();
let gas_floor_target = ::std::cmp::max(gas_range_target.0, engine.params().min_gas_limit);
engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target, gas_range_target.1);
let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit);
let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target);
engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target, gas_ceil_target);
engine.on_new_block(&mut r.block);
Ok(r)
}

View File

@ -53,7 +53,7 @@ use verification::queue::BlockQueue;
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use client::{
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
ChainNotify, PruningInfo,
};
use client::Error as ClientError;
@ -848,7 +848,7 @@ impl BlockChainClient for Client {
difficulty: header.difficulty(),
last_hashes: last_hashes,
gas_used: U256::zero(),
gas_limit: U256::max_value(),
gas_limit: header.gas_limit(),
};
// that's just a copy of the state.
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
@ -873,6 +873,81 @@ impl BlockChainClient for Client {
Ok(ret)
}
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
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: header.gas_limit(),
};
// that's just a copy of the state.
let mut 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 needed_balance = t.value + t.gas * t.gas_price;
if balance < needed_balance {
// give the sender a sufficient balance
original_state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
}
let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false };
let mut tx = t.clone();
let mut cond = |gas| {
let mut state = original_state.clone();
tx.gas = gas;
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
.transact(&tx, options.clone())
.map(|r| r.trace[0].result.succeeded())
.unwrap_or(false)
};
let mut upper = env_info.gas_limit;
if !cond(upper) {
// impossible at block gas limit - try `UPPER_CEILING` instead.
// TODO: consider raising limit by powers of two.
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
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)?;
@ -1315,11 +1390,6 @@ impl BlockChainClient for Client {
}
}
fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone()));
}
fn signing_network_id(&self) -> Option<u64> {
self.engine.signing_network_id(&self.latest_env_info())
}
@ -1414,16 +1484,6 @@ impl MiningBlockChainClient for Client {
&self.factories.vm
}
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_proposal_block(&self, block: SealedBlock) {
self.notify(|notify| {
notify.new_blocks(
@ -1471,6 +1531,22 @@ impl MiningBlockChainClient for Client {
}
}
impl EngineClient for Client {
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone()));
}
}
impl MayPanic for Client {
fn on_panic<F>(&self, closure: F) where F: OnPanicListener {
self.panic_handler.on_panic(closure);

View File

@ -28,7 +28,7 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain
pub use self::error::Error;
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
pub use self::chain_notify::ChainNotify;
pub use self::traits::{BlockChainClient, MiningBlockChainClient};
pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient};
pub use self::traits::ProvingBlockChainClient;

View File

@ -24,7 +24,7 @@ use devtools::*;
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, PendingTransaction, Action};
use blockchain::TreeRoute;
use client::{
BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId,
BlockChainClient, MiningBlockChainClient, EngineClient, BlockChainInfo, BlockStatus, BlockId,
TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError,
};
use db::{NUM_COLUMNS, COL_STATE};
@ -372,16 +372,6 @@ impl MiningBlockChainClient for TestBlockChainClient {
}
fn broadcast_proposal_block(&self, _block: SealedBlock) {}
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
}
impl BlockChainClient for TestBlockChainClient {
@ -389,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()
}
@ -699,8 +693,6 @@ impl BlockChainClient for TestBlockChainClient {
self.spec.engine.handle_message(&message).unwrap();
}
fn broadcast_consensus_message(&self, _message: Bytes) {}
fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain_info().best_block_number)
}
@ -727,3 +719,17 @@ impl BlockChainClient for TestBlockChainClient {
fn registry_address(&self, _name: String) -> Option<Address> { None }
}
impl EngineClient for TestBlockChainClient {
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_consensus_message(&self, _message: Bytes) {}
}

View File

@ -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>;
@ -208,9 +211,6 @@ pub trait BlockChainClient : Sync + Send {
/// Queue conensus engine message.
fn queue_consensus_message(&self, message: Bytes);
/// Used by PoA to communicate with peers.
fn broadcast_consensus_message(&self, message: Bytes);
/// List all transactions that are allowed into the next block.
fn ready_transactions(&self) -> Vec<PendingTransaction>;
@ -294,12 +294,6 @@ pub trait MiningBlockChainClient: BlockChainClient {
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;
/// Used by PoA to try sealing on period change.
fn update_sealing(&self);
/// Used by PoA to submit gathered signatures.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a block proposal.
fn broadcast_proposal_block(&self, block: SealedBlock);
@ -310,6 +304,18 @@ pub trait MiningBlockChainClient: BlockChainClient {
fn latest_schedule(&self) -> Schedule;
}
/// Client facilities used by internally sealing Engines.
pub trait EngineClient: MiningBlockChainClient {
/// Make a new block and seal it.
fn update_sealing(&self);
/// Submit a seal for a block in the mining queue.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a consensus message to the network.
fn broadcast_consensus_message(&self, message: Bytes);
}
/// Extended client interface for providing proofs of the state.
pub trait ProvingBlockChainClient: BlockChainClient {
/// Prove account storage at a specific block id.

View File

@ -32,11 +32,13 @@ use blockchain::extras::BlockDetails;
use views::HeaderView;
use evm::Schedule;
use ethjson;
use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel};
use service::ClientIoMessage;
use io::{IoContext, IoHandler, TimerToken, IoService};
use transaction::SignedTransaction;
use env_info::EnvInfo;
use builtin::Builtin;
use client::{Client, EngineClient};
use super::validator_set::{ValidatorSet, new_validator_set};
use state::CleanupMode;
/// `AuthorityRound` params.
#[derive(Debug, PartialEq)]
@ -45,12 +47,12 @@ pub struct AuthorityRoundParams {
pub gas_limit_bound_divisor: U256,
/// Time to wait before next block or authority switching.
pub step_duration: Duration,
/// Valid authorities.
pub authorities: Vec<Address>,
/// Number of authorities.
pub authority_n: usize,
/// Block reward.
pub block_reward: U256,
/// Starting step,
pub start_step: Option<u64>,
/// Valid validators.
pub validators: ethjson::spec::ValidatorSet,
}
impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
@ -58,8 +60,8 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
AuthorityRoundParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
step_duration: Duration::from_secs(p.step_duration.into()),
authority_n: p.authorities.len(),
authorities: p.authorities.into_iter().map(Into::into).collect::<Vec<_>>(),
validators: p.validators,
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
start_step: p.start_step.map(Into::into),
}
}
@ -69,14 +71,17 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct AuthorityRound {
params: CommonParams,
our_params: AuthorityRoundParams,
gas_limit_bound_divisor: U256,
block_reward: U256,
step_duration: Duration,
builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<()>,
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
step: AtomicUsize,
proposed: AtomicBool,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
client: RwLock<Option<Weak<EngineClient>>>,
account_provider: Mutex<Arc<AccountProvider>>,
password: RwLock<Option<String>>,
validators: Box<ValidatorSet + Send + Sync>,
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@ -105,14 +110,17 @@ impl AuthorityRound {
let engine = Arc::new(
AuthorityRound {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
block_reward: our_params.block_reward,
step_duration: our_params.step_duration,
builtins: builtins,
transition_service: IoService::<()>::start()?,
message_channel: Mutex::new(None),
step: AtomicUsize::new(initial_step),
proposed: AtomicBool::new(false),
account_provider: Mutex::new(None),
client: RwLock::new(None),
account_provider: Mutex::new(Arc::new(AccountProvider::transient_provider())),
password: RwLock::new(None),
validators: new_validator_set(our_params.validators),
});
// Do not initialize timeouts for tests.
if should_timeout {
@ -124,7 +132,7 @@ impl AuthorityRound {
fn remaining_step_duration(&self) -> Duration {
let now = unix_now();
let step_end = self.our_params.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
if step_end > now {
step_end - now
} else {
@ -132,13 +140,12 @@ impl AuthorityRound {
}
}
fn step_proposer(&self, step: usize) -> &Address {
let p = &self.our_params;
p.authorities.get(step % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed")
fn step_proposer(&self, step: usize) -> Address {
self.validators.get(step)
}
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
self.step_proposer(step) == address
self.step_proposer(step) == *address
}
}
@ -183,10 +190,9 @@ impl Engine for AuthorityRound {
fn step(&self) {
self.step.fetch_add(1, AtomicOrdering::SeqCst);
self.proposed.store(false, AtomicOrdering::SeqCst);
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::UpdateSealing) {
Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)),
Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.update_sealing();
}
}
}
@ -207,7 +213,7 @@ impl Engine for AuthorityRound {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -217,8 +223,7 @@ impl Engine for AuthorityRound {
}
fn is_sealer(&self, author: &Address) -> Option<bool> {
let p = &self.our_params;
Some(p.authorities.contains(author))
Some(self.validators.contains(author))
}
/// Attempt to seal the block internally.
@ -230,25 +235,31 @@ impl Engine for AuthorityRound {
let header = block.header();
let step = self.step.load(AtomicOrdering::SeqCst);
if self.is_step_proposer(step, header.author()) {
if let Some(ref ap) = *self.account_provider.lock() {
// Account should be permanently unlocked, otherwise sealing will fail.
let ref ap = *self.account_provider.lock();
if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst);
let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()];
return Seal::Regular(rlps);
return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
} else {
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable.");
}
} else {
warn!(target: "poa", "generate_seal: FAIL: Accounts not provided.");
}
} else {
trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step);
}
Seal::None
}
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) {
let fields = block.fields_mut();
// Bestow block reward
fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty);
// Commit state so that we can actually figure out the state root.
if let Err(e) = fields.state.commit() {
warn!("Encountered error on state commit: {}", e);
}
}
/// Check the number of seal fields.
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
if header.seal().len() != self.seal_fields() {
@ -267,7 +278,7 @@ impl Engine for AuthorityRound {
// Give one step slack if step is lagging, double vote is still not possible.
if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 {
let proposer_signature = header_signature(header)?;
let ok_sig = verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?;
let ok_sig = verify_address(&self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?;
if ok_sig {
Ok(())
} else {
@ -292,7 +303,7 @@ impl Engine for AuthorityRound {
Err(EngineError::DoubleVote(header.author().clone()))?;
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -323,8 +334,9 @@ impl Engine for AuthorityRound {
}
}
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
*self.message_channel.lock() = Some(message_channel);
fn register_client(&self, client: Weak<Client>) {
*self.client.write() = Some(client.clone());
self.validators.register_call_contract(client);
}
fn set_signer(&self, _address: Address, password: String) {
@ -332,7 +344,7 @@ impl Engine for AuthorityRound {
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(account_provider);
*self.account_provider.lock() = account_provider;
}
}
@ -440,7 +452,7 @@ mod tests {
let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
// Two authorities.
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err());
@ -459,7 +471,7 @@ mod tests {
let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
// Two authorities.
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok());

View File

@ -16,6 +16,8 @@
//! A blockchain engine that supports a basic, non-BFT proof-of-authority.
use std::sync::Weak;
use util::*;
use ethkey::{recover, public_to_address};
use account_provider::AccountProvider;
use block::*;
@ -28,26 +30,23 @@ use evm::Schedule;
use ethjson;
use header::Header;
use transaction::SignedTransaction;
use util::*;
use client::Client;
use super::validator_set::{ValidatorSet, new_validator_set};
/// `BasicAuthority` params.
#[derive(Debug, PartialEq)]
pub struct BasicAuthorityParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// Block duration.
pub duration_limit: u64,
/// Valid signatories.
pub authorities: HashSet<Address>,
pub validators: ethjson::spec::ValidatorSet,
}
impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
fn from(p: ethjson::spec::BasicAuthorityParams) -> Self {
BasicAuthorityParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
duration_limit: p.duration_limit.into(),
authorities: p.authorities.into_iter().map(Into::into).collect::<HashSet<_>>(),
validators: p.validators,
}
}
}
@ -56,10 +55,11 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct BasicAuthority {
params: CommonParams,
our_params: BasicAuthorityParams,
gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
password: RwLock<Option<String>>,
validators: Box<ValidatorSet + Send + Sync>,
}
impl BasicAuthority {
@ -67,8 +67,9 @@ impl BasicAuthority {
pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap<Address, Builtin>) -> Self {
BasicAuthority {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
builtins: builtins,
validators: new_validator_set(our_params.validators),
account_provider: Mutex::new(None),
password: RwLock::new(None),
}
@ -95,7 +96,7 @@ impl Engine for BasicAuthority {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -105,23 +106,23 @@ impl Engine for BasicAuthority {
}
fn is_sealer(&self, author: &Address) -> Option<bool> {
Some(self.our_params.authorities.contains(author))
Some(self.validators.contains(author))
}
/// Attempt to seal the block internally.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
if let Some(ref ap) = *self.account_provider.lock() {
let header = block.header();
let author = header.author();
if self.validators.contains(author) {
let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) {
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), message) {
return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
}
}
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
}
@ -145,7 +146,7 @@ impl Engine for BasicAuthority {
// check the signature is legit.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.our_params.authorities.contains(&signer) {
if !self.validators.contains(&signer) {
return Err(BlockError::InvalidSeal)?;
}
Ok(())
@ -161,7 +162,7 @@ impl Engine for BasicAuthority {
if header.difficulty() != parent.difficulty() {
return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() })))
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -179,6 +180,10 @@ impl Engine for BasicAuthority {
t.sender().map(|_|()) // Perform EC recovery and cache sender
}
fn register_client(&self, client: Weak<Client>) {
self.validators.register_call_contract(client);
}
fn set_signer(&self, _address: Address, password: String) {
*self.password.write() = Some(password);
}

View File

@ -21,6 +21,7 @@ mod instant_seal;
mod basic_authority;
mod authority_round;
mod tendermint;
mod validator_set;
pub use self::null_engine::NullEngine;
pub use self::instant_seal::InstantSeal;
@ -28,6 +29,7 @@ pub use self::basic_authority::BasicAuthority;
pub use self::authority_round::AuthorityRound;
pub use self::tendermint::Tendermint;
use std::sync::Weak;
use util::*;
use account_provider::AccountProvider;
use block::ExecutedBlock;
@ -36,13 +38,12 @@ use env_info::EnvInfo;
use error::Error;
use spec::CommonParams;
use evm::Schedule;
use io::IoChannel;
use service::ClientIoMessage;
use header::Header;
use transaction::SignedTransaction;
use ethereum::ethash;
use blockchain::extras::BlockDetails;
use views::HeaderView;
use client::Client;
/// Voting errors.
#[derive(Debug)]
@ -207,14 +208,15 @@ pub trait Engine : Sync + Send {
/// Register an account which signs consensus messages.
fn set_signer(&self, _address: Address, _password: String) {}
/// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {}
/// Add a channel for communication with Client which can be used for sealing.
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
/// Add Client which can be used for sealing, querying the state and sending messages.
fn register_client(&self, _client: Weak<Client>) {}
/// Add an account provider useful for Engines that sign stuff.
fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {}
/// Trigger next step of the consensus engine.
fn step(&self) {}
/// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {}
}

View File

@ -27,8 +27,10 @@ mod transition;
mod params;
mod vote_collector;
use std::sync::Weak;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::*;
use client::{Client, EngineClient};
use error::{Error, BlockError};
use header::Header;
use builtin::Builtin;
@ -43,8 +45,9 @@ use engines::{Engine, Seal, EngineError};
use blockchain::extras::BlockDetails;
use views::HeaderView;
use evm::Schedule;
use io::{IoService, IoChannel};
use service::ClientIoMessage;
use state::CleanupMode;
use io::IoService;
use super::validator_set::{ValidatorSet, new_validator_set};
use self::message::*;
use self::transition::TransitionHandler;
use self::params::TendermintParams;
@ -74,9 +77,11 @@ pub type BlockHash = H256;
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
pub struct Tendermint {
params: CommonParams,
our_params: TendermintParams,
gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>,
step_service: IoService<Step>,
client: RwLock<Option<Weak<EngineClient>>>,
block_reward: U256,
/// Address to be used as authority.
authority: RwLock<Address>,
/// Password used for signing messages.
@ -89,8 +94,6 @@ pub struct Tendermint {
step: RwLock<Step>,
/// Vote accumulator.
votes: VoteCollector,
/// Channel for updating the sealing.
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
/// Used to sign messages and proposals.
account_provider: Mutex<Option<Arc<AccountProvider>>>,
/// Message for the last PoLC.
@ -99,6 +102,8 @@ pub struct Tendermint {
last_lock: AtomicUsize,
/// Bare hash of the proposed block, used for seal submission.
proposal: RwLock<Option<H256>>,
/// Set used to determine the current validators.
validators: Box<ValidatorSet + Send + Sync>,
}
impl Tendermint {
@ -107,53 +112,49 @@ impl Tendermint {
let engine = Arc::new(
Tendermint {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
builtins: builtins,
client: RwLock::new(None),
step_service: IoService::<Step>::start()?,
block_reward: our_params.block_reward,
authority: RwLock::new(Address::default()),
password: RwLock::new(None),
height: AtomicUsize::new(1),
round: AtomicUsize::new(0),
step: RwLock::new(Step::Propose),
votes: VoteCollector::new(),
message_channel: Mutex::new(None),
account_provider: Mutex::new(None),
lock_change: RwLock::new(None),
last_lock: AtomicUsize::new(0),
proposal: RwLock::new(None),
validators: new_validator_set(our_params.validators),
});
let handler = TransitionHandler { engine: Arc::downgrade(&engine) };
let handler = TransitionHandler::new(Arc::downgrade(&engine), our_params.timeouts);
engine.step_service.register_handler(Arc::new(handler))?;
Ok(engine)
}
fn update_sealing(&self) {
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::UpdateSealing) {
Ok(_) => trace!(target: "poa", "UpdateSealing message sent."),
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.update_sealing();
}
}
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) {
Ok(_) => trace!(target: "poa", "SubmitSeal message sent."),
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.submit_seal(block_hash, seal);
}
}
}
fn broadcast_message(&self, message: Bytes) {
let channel = self.message_channel.lock().clone();
if let Some(ref channel) = channel {
match channel.send(ClientIoMessage::BroadcastMessage(message)) {
Ok(_) => trace!(target: "poa", "BroadcastMessage message sent."),
Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.broadcast_consensus_message(message);
}
} else {
warn!(target: "poa", "broadcast_message: No IoChannel available.");
}
}
@ -264,23 +265,22 @@ impl Tendermint {
}
fn is_authority(&self, address: &Address) -> bool {
self.our_params.authorities.contains(address)
self.validators.contains(address)
}
fn is_above_threshold(&self, n: usize) -> bool {
n > self.our_params.authority_n * 2/3
n > self.validators.count() * 2/3
}
/// Check if address is a proposer for given round.
fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> {
let ref p = self.our_params;
let proposer_nonce = height + round;
trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce);
let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed");
if proposer == address {
let proposer = self.validators.get(proposer_nonce);
if proposer == *address {
Ok(())
} else {
Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() }))
Err(EngineError::NotProposer(Mismatch { expected: proposer, found: address.clone() }))
}
}
@ -404,7 +404,7 @@ impl Engine for Tendermint {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -469,6 +469,17 @@ impl Engine for Tendermint {
Ok(())
}
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) {
let fields = block.fields_mut();
// Bestow block reward
fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty);
// Commit state so that we can actually figure out the state root.
if let Err(e) = fields.state.commit() {
warn!("Encountered error on state commit: {}", e);
}
}
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let seal_length = header.seal().len();
if seal_length == self.seal_fields() {
@ -507,7 +518,7 @@ impl Engine for Tendermint {
Some(a) => a,
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
};
if !self.our_params.authorities.contains(&address) {
if !self.validators.contains(&address) {
Err(EngineError::NotAuthorized(address.to_owned()))?
}
@ -540,7 +551,7 @@ impl Engine for Tendermint {
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -643,9 +654,9 @@ impl Engine for Tendermint {
self.to_step(next_step);
}
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
trace!(target: "poa", "Register the IoChannel.");
*self.message_channel.lock() = Some(message_channel);
fn register_client(&self, client: Weak<Client>) {
*self.client.write() = Some(client.clone());
self.validators.register_call_contract(client);
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
@ -656,21 +667,20 @@ impl Engine for Tendermint {
#[cfg(test)]
mod tests {
use util::*;
use io::{IoContext, IoHandler};
use block::*;
use error::{Error, BlockError};
use header::Header;
use io::IoChannel;
use env_info::EnvInfo;
use client::chain_notify::ChainNotify;
use miner::MinerService;
use tests::helpers::*;
use account_provider::AccountProvider;
use service::ClientIoMessage;
use spec::Spec;
use engines::{Engine, EngineError, Seal};
use super::*;
use super::message::*;
/// Accounts inserted with "0" and "1" are authorities. First proposer is "0".
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
fn setup() -> (Spec, Arc<AccountProvider>) {
let tap = Arc::new(AccountProvider::transient_provider());
let spec = Spec::new_test_tendermint();
@ -678,13 +688,13 @@ mod tests {
(spec, tap)
}
fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec<Bytes>) {
fn propose_default(spec: &Spec, proposer: Address) -> (ClosedBlock, Vec<Bytes>) {
let mut db_result = get_temp_state_db();
let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock();
let b = b.close();
if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) {
(b, seal)
} else {
@ -692,7 +702,7 @@ mod tests {
}
}
fn vote<F>(engine: &Arc<Engine>, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
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 m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
engine.handle_message(&m).unwrap();
@ -710,37 +720,26 @@ mod tests {
]
}
fn precommit_signatures(tap: &Arc<AccountProvider>, height: Height, round: Round, bare_hash: Option<H256>, v1: H160, v2: H160) -> Bytes {
let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash);
::rlp::encode(&vec![
H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()),
H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap())
]).to_vec()
}
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
tap.unlock_account_permanently(addr, acc.into()).unwrap();
addr
}
fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Arc<Engine>, acc: &str) -> Address {
fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Engine, acc: &str) -> Address {
let addr = insert_and_unlock(tap, acc);
engine.set_signer(addr.clone(), acc.into());
addr
}
struct TestIo {
received: RwLock<Vec<ClientIoMessage>>
#[derive(Default)]
struct TestNotify {
messages: RwLock<Vec<Bytes>>,
}
impl TestIo {
fn new() -> Arc<Self> { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) }
}
impl IoHandler<ClientIoMessage> for TestIo {
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
self.received.write().push(net_message.clone());
impl ChainNotify for TestNotify {
fn broadcast(&self, data: Vec<u8>) {
self.messages.write().push(data);
}
}
@ -864,10 +863,10 @@ mod tests {
fn can_generate_seal() {
let (spec, tap) = setup();
let proposer = insert_and_register(&tap, &spec.engine, "1");
let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1");
let (b, seal) = propose_default(&spec, proposer);
assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok());
assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok());
spec.engine.stop();
}
@ -875,10 +874,10 @@ mod tests {
fn can_recognize_proposal() {
let (spec, tap) = setup();
let proposer = insert_and_register(&tap, &spec.engine, "1");
let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1");
let (b, seal) = propose_default(&spec, proposer);
let sealed = b.seal(spec.engine.as_ref(), seal).unwrap();
let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap();
assert!(spec.engine.is_proposal(sealed.header()));
spec.engine.stop();
}
@ -888,8 +887,8 @@ mod tests {
let (spec, tap) = setup();
let engine = spec.engine.clone();
let v0 = insert_and_register(&tap, &engine, "0");
let v1 = insert_and_register(&tap, &engine, "1");
let v0 = insert_and_register(&tap, engine.as_ref(), "0");
let v1 = insert_and_register(&tap, engine.as_ref(), "1");
let h = 0;
let r = 0;
@ -898,57 +897,74 @@ mod tests {
let (b, _) = propose_default(&spec, v1.clone());
let proposal = Some(b.header().bare_hash());
// Register IoHandler remembers messages.
let test_io = TestIo::new();
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
engine.register_message_channel(channel);
let client = generate_dummy_client(0);
let notify = Arc::new(TestNotify::default());
client.add_notify(notify.clone());
engine.register_client(Arc::downgrade(&client));
let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
let prevote_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
let precommit_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal);
let prevote_future = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal);
// Relays all valid present and future messages.
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current)));
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current)));
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future)));
assert!(notify.messages.read().contains(&prevote_current));
assert!(notify.messages.read().contains(&precommit_current));
assert!(notify.messages.read().contains(&prevote_future));
engine.stop();
}
#[test]
fn seal_submission() {
let (spec, tap) = setup();
let engine = spec.engine.clone();
use ethkey::{Generator, Random};
use types::transaction::{Transaction, Action};
use client::BlockChainClient;
let v0 = insert_and_register(&tap, &engine, "0");
let v1 = insert_and_register(&tap, &engine, "1");
let client = generate_dummy_client_with_spec_and_data(Spec::new_test_tendermint, 0, 0, &[]);
let engine = client.engine();
let tap = Arc::new(AccountProvider::transient_provider());
// Accounts for signing votes.
let v0 = insert_and_unlock(&tap, "0");
let v1 = insert_and_unlock(&tap, "1");
let notify = Arc::new(TestNotify::default());
engine.register_account_provider(tap.clone());
client.add_notify(notify.clone());
engine.register_client(Arc::downgrade(&client));
let keypair = Random.generate().unwrap();
let transaction = Transaction {
action: Action::Create,
value: U256::zero(),
data: "3331600055".from_hex().unwrap(),
gas: U256::from(100_000),
gas_price: U256::zero(),
nonce: U256::zero(),
}.sign(keypair.secret(), None);
client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap();
client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap();
// Propose
let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash());
// Propose timeout
engine.step();
let h = 1;
let r = 0;
// Register IoHandler remembers messages.
let test_io = TestIo::new();
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
engine.register_message_channel(channel);
// Propose
let (b, mut seal) = propose_default(&spec, v1.clone());
let proposal = Some(b.header().bare_hash());
engine.step();
// Prevote.
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0);
let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()));
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1);
let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal));
assert_eq!(client.chain_info().best_block_number, 0);
// Last precommit.
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
assert_eq!(client.chain_info().best_block_number, 1);
assert!(first ^ second);
engine.stop();
}
}

View File

@ -18,33 +18,20 @@
use ethjson;
use super::transition::TendermintTimeouts;
use util::{Address, U256};
use util::{U256, Uint};
use time::Duration;
/// `Tendermint` params.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct TendermintParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// List of authorities.
pub authorities: Vec<Address>,
/// Number of authorities.
pub authority_n: usize,
/// List of validators.
pub validators: ethjson::spec::ValidatorSet,
/// Timeout durations for different steps.
pub timeouts: TendermintTimeouts,
}
impl Default for TendermintParams {
fn default() -> Self {
let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()];
let val_n = authorities.len();
TendermintParams {
gas_limit_bound_divisor: 0x0400.into(),
authorities: authorities,
authority_n: val_n,
timeouts: TendermintTimeouts::default(),
}
}
/// Block reward.
pub block_reward: U256,
}
fn to_duration(ms: ethjson::uint::Uint) -> Duration {
@ -54,19 +41,17 @@ fn to_duration(ms: ethjson::uint::Uint) -> Duration {
impl From<ethjson::spec::TendermintParams> for TendermintParams {
fn from(p: ethjson::spec::TendermintParams) -> Self {
let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect();
let val_n = val.len();
let dt = TendermintTimeouts::default();
TendermintParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
authorities: val,
authority_n: val_n,
validators: p.validators,
timeouts: TendermintTimeouts {
propose: p.timeout_propose.map_or(dt.propose, to_duration),
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
precommit: p.timeout_precommit.map_or(dt.precommit, to_duration),
commit: p.timeout_commit.map_or(dt.commit, to_duration),
},
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
}
}
}

View File

@ -23,7 +23,17 @@ use super::{Tendermint, Step};
use engines::Engine;
pub struct TransitionHandler {
pub engine: Weak<Tendermint>,
engine: Weak<Tendermint>,
timeouts: TendermintTimeouts,
}
impl TransitionHandler {
pub fn new(engine: Weak<Tendermint>, timeouts: TendermintTimeouts) -> Self {
TransitionHandler {
engine: engine,
timeouts: timeouts,
}
}
}
/// Base timeout of each step in ms.
@ -67,9 +77,7 @@ fn set_timeout(io: &IoContext<Step>, timeout: Duration) {
impl IoHandler<Step> for TransitionHandler {
fn initialize(&self, io: &IoContext<Step>) {
if let Some(engine) = self.engine.upgrade() {
set_timeout(io, engine.our_params.timeouts.propose)
}
set_timeout(io, self.timeouts.propose)
}
fn timeout(&self, _io: &IoContext<Step>, timer: TimerToken) {
@ -81,16 +89,14 @@ impl IoHandler<Step> for TransitionHandler {
}
fn message(&self, io: &IoContext<Step>, next_step: &Step) {
if let Some(engine) = self.engine.upgrade() {
if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) {
warn!(target: "poa", "Could not remove consensus timer {}.", io_err)
}
match *next_step {
Step::Propose => set_timeout(io, engine.our_params.timeouts.propose),
Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote),
Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit),
Step::Commit => set_timeout(io, engine.our_params.timeouts.commit),
Step::Propose => set_timeout(io, self.timeouts.propose),
Step::Prevote => set_timeout(io, self.timeouts.prevote),
Step::Precommit => set_timeout(io, self.timeouts.precommit),
Step::Commit => set_timeout(io, self.timeouts.commit),
};
}
}
}

View File

@ -0,0 +1,218 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Validator set maintained in a contract.
use std::sync::Weak;
use util::*;
use client::{Client, BlockChainClient};
use client::chain_notify::ChainNotify;
use super::ValidatorSet;
use super::simple_list::SimpleList;
/// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorContract {
address: Address,
validators: RwLock<SimpleList>,
provider: RwLock<Option<provider::Contract>>,
}
impl ValidatorContract {
pub fn new(contract_address: Address) -> Self {
ValidatorContract {
address: contract_address,
validators: Default::default(),
provider: RwLock::new(None),
}
}
/// Queries the state and updates the set of validators.
pub fn update(&self) {
if let Some(ref provider) = *self.provider.read() {
match provider.get_validators() {
Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new);
*self.validators.write() = SimpleList::new(new);
},
Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s),
}
} else {
warn!(target: "engine", "Set of validators could not be updated: no provider contract.")
}
}
}
/// Checks validators on every block.
impl ChainNotify for ValidatorContract {
fn new_blocks(
&self,
_imported: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<Bytes>,
_duration: u64) {
self.update();
}
}
impl ValidatorSet for Arc<ValidatorContract> {
fn contains(&self, address: &Address) -> bool {
self.validators.read().contains(address)
}
fn get(&self, nonce: usize) -> Address {
self.validators.read().get(nonce).clone()
}
fn count(&self) -> usize {
self.validators.read().count()
}
fn register_call_contract(&self, client: Weak<Client>) {
if let Some(c) = client.upgrade() {
c.add_notify(self.clone());
}
{
*self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))));
}
self.update();
}
}
mod provider {
// Autogenerated from JSON contract definition using Rust contract convertor.
#![allow(unused_imports)]
use std::string::String;
use std::result::Result;
use std::fmt;
use {util, ethabi};
use util::{FixedHash, Uint};
pub struct Contract {
contract: ethabi::Contract,
address: util::Address,
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
}
impl Contract {
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
Contract {
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
address: address,
do_call: Box::new(do_call),
}
}
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> {
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<_>>() }))
}
}
}
#[cfg(test)]
mod tests {
use util::*;
use spec::Spec;
use account_provider::AccountProvider;
use transaction::{Transaction, Action};
use client::{BlockChainClient, EngineClient};
use miner::MinerService;
use tests::helpers::generate_dummy_client_with_spec_and_data;
use super::super::ValidatorSet;
use super::ValidatorContract;
#[test]
fn fetches_validators() {
let client = generate_dummy_client_with_spec_and_data(Spec::new_validator_contract, 0, 0, &[]);
let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_call_contract(Arc::downgrade(&client));
vc.update();
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
}
#[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 spec_factory = || {
let spec = Spec::new_validator_contract();
spec.engine.register_account_provider(tap.clone());
spec
};
let client = generate_dummy_client_with_spec_and_data(spec_factory, 0, 0, &[]);
client.engine().register_client(Arc::downgrade(&client));
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
client.miner().set_engine_signer(v1, "".into()).unwrap();
// Remove "1" validator.
let tx = Transaction {
nonce: 0.into(),
gas_price: 0.into(),
gas: 500_000.into(),
action: Action::Call(validator_contract),
value: 0.into(),
data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(),
}.sign(&"1".sha3(), None);
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 1);
// Add "1" validator back in.
let tx = Transaction {
nonce: 1.into(),
gas_price: 0.into(),
gas: 500_000.into(),
action: Action::Call(validator_contract),
value: 0.into(),
data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(),
}.sign(&"1".sha3(), 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.
assert_eq!(client.chain_info().best_block_number, 1);
// Switch to the validator that is still there.
client.miner().set_engine_signer(v0, "".into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 2);
// Switch back to the added validator, since the state is updated.
client.miner().set_engine_signer(v1, "".into()).unwrap();
let tx = Transaction {
nonce: 2.into(),
gas_price: 0.into(),
gas: 21000.into(),
action: Action::Call(Address::default()),
value: 0.into(),
data: Vec::new(),
}.sign(&"1".sha3(), None);
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
client.update_sealing();
// Able to seal again.
assert_eq!(client.chain_info().best_block_number, 3);
}
}

View File

@ -0,0 +1,46 @@
// 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/>.
/// Validator lists.
mod simple_list;
mod contract;
use std::sync::Weak;
use util::{Address, Arc};
use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client;
use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))),
}
}
pub trait ValidatorSet {
/// Checks if a given address is a validator.
fn contains(&self, address: &Address) -> bool;
/// Draws an validator nonce modulo number of validators.
fn get(&self, nonce: usize) -> Address;
/// Returns the current number of validators.
fn count(&self) -> usize;
/// Allows blockchain state access.
fn register_call_contract(&self, _client: Weak<Client>) {}
}

View File

@ -0,0 +1,68 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Preconfigured validator list.
use util::Address;
use super::ValidatorSet;
#[derive(Debug, PartialEq, Eq, Default)]
pub struct SimpleList {
validators: Vec<Address>,
validator_n: usize,
}
impl SimpleList {
pub fn new(validators: Vec<Address>) -> Self {
SimpleList {
validator_n: validators.len(),
validators: validators,
}
}
}
impl ValidatorSet for SimpleList {
fn contains(&self, address: &Address) -> bool {
self.validators.contains(address)
}
fn get(&self, nonce: usize) -> Address {
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
}
fn count(&self) -> usize {
self.validator_n
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use util::Address;
use super::super::ValidatorSet;
use super::SimpleList;
#[test]
fn simple_list() {
let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
let list = SimpleList::new(vec![a1.clone(), a2.clone()]);
assert!(list.contains(&a1));
assert_eq!(list.get(0), a1);
assert_eq!(list.get(1), a2);
assert_eq!(list.get(2), a1);
}
}

View File

@ -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,

View File

@ -33,7 +33,7 @@ fn test_trie(json: &[u8], trie: TrieSpec) -> Vec<String> {
let key: Vec<u8> = key.into();
let value: Vec<u8> = value.map_or_else(Vec::new, Into::into);
t.insert(&key, &value)
.expect(&format!("Trie test '{:?}' failed due to internal error", name))
.expect(&format!("Trie test '{:?}' failed due to internal error", name));
}
if *t.root() != test.root.into() {

View File

@ -23,6 +23,7 @@ use std::cell::Cell;
use transaction::{SignedTransaction, Action};
use transient_hashmap::TransientHashMap;
use miner::{TransactionQueue, TransactionImportResult, TransactionOrigin, AccountDetails};
use miner::transaction_queue::QueuingInstant;
use error::{Error, TransactionError};
use util::{Uint, U256, H256, Address, Hashable};
@ -78,6 +79,7 @@ impl BanningTransactionQueue {
pub fn add_with_banlist<F, G>(
&mut self,
transaction: SignedTransaction,
time: QueuingInstant,
account_details: &F,
gas_estimator: &G,
) -> Result<TransactionImportResult, Error> where
@ -115,7 +117,7 @@ impl BanningTransactionQueue {
}
}
}
self.queue.add(transaction, TransactionOrigin::External, None, account_details, gas_estimator)
self.queue.add(transaction, TransactionOrigin::External, time, None, account_details, gas_estimator)
}
/// Ban transaction with given hash.
@ -158,7 +160,7 @@ impl BanningTransactionQueue {
Threshold::BanAfter(threshold) if count > threshold => {
// Banlist the sender.
// Remove all transactions from the queue.
self.remove_all(address, !U256::zero());
self.cull(address, !U256::zero());
true
},
_ => false
@ -263,7 +265,7 @@ mod tests {
let mut txq = queue();
// when
txq.queue().add(tx, TransactionOrigin::External, None, &default_account_details, &gas_required).unwrap();
txq.queue().add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_required).unwrap();
// then
// should also deref to queue
@ -279,12 +281,12 @@ mod tests {
let banlist1 = txq.ban_sender(tx.sender().unwrap());
assert!(!banlist1, "Threshold not reached yet.");
// Insert once
let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap();
let import1 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required).unwrap();
assert_eq!(import1, TransactionImportResult::Current);
// when
let banlist2 = txq.ban_sender(tx.sender().unwrap());
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required);
let import2 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required);
// then
assert!(banlist2, "Threshold should be reached - banned.");
@ -303,12 +305,12 @@ mod tests {
let banlist1 = txq.ban_recipient(recipient);
assert!(!banlist1, "Threshold not reached yet.");
// Insert once
let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap();
let import1 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required).unwrap();
assert_eq!(import1, TransactionImportResult::Current);
// when
let banlist2 = txq.ban_recipient(recipient);
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required);
let import2 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required);
// then
assert!(banlist2, "Threshold should be reached - banned.");
@ -325,12 +327,12 @@ mod tests {
let banlist1 = txq.ban_codehash(codehash);
assert!(!banlist1, "Threshold not reached yet.");
// Insert once
let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap();
let import1 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required).unwrap();
assert_eq!(import1, TransactionImportResult::Current);
// when
let banlist2 = txq.ban_codehash(codehash);
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required);
let import2 = txq.add_with_banlist(tx.clone(), 0, &default_account_details, &gas_required);
// then
assert!(banlist2, "Threshold should be reached - banned.");

View File

@ -417,15 +417,12 @@ impl Miner {
let block = open_block.close();
let fetch_account = |a: &Address| AccountDetails {
nonce: chain.latest_nonce(a),
balance: chain.latest_balance(a),
};
let fetch_nonce = |a: &Address| chain.latest_nonce(a);
{
let mut queue = self.transaction_queue.lock();
for hash in invalid_transactions {
queue.remove_invalid(&hash, &fetch_account);
queue.remove_invalid(&hash, &fetch_nonce);
}
for hash in transactions_to_penalize {
queue.penalize(&hash);
@ -597,6 +594,8 @@ impl Miner {
let schedule = chain.latest_schedule();
let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into();
let best_block_header = chain.best_block_header().decode();
let insertion_time = chain.chain_info().best_block_number;
transactions.into_iter()
.map(|tx| {
if chain.transaction_block(TransactionId::Hash(tx.hash())).is_some() {
@ -618,10 +617,10 @@ impl Miner {
match origin {
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
transaction_queue.add(tx, origin, min_block, &fetch_account, &gas_required)
transaction_queue.add(tx, origin, insertion_time, min_block, &fetch_account, &gas_required)
},
TransactionOrigin::External => {
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
transaction_queue.add_with_banlist(tx, insertion_time, &fetch_account, &gas_required)
}
}
},
@ -674,7 +673,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) => {
@ -682,7 +681,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(),
@ -707,16 +706,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)
}
}
@ -765,9 +762,17 @@ impl MinerService for Miner {
if let Some(ref ap) = self.accounts {
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
}
// Limit the scope of the locks.
{
let mut sealing_work = self.sealing_work.lock();
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
*self.author.write() = address;
}
// --------------------------------------------------------------------------
// | NOTE Code below may require author and sealing_work locks |
// | (some `Engine`s call `EngineClient.update_sealing()`) |.
// | Make sure to release the locks before calling that method. |
// --------------------------------------------------------------------------
self.engine.set_signer(address, password);
}
Ok(())
@ -1141,8 +1146,13 @@ impl MinerService for Miner {
// ...and at the end remove the old ones
{
let fetch_account = |a: &Address| AccountDetails {
nonce: chain.latest_nonce(a),
balance: chain.latest_balance(a),
};
let time = chain.chain_info().best_block_number;
let mut transaction_queue = self.transaction_queue.lock();
transaction_queue.remove_old(|sender| chain.latest_nonce(sender));
transaction_queue.remove_old(&fetch_account, time);
}
if enacted.len() > 0 {

View File

@ -82,7 +82,7 @@ impl PriceInfo {
}
}
#[test]
#[test] #[ignore]
fn should_get_price_info() {
use std::sync::Arc;
use std::time::Duration;

View File

@ -51,8 +51,8 @@
//! let gas_estimator = |_tx: &SignedTransaction| 2.into();
//!
//! let mut txq = TransactionQueue::default();
//! txq.add(st2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
//! txq.add(st1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
//! txq.add(st2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
//! txq.add(st1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
//!
//! // Check status
//! assert_eq!(txq.status().pending, 2);
@ -64,7 +64,7 @@
//!
//! // And when transaction is removed (but nonce haven't changed)
//! // it will move subsequent transactions to future
//! txq.remove_invalid(&st1.hash(), &default_account_details);
//! txq.remove_invalid(&st1.hash(), &|_| 10.into());
//! assert_eq!(txq.status().pending, 0);
//! assert_eq!(txq.status().future, 1);
//! assert_eq!(txq.top_transactions().len(), 0);
@ -78,11 +78,11 @@
//! - When it's removed from `future` - all `future` transactions heights are recalculated and then
//! we check if the transactions should go to `current` (comparing state nonce)
//! - When it's removed from `current` - all transactions from this sender (`current` & `future`) are recalculated.
//! 3. `remove_all` is used to inform the queue about client (state) nonce changes.
//! 3. `cull` is used to inform the queue about client (state) nonce changes.
//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce
//! - It moves matching `future` transactions to `current`
//! 4. `remove_old` is used as convenient method to update the state nonce for all senders in the queue.
//! - Invokes `remove_all` with latest state nonce for all senders.
//! - Invokes `cull` with latest state nonce for all senders.
use std::ops::Deref;
use std::cmp::Ordering;
@ -258,16 +258,19 @@ struct VerifiedTransaction {
transaction: SignedTransaction,
/// Transaction origin.
origin: TransactionOrigin,
/// Insertion time
insertion_time: QueuingInstant,
/// Delay until specifid block.
min_block: Option<BlockNumber>,
}
impl VerifiedTransaction {
fn new(transaction: SignedTransaction, origin: TransactionOrigin, min_block: Option<BlockNumber>) -> Result<Self, Error> {
fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option<BlockNumber>) -> Result<Self, Error> {
transaction.sender()?;
Ok(VerifiedTransaction {
transaction: transaction,
origin: origin,
insertion_time: time,
min_block: min_block,
})
}
@ -283,6 +286,10 @@ impl VerifiedTransaction {
fn sender(&self) -> Address {
self.transaction.sender().expect("Sender is verified in new; qed")
}
fn cost(&self) -> U256 {
self.transaction.value + self.transaction.gas_price * self.transaction.gas
}
}
#[derive(Debug, Default)]
@ -488,6 +495,10 @@ pub enum PrioritizationStrategy {
GasFactorAndGasPrice,
}
/// Point in time when transaction was inserted.
pub type QueuingInstant = BlockNumber;
const DEFAULT_QUEUING_PERIOD: BlockNumber = 128;
/// `TransactionQueue` implementation
pub struct TransactionQueue {
/// Prioritization strategy for this queue
@ -498,6 +509,10 @@ pub struct TransactionQueue {
tx_gas_limit: U256,
/// Current gas limit (block gas limit * factor). Transactions above the limit will not be accepted (default to !0)
gas_limit: U256,
/// Maximal time transaction may occupy the queue.
/// When we reach `max_time_in_queue / 2^3` we re-validate
/// account balance.
max_time_in_queue: QueuingInstant,
/// Priority queue for transactions that can go to block
current: TransactionSet,
/// Priority queue for transactions that has been received but are not yet valid to go to block
@ -545,6 +560,7 @@ impl TransactionQueue {
minimal_gas_price: U256::zero(),
tx_gas_limit: tx_gas_limit,
gas_limit: !U256::zero(),
max_time_in_queue: DEFAULT_QUEUING_PERIOD,
current: current,
future: future,
by_hash: HashMap::new(),
@ -624,6 +640,7 @@ impl TransactionQueue {
&mut self,
tx: SignedTransaction,
origin: TransactionOrigin,
time: QueuingInstant,
min_block: Option<BlockNumber>,
fetch_account: &F,
gas_estimator: &G,
@ -635,7 +652,7 @@ impl TransactionQueue {
let hash = tx.hash();
let cloned_tx = tx.clone();
let result = self.add_internal(tx, origin, min_block, fetch_account, gas_estimator);
let result = self.add_internal(tx, origin, time, min_block, fetch_account, gas_estimator);
match result {
Ok(TransactionImportResult::Current) => {
self.local_transactions.mark_pending(hash);
@ -656,7 +673,7 @@ impl TransactionQueue {
}
result
} else {
self.add_internal(tx, origin, min_block, fetch_account, gas_estimator)
self.add_internal(tx, origin, time, min_block, fetch_account, gas_estimator)
}
}
@ -665,6 +682,7 @@ impl TransactionQueue {
&mut self,
tx: SignedTransaction,
origin: TransactionOrigin,
time: QueuingInstant,
min_block: Option<BlockNumber>,
fetch_account: &F,
gas_estimator: &G,
@ -734,10 +752,10 @@ impl TransactionQueue {
// Verify signature
tx.check_low_s()?;
let vtx = VerifiedTransaction::new(tx, origin, min_block)?;
let vtx = VerifiedTransaction::new(tx, origin, time, min_block)?;
let client_account = fetch_account(&vtx.sender());
let cost = vtx.transaction.value + vtx.transaction.gas_price * vtx.transaction.gas;
let cost = vtx.cost();
if client_account.balance < cost {
trace!(target: "txqueue",
"Dropping transaction without sufficient balance: {:?} ({} < {})",
@ -759,7 +777,7 @@ impl TransactionQueue {
/// Removes all transactions from particular sender up to (excluding) given client (state) nonce.
/// Client (State) Nonce = next valid nonce for this sender.
pub fn remove_all(&mut self, sender: Address, client_nonce: U256) {
pub fn cull(&mut self, sender: Address, client_nonce: U256) {
// Check if there is anything in current...
let should_check_in_current = self.current.by_address.row(&sender)
// If nonce == client_nonce nothing is changed
@ -775,11 +793,11 @@ impl TransactionQueue {
return;
}
self.remove_all_internal(sender, client_nonce);
self.cull_internal(sender, client_nonce);
}
/// Always updates future and moves transactions from current to future.
fn remove_all_internal(&mut self, sender: Address, client_nonce: U256) {
fn cull_internal(&mut self, sender: Address, client_nonce: U256) {
// We will either move transaction to future or remove it completely
// so there will be no transactions from this sender in current
self.last_nonces.remove(&sender);
@ -794,16 +812,46 @@ impl TransactionQueue {
}
/// Checks the current nonce for all transactions' senders in the queue and removes the old transactions.
pub fn remove_old<F>(&mut self, fetch_nonce: F) where
F: Fn(&Address) -> U256,
pub fn remove_old<F>(&mut self, fetch_account: &F, current_time: QueuingInstant) where
F: Fn(&Address) -> AccountDetails,
{
let senders = self.current.by_address.keys()
.chain(self.future.by_address.keys())
.cloned()
.collect::<HashSet<_>>();
.map(|sender| (*sender, fetch_account(sender)))
.collect::<HashMap<_, _>>();
for sender in senders {
self.remove_all(sender, fetch_nonce(&sender));
for (sender, details) in senders.iter() {
self.cull(*sender, details.nonce);
}
let max_time = self.max_time_in_queue;
let balance_check = max_time >> 3;
// Clear transactions occupying the queue too long
let invalid = self.by_hash.iter()
.filter(|&(_, ref tx)| !tx.origin.is_local())
.map(|(hash, tx)| (hash, tx, current_time.saturating_sub(tx.insertion_time)))
.filter_map(|(hash, tx, time_diff)| {
if time_diff > max_time {
return Some(*hash);
}
if time_diff > balance_check {
return match senders.get(&tx.sender()) {
Some(details) if tx.cost() > details.balance => {
Some(*hash)
},
_ => None,
};
}
None
})
.collect::<Vec<_>>();
let fetch_nonce = |a: &Address| senders.get(a)
.expect("We fetch details for all senders from both current and future")
.nonce;
for hash in invalid {
self.remove_invalid(&hash, &fetch_nonce);
}
}
@ -851,8 +899,8 @@ impl TransactionQueue {
/// so transactions left in queue are processed according to client nonce.
///
/// If gap is introduced marks subsequent transactions as future
pub fn remove_invalid<T>(&mut self, transaction_hash: &H256, fetch_account: &T)
where T: Fn(&Address) -> AccountDetails {
pub fn remove_invalid<T>(&mut self, transaction_hash: &H256, fetch_nonce: &T)
where T: Fn(&Address) -> U256 {
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
let transaction = self.by_hash.remove(transaction_hash);
@ -864,7 +912,7 @@ impl TransactionQueue {
let transaction = transaction.expect("None is tested in early-exit condition above; qed");
let sender = transaction.sender();
let nonce = transaction.nonce();
let current_nonce = fetch_account(&sender).nonce;
let current_nonce = fetch_nonce(&sender);
trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash());
@ -889,7 +937,7 @@ impl TransactionQueue {
if order.is_some() {
// This will keep consistency in queue
// Moves all to future and then promotes a batch from current:
self.remove_all_internal(sender, current_nonce);
self.cull_internal(sender, current_nonce);
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
return;
}
@ -1039,7 +1087,7 @@ impl TransactionQueue {
/// Finds transaction in the queue by hash (if any)
pub fn find(&self, hash: &H256) -> Option<SignedTransaction> {
match self.by_hash.get(hash) { Some(transaction_ref) => Some(transaction_ref.transaction.clone()), None => None }
self.by_hash.get(hash).map(|tx| tx.transaction.clone())
}
/// Removes all elements (in any state) from the queue
@ -1388,14 +1436,14 @@ mod test {
let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into());
let sender = tx1.sender().unwrap();
let nonce = tx1.nonce;
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 2);
assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into()));
// when
let tx = new_tx(123.into(), 1.into());
let res = txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
// No longer the case as we don't even consider a transaction that isn't above a full
@ -1427,12 +1475,12 @@ mod test {
gas_limit: !U256::zero(),
};
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, 0, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, 0, None).unwrap();
let mut by_hash = {
let mut x = HashMap::new();
let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, 0, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, 0, None).unwrap();
x.insert(tx1.hash(), tx1);
x.insert(tx2.hash(), tx2);
x
@ -1470,12 +1518,12 @@ mod test {
// Create two transactions with same nonce
// (same hash)
let (tx1, tx2) = new_tx_pair_default(0.into(), 0.into());
let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, 0, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, 0, None).unwrap();
let by_hash = {
let mut x = HashMap::new();
let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, 0, None).unwrap();
let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, 0, None).unwrap();
x.insert(tx1.hash(), tx1);
x.insert(tx2.hash(), tx2);
x
@ -1517,10 +1565,10 @@ mod test {
gas_limit: !U256::zero(),
};
let tx = new_tx_default();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, 0, None).unwrap();
let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none());
let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External, None).unwrap();
let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External, 0, None).unwrap();
let order2 = TransactionOrder::for_transaction(&tx2, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some());
}
@ -1537,7 +1585,7 @@ mod test {
assert_eq!(set.gas_price_entry_limit(), 0.into());
let tx = new_tx_default();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None).unwrap();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, 0, None).unwrap();
let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none());
assert_eq!(set.gas_price_entry_limit(), 2.into());
@ -1552,12 +1600,12 @@ mod test {
!U256::zero() };
// First insert one transaction to future
let res = txq.add(tx, TransactionOrigin::External, None, &prev_nonce, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator);
assert_eq!(res.unwrap(), TransactionImportResult::Future);
assert_eq!(txq.status().future, 1);
// now import second transaction to current
let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// and then there should be only one transaction in current (the one with higher gas_price)
assert_eq!(res.unwrap(), TransactionImportResult::Current);
@ -1577,12 +1625,12 @@ mod test {
!U256::zero() };
// First insert one transaction to future
let res = txq.add(tx.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator);
let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator);
assert_eq!(res.unwrap(), TransactionImportResult::Future);
assert_eq!(txq.status().future, 1);
// now import second transaction to current
let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(res.unwrap(), TransactionImportResult::Current);
@ -1601,7 +1649,7 @@ mod test {
let tx = new_tx_default();
// when
let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(res.unwrap(), TransactionImportResult::Current);
@ -1620,10 +1668,10 @@ mod test {
txq.set_minimal_gas_price(15.into());
// when
let res1 = txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res3 = txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res4 = txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
@ -1654,10 +1702,10 @@ mod test {
txq.set_minimal_gas_price(15.into());
// when
let res1 = txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res3 = txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res4 = txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
@ -1700,7 +1748,7 @@ mod test {
txq.set_gas_limit(limit);
// when
let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(unwrap_tx_err(res), TransactionError::GasLimitExceeded {
@ -1724,7 +1772,7 @@ mod test {
};
// when
let res = txq.add(tx, TransactionOrigin::External, None, &account, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &account, &gas_estimator);
// then
assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientBalance {
@ -1744,7 +1792,7 @@ mod test {
txq.set_minimal_gas_price(tx.gas_price + U256::one());
// when
let res = txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientGasPrice {
@ -1764,7 +1812,7 @@ mod test {
txq.set_minimal_gas_price(tx.gas_price + U256::one());
// when
let res = txq.add(tx, TransactionOrigin::Local, None, &default_account_details, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(res.unwrap(), TransactionImportResult::Current);
@ -1794,7 +1842,7 @@ mod test {
rlp::decode(s.as_raw())
};
// when
let res = txq.add(stx, TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(stx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
assert!(res.is_err());
@ -1808,8 +1856,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let top = txq.top_transactions();
@ -1828,9 +1876,9 @@ mod test {
// when
// first insert the one with higher gas price
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then the one with lower gas price, but local
txq.add(tx.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let top = txq.top_transactions();
@ -1847,15 +1895,15 @@ mod test {
// the second one has same nonce but higher `gas_price`
let (_, tx0) = new_similar_tx_pair();
txq.add(tx0.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx0.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// the one with higher gas price is first
assert_eq!(txq.top_transactions()[0], tx0);
assert_eq!(txq.top_transactions()[1], tx1);
// when
// insert second as local
txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
// the order should be updated
@ -1874,9 +1922,9 @@ mod test {
// when
// first insert local one with higher gas price
txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
// then the one with lower gas price, but from retracted block
txq.add(tx.clone(), TransactionOrigin::RetractedBlock, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::RetractedBlock, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let top = txq.top_transactions();
@ -1892,8 +1940,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let top = txq.top_transactions();
@ -1912,10 +1960,10 @@ mod test {
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
// insert everything
txq.add(txa.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(txa.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 4);
@ -1940,10 +1988,10 @@ mod test {
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
// insert everything
txq.add(txa.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txa.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
let top = txq.top_transactions();
assert_eq!(top[0], tx1);
@ -1973,10 +2021,10 @@ mod test {
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
// insert everything
txq.add(txa.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
let top = txq.top_transactions();
assert_eq!(top[0], tx1);
@ -2005,8 +2053,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let top = txq.pending_hashes();
@ -2023,8 +2071,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(2.into(), 0.into());
// when
let res1 = txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
assert_eq!(res1, TransactionImportResult::Current);
@ -2045,8 +2093,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when
let res1 = txq.add(tx.clone(), TransactionOrigin::External, Some(1), &default_account_details, &gas_estimator).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(1), &default_account_details, &gas_estimator).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
assert_eq!(res1, TransactionImportResult::Current);
@ -2067,12 +2115,12 @@ mod test {
let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 2);
// when
txq.remove_all(tx.sender().unwrap(), next2_nonce);
txq.cull(tx.sender().unwrap(), next2_nonce);
// should remove both transactions since they are not valid
// then
@ -2090,13 +2138,13 @@ mod test {
let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None);
let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None);
txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 1);
txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
// when
txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let stats = txq.status();
@ -2112,14 +2160,14 @@ mod test {
// given
let mut txq2 = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(3.into(), 0.into());
txq2.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq2.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq2.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq2.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq2.status().pending, 1);
assert_eq!(txq2.status().future, 1);
// when
txq2.remove_all(tx.sender().unwrap(), tx.nonce + U256::one());
txq2.remove_all(tx2.sender().unwrap(), tx2.nonce + U256::one());
txq2.cull(tx.sender().unwrap(), tx.nonce + U256::one());
txq2.cull(tx2.sender().unwrap(), tx2.nonce + U256::one());
// then
@ -2134,14 +2182,14 @@ mod test {
let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let tx3 = new_tx_default();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 3);
// when
txq.remove_invalid(&tx.hash(), &default_account_details);
txq.remove_invalid(&tx.hash(), &|_| default_nonce());
// then
let stats = txq.status();
@ -2156,8 +2204,8 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// add
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
let stats = txq.status();
assert_eq!(stats.pending, 2);
@ -2176,11 +2224,11 @@ mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let sender = tx.sender().unwrap();
let nonce = tx.nonce;
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 1);
// when
let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator);
let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator);
// then
let t = txq.top_transactions();
@ -2197,14 +2245,14 @@ mod test {
txq.current.set_limit(10);
let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into());
let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into());
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 2);
// when
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
txq.add(tx4.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
assert_eq!(txq.status().future, 1);
@ -2215,11 +2263,11 @@ mod test {
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero());
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// limited by gas
txq.add(tx4.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap_err();
txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap_err();
assert_eq!(txq.status().pending, 2);
}
@ -2229,12 +2277,12 @@ mod test {
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2));
txq.add(tx1.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
// Not accepted because of limit
txq.add(tx5.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap_err();
txq.add(tx3.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4.clone(), TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx5.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap_err();
txq.add(tx3.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 4);
}
@ -2246,7 +2294,7 @@ mod test {
let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() };
// when
let res = txq.add(tx, TransactionOrigin::External, None, &fetch_last_nonce, &gas_estimator);
let res = txq.add(tx, TransactionOrigin::External, 0, None, &fetch_last_nonce, &gas_estimator);
// then
assert_eq!(unwrap_tx_err(res), TransactionError::Old);
@ -2262,12 +2310,12 @@ mod test {
balance: !U256::zero() };
let mut txq = TransactionQueue::default();
let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
assert_eq!(txq.status().pending, 0);
// when
let res = txq.add(tx2.clone(), TransactionOrigin::External, None, &nonce, &gas_estimator);
let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &nonce, &gas_estimator);
// then
assert_eq!(unwrap_tx_err(res), TransactionError::AlreadyImported);
@ -2281,15 +2329,15 @@ mod test {
// given
let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 2);
// when
txq.remove_invalid(&tx1.hash(), &default_account_details);
txq.remove_invalid(&tx1.hash(), &|_| default_nonce());
assert_eq!(txq.status().pending, 0);
assert_eq!(txq.status().future, 1);
txq.add(tx1.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let stats = txq.status();
@ -2303,15 +2351,15 @@ mod test {
let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let tx3 = new_tx_default();
txq.add(tx2.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
txq.add(tx3.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().pending, 3);
// when
let sender = tx.sender().unwrap();
txq.remove_all(sender, default_nonce() + U256::one());
txq.cull(sender, default_nonce() + U256::one());
// then
let stats = txq.status();
@ -2333,8 +2381,8 @@ mod test {
};
// when
txq.add(tx, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let stats = txq.status();
@ -2361,10 +2409,10 @@ mod test {
};
// when
txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 1);
txq.add(tx0, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx0, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
// then
let stats = txq.status();
@ -2376,18 +2424,16 @@ mod test {
#[test]
fn should_recalculate_height_when_removing_from_future() {
// given
let previous_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
!U256::zero() };
let next_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce + U256::one(), balance:
!U256::zero() };
let previous_nonce = |a: &Address|
AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() };
let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx1.clone(), TransactionOrigin::External, None, &previous_nonce, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, None, &previous_nonce, &gas_estimator).unwrap();
txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &previous_nonce, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 0, None, &previous_nonce, &gas_estimator).unwrap();
assert_eq!(txq.status().future, 2);
// when
txq.remove_invalid(&tx1.hash(), &next_nonce);
txq.remove_invalid(&tx1.hash(), &|_| default_nonce() + 1.into());
// then
let stats = txq.status();
@ -2414,7 +2460,7 @@ mod test {
let details = |_a: &Address| AccountDetails { nonce: nonce, balance: !U256::zero() };
// when
txq.add(tx, TransactionOrigin::External, None, &details, &gas_estimator).unwrap();
txq.add(tx, TransactionOrigin::External, 0, None, &details, &gas_estimator).unwrap();
// then
assert_eq!(txq.last_nonce(&from), Some(nonce));
@ -2429,17 +2475,17 @@ mod test {
let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() };
// Insert first transaction
txq.add(tx1, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap();
txq.add(tx1, TransactionOrigin::External, 0, None, &details1, &gas_estimator).unwrap();
// when
txq.remove_all(tx2.sender().unwrap(), nonce2 + U256::one());
txq.cull(tx2.sender().unwrap(), nonce2 + U256::one());
// then
assert!(txq.top_transactions().is_empty());
}
#[test]
fn should_return_valid_last_nonce_after_remove_all() {
fn should_return_valid_last_nonce_after_cull() {
// given
let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into());
@ -2449,11 +2495,11 @@ mod test {
// when
// Insert first transaction
assert_eq!(txq.add(tx1, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current);
assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current);
// Second should go to future
assert_eq!(txq.add(tx2, TransactionOrigin::External, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future);
assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future);
// Now block is imported
txq.remove_all(sender, nonce2 - U256::from(1));
txq.cull(sender, nonce2 - U256::from(1));
// tx2 should be not be promoted to current
assert_eq!(txq.status().pending, 0);
assert_eq!(txq.status().future, 1);
@ -2470,9 +2516,9 @@ mod test {
assert_eq!(txq.has_local_pending_transactions(), false);
// when
assert_eq!(txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current);
assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current);
assert_eq!(txq.has_local_pending_transactions(), false);
assert_eq!(txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current);
assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current);
// then
assert_eq!(txq.has_local_pending_transactions(), true);
@ -2487,8 +2533,8 @@ mod test {
default_account_details(a).balance };
// when
assert_eq!(txq.add(tx2, TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future);
assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future);
assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future);
assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future);
// then
assert_eq!(txq.future.by_priority.len(), 1);
@ -2513,14 +2559,14 @@ mod test {
(tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None))
};
let sender = tx1.sender().unwrap();
txq.add(tx1, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3, TransactionOrigin::Local, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.future.by_priority.len(), 0);
assert_eq!(txq.current.by_priority.len(), 3);
// when
let res = txq.add(tx2_2, TransactionOrigin::Local, None, &default_account_details, &gas_estimator);
let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator);
// then
assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into());
@ -2536,8 +2582,8 @@ mod test {
let high_gas = |_: &SignedTransaction| 100_001.into();
// when
let res1 = txq.add(tx1, TransactionOrigin::Local, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::Local, None, &default_account_details, &high_gas);
let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator);
let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_account_details, &high_gas);
// then
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
@ -2554,20 +2600,45 @@ mod test {
let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into());
let nonce1 = tx1.nonce;
let next_nonce = |_: &Address|
AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() };
// Insert all transactions
txq.add(tx1, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4, TransactionOrigin::External, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.top_transactions().len(), 4);
// when
txq.remove_old(|_| nonce1 + U256::one());
txq.remove_old(&next_nonce, 0);
// then
assert_eq!(txq.top_transactions().len(), 2);
}
#[test]
fn should_remove_out_of_date_transactions_occupying_queue() {
// given
let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into());
// Insert all transactions
txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx2, TransactionOrigin::External, 5, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_account_details, &gas_estimator).unwrap();
txq.add(tx4, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap();
assert_eq!(txq.top_transactions().len(), 3);
assert_eq!(txq.future_transactions().len(), 1);
// when
txq.remove_old(&default_account_details, 9 + super::DEFAULT_QUEUING_PERIOD);
// then
assert_eq!(txq.top_transactions().len(), 2);
assert_eq!(txq.future_transactions().len(), 0);
assert_eq!(txq.top_transactions(), vec![tx1, tx3]);
}
}

View File

@ -20,7 +20,7 @@ use util::*;
use io::*;
use spec::Spec;
use error::*;
use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify};
use client::{Client, ClientConfig, ChainNotify};
use miner::Miner;
use snapshot::ManifestData;
use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
@ -46,12 +46,6 @@ pub enum ClientIoMessage {
FeedBlockChunk(H256, Bytes),
/// Take a snapshot for the block with given number.
TakeSnapshot(u64),
/// Trigger sealing update (useful for internal sealing).
UpdateSealing,
/// Submit seal (useful for internal sealing).
SubmitSeal(H256, Vec<Bytes>),
/// Broadcast a message to the network.
BroadcastMessage(Bytes),
/// New consensus message received.
NewMessage(Bytes)
}
@ -114,7 +108,7 @@ impl ClientService {
});
io_service.register_handler(client_io)?;
spec.engine.register_message_channel(io_service.channel());
spec.engine.register_client(Arc::downgrade(&client));
let stop_guard = ::devtools::StopGuard::new();
run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share());
@ -221,9 +215,6 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler {
debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e);
}
},
ClientIoMessage::UpdateSealing => self.client.update_sealing(),
ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()),
ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()),
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
trace!(target: "poa", "Invalid message received: {}", e);
},

View File

@ -337,9 +337,14 @@ impl Spec {
pub fn new_instant() -> Spec { load_bundled!("instant_seal") }
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
/// Accounts with secrets "0".sha3() and "1".sha3() are the validators.
pub fn new_test_round() -> Self { load_bundled!("authority_round") }
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators.
/// Accounts with secrets "0".sha3() and "1".sha3() are initially the validators.
/// Second validator can be removed with "0xf94e18670000000000000000000000000000000000000000000000000000000000000001" and added back in using "0x4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work).
/// Account "0".sha3() and "1".sha3() are a authorities.
pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") }

View File

@ -178,8 +178,8 @@ impl Account {
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
using it will not fail.");
let item: U256 = match db.get(key){
Ok(x) => x.map_or_else(U256::zero, |v| decode(&*v)),
let item: U256 = match db.get_with(key, ::rlp::decode) {
Ok(x) => x.unwrap_or_else(U256::zero),
Err(e) => panic!("Encountered potential DB corruption: {}", e),
};
let value: H256 = item.into();
@ -453,12 +453,12 @@ impl Account {
/// omitted.
pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
use util::trie::{Trie, TrieDB};
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
use util::trie::recorder::Recorder;
let mut recorder = TrieRecorder::with_depth(from_level);
let mut recorder = Recorder::with_depth(from_level);
let trie = TrieDB::new(db, &self.storage_root)?;
let _ = trie.get_recorded(&storage_key, &mut recorder)?;
let _ = trie.get_with(&storage_key, &mut recorder)?;
Ok(recorder.drain().into_iter().map(|r| r.data).collect())
}

View File

@ -32,7 +32,7 @@ use state_db::StateDB;
use util::*;
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
use util::trie::recorder::Recorder;
mod account;
mod substate;
@ -425,8 +425,8 @@ impl State {
// account is not found in the global cache, get from the DB and insert into local
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
let maybe_acc = match db.get(address) {
Ok(acc) => acc.map(|v| Account::from_rlp(&v)),
let maybe_acc = match db.get_with(address, Account::from_rlp) {
Ok(acc) => acc,
Err(e) => panic!("Potential DB corruption encountered: {}", e),
};
let r = maybe_acc.as_ref().map_or(H256::new(), |a| {
@ -690,8 +690,8 @@ impl State {
// not found in the global cache, get from the DB and insert into local
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
let mut maybe_acc = match db.get(a) {
Ok(acc) => acc.map(|v| Account::from_rlp(&v)),
let mut maybe_acc = match db.get_with(a, Account::from_rlp) {
Ok(acc) => acc,
Err(e) => panic!("Potential DB corruption encountered: {}", e),
};
if let Some(ref mut account) = maybe_acc.as_mut() {
@ -722,9 +722,8 @@ impl State {
None => {
let maybe_acc = if self.db.check_non_null_bloom(a) {
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
match db.get(a) {
Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))),
Ok(None) => AccountEntry::new_clean(None),
match db.get_with(a, Account::from_rlp) {
Ok(acc) => AccountEntry::new_clean(acc),
Err(e) => panic!("Potential DB corruption encountered: {}", e),
}
} else {
@ -770,9 +769,9 @@ impl State {
/// Requires a secure trie to be used for accurate results.
/// `account_key` == sha3(address)
pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
let mut recorder = TrieRecorder::with_depth(from_level);
let mut recorder = Recorder::with_depth(from_level);
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let _ = trie.get_recorded(&account_key, &mut recorder)?;
trie.get_with(&account_key, &mut recorder)?;
Ok(recorder.drain().into_iter().map(|r| r.data).collect())
}
@ -786,8 +785,8 @@ impl State {
// TODO: probably could look into cache somehow but it's keyed by
// address, not sha3(address).
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let acc = match trie.get(&account_key)? {
Some(rlp) => Account::from_rlp(&rlp),
let acc = match trie.get_with(&account_key, Account::from_rlp)? {
Some(acc) => acc,
None => return Ok(Vec::new()),
};
@ -799,8 +798,8 @@ impl State {
/// Only works when backed by a secure trie.
pub fn code_by_address_hash(&self, account_key: H256) -> Result<Option<Bytes>, Box<TrieError>> {
let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?;
let mut acc = match trie.get(&account_key)? {
Some(rlp) => Account::from_rlp(&rlp),
let mut acc = match trie.get_with(&account_key, Account::from_rlp)? {
Some(acc) => acc,
None => return Ok(None),
};

View File

@ -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)
}
}

View File

@ -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;
@ -239,6 +239,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();

View File

@ -1,6 +1,6 @@
[package]
name = "evmjit"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[lib]

View File

@ -3,7 +3,7 @@ description = "Fetching hash-addressed content."
homepage = "http://parity.io"
license = "GPL-3.0"
name = "parity-hash-fetch"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]

View File

@ -1,7 +1,7 @@
[package]
description = "Types that implement IPC and are common to multiple modules."
name = "ipc-common-types"
version = "1.5.0"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"

View File

@ -1,6 +1,6 @@
[package]
name = "ethcore-ipc-codegen"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "GPL-3.0"
description = "Macros to auto-generate implementations for ipc call"

View File

@ -1,6 +1,6 @@
[package]
name = "ethcore-ipc-nano"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "GPL-3.0"

View File

@ -1,6 +1,6 @@
[package]
name = "ethcore-ipc"
version = "1.5.0"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "GPL-3.0"

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.2.168",
"version": "0.2.182",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",
@ -139,7 +139,6 @@
"blockies": "0.0.2",
"brace": "0.9.0",
"bytes": "2.4.0",
"crypto-js": "3.1.9-1",
"debounce": "1.0.0",
"es6-error": "4.0.0",
"es6-promise": "4.0.5",
@ -153,6 +152,7 @@
"isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5",
"lodash": "4.17.2",
"loglevel": "1.4.1",
"marked": "0.3.6",
"material-ui": "0.16.5",
"material-ui-chip-input": "0.11.1",

View File

@ -75,6 +75,10 @@ export default class Contract {
return this._functions;
}
get receipt () {
return this._receipt;
}
get instance () {
this._instance.address = this._address;
return this._instance;
@ -139,6 +143,7 @@ export default class Contract {
}
setState({ state: 'hasReceipt', receipt });
this._receipt = receipt;
this._address = receipt.contractAddress;
return this._address;
});
@ -218,14 +223,19 @@ export default class Contract {
}
_encodeOptions (func, options, values) {
options.data = this.getCallData(func, options, values);
return options;
const data = this.getCallData(func, options, values);
return {
...options,
data
};
}
_addOptionsTo (options = {}) {
return Object.assign({
to: this._address
}, options);
return {
to: this._address,
...options
};
}
_bindFunction = (func) => {

View File

@ -31,6 +31,7 @@ const eth = new Api(transport);
describe('api/contract/Contract', () => {
const ADDR = '0x0123456789';
const ABI = [
{
type: 'function', name: 'test',
@ -41,12 +42,42 @@ describe('api/contract/Contract', () => {
type: 'function', name: 'test2',
outputs: [{ type: 'uint' }, { type: 'uint' }]
},
{ type: 'constructor' },
{
type: 'constructor',
inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }]
},
{ type: 'event', name: 'baz' },
{ type: 'event', name: 'foo' }
];
const VALUES = [true, 'jacogr'];
const ENCODED = '0x023562050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000';
const ABI_NO_PARAMS = [
{
type: 'function', name: 'test',
inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }],
outputs: [{ type: 'uint' }]
},
{
type: 'function', name: 'test2',
outputs: [{ type: 'uint' }, { type: 'uint' }]
},
{
type: 'constructor'
},
{ type: 'event', name: 'baz' },
{ type: 'event', name: 'foo' }
];
const VALUES = [ true, 'jacogr' ];
const CALLDATA = `
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000006
6a61636f67720000000000000000000000000000000000000000000000000000
`.replace(/\s/g, '');
const SIGNATURE = '02356205';
const ENCODED = `0x${SIGNATURE}${CALLDATA}`;
const RETURN1 = '0000000000000000000000000000000000000000000000000000000000123456';
const RETURN2 = '0000000000000000000000000000000000000000000000000000000000456789';
let scope;
@ -230,6 +261,33 @@ describe('api/contract/Contract', () => {
});
});
describe('deploy without parameters', () => {
const contract = new Contract(eth, ABI_NO_PARAMS);
const CODE = '0x123';
const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351';
const RECEIPT_DONE = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 2500 };
let scope;
describe('success', () => {
before(() => {
scope = mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'parity_postTransaction', reply: { result: '0x678' } },
{ method: 'parity_checkRequest', reply: { result: '0x890' } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } },
{ method: 'eth_getCode', reply: { result: CODE } }
]);
return contract.deploy({ data: CODE }, []);
});
it('passes the options through to postTransaction (incl. gas calculation)', () => {
expect(scope.body.parity_postTransaction.params[0].data).to.equal(CODE);
});
});
});
describe('deploy', () => {
const contract = new Contract(eth, ABI);
const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351';
@ -252,7 +310,7 @@ describe('api/contract/Contract', () => {
{ method: 'eth_getCode', reply: { result: '0x456' } }
]);
return contract.deploy({ data: '0x123' }, []);
return contract.deploy({ data: '0x123' }, VALUES);
});
it('calls estimateGas, postTransaction, checkRequest, getTransactionReceipt & getCode in order', () => {
@ -261,7 +319,7 @@ describe('api/contract/Contract', () => {
it('passes the options through to postTransaction (incl. gas calculation)', () => {
expect(scope.body.parity_postTransaction.params).to.deep.equal([
{ data: '0x123', gas: '0x4b0' }
{ data: `0x123${CALLDATA}`, gas: '0x4b0' }
]);
});
@ -280,7 +338,7 @@ describe('api/contract/Contract', () => {
]);
return contract
.deploy({ data: '0x123' }, [])
.deploy({ data: '0x123' }, VALUES)
.catch((error) => {
expect(error.message).to.match(/not deployed, gasUsed/);
});
@ -296,7 +354,7 @@ describe('api/contract/Contract', () => {
]);
return contract
.deploy({ data: '0x123' }, [])
.deploy({ data: '0x123' }, VALUES)
.catch((error) => {
expect(error.message).to.match(/not deployed, getCode/);
});

View File

@ -20,15 +20,21 @@ export function bytesToHex (bytes) {
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join('');
}
export function hex2Ascii (_hex) {
const hex = /^(?:0x)?(.*)$/.exec(_hex.toString())[1];
export function hexToBytes (hex) {
const raw = toHex(hex).slice(2);
const bytes = [];
let str = '';
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
for (let i = 0; i < raw.length; i += 2) {
bytes.push(parseInt(raw.substr(i, 2), 16));
}
return bytes;
}
export function hexToAscii (hex) {
const bytes = hexToBytes(hex);
const str = bytes.map((byte) => String.fromCharCode(byte)).join('');
return str;
}

View File

@ -14,7 +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 { bytesToHex } from './format';
import { bytesToHex, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format';
describe('api/util/format', () => {
describe('bytesToHex', () => {
@ -26,4 +26,46 @@ describe('api/util/format', () => {
expect(bytesToHex([0, 15, 16])).to.equal('0x000f10');
});
});
describe('hexToBytes', () => {
it('correctly converts an empty string', () => {
expect(hexToBytes('')).to.deep.equal([]);
expect(hexToBytes('0x')).to.deep.equal([]);
});
it('correctly converts a non-empty string', () => {
expect(hexToBytes('0x000f10')).to.deep.equal([0, 15, 16]);
});
});
describe('asciiToHex', () => {
it('correctly converts an empty string', () => {
expect(asciiToHex('')).to.equal('0x');
});
it('correctly converts a non-empty string', () => {
expect(asciiToHex('abc')).to.equal('0x616263');
});
});
describe('hexToAscii', () => {
it('correctly converts an empty string', () => {
expect(hexToAscii('')).to.equal('');
expect(hexToAscii('0x')).to.equal('');
});
it('correctly converts a non-empty string', () => {
expect(hexToAscii('0x616263')).to.equal('abc');
});
});
describe('bytesToAscii', () => {
it('correctly converts an empty string', () => {
expect(bytesToAscii([])).to.equal('');
});
it('correctly converts a non-empty string', () => {
expect(bytesToAscii([97, 98, 99])).to.equal('abc');
});
});
});

View File

@ -16,7 +16,7 @@
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
import { bytesToHex, hex2Ascii, asciiToHex } from './format';
import { bytesToHex, hexToAscii, asciiToHex } from './format';
import { fromWei, toWei } from './wei';
import { sha3 } from './sha3';
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
@ -30,7 +30,7 @@ export default {
isInstanceOf,
isString,
bytesToHex,
hex2Ascii,
hexToAscii,
asciiToHex,
createIdentityImg,
decodeCallData,

View File

@ -14,21 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import CryptoJS from 'crypto-js';
import CryptoSha3 from 'crypto-js/sha3';
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') {
if (value.length > 2 && value.substr(0, 2) === '0x') {
value = value.substr(2);
const forceHex = options && options.encoding === 'hex';
if (forceHex || (!options && isHex(value))) {
const bytes = hexToBytes(value);
return sha3(bytes);
}
value = CryptoJS.enc.Hex.parse(value);
}
const hash = CryptoSha3(value, {
outputLength: 256
}).toString();
const hash = keccak_256(value);
return `0x${hash}`;
}
sha3.text = (val) => sha3(val, { encoding: 'raw' });

View File

@ -21,5 +21,25 @@ describe('api/util/sha3', () => {
it('constructs a correct sha3 value', () => {
expect(sha3('jacogr')).to.equal('0x2f4ff4b5a87abbd2edfed699db48a97744e028c7f7ce36444d40d29d792aa4dc');
});
it('constructs a correct sha3 encoded as hex', () => {
const key = '000000000000000000000000391694e7e0b0cce554cb130d723a9d27458f9298' + '0000000000000000000000000000000000000000000000000000000000000001';
expect(sha3(key, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9');
expect(sha3(`0x${key}`, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9');
});
it('constructs a correct sha3 from Uint8Array', () => {
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');
});
});
});

View File

@ -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));
}

View File

@ -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', () => {

28
js/src/config.js Normal file
View File

@ -0,0 +1,28 @@
// 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 LogLevel from 'loglevel';
export const LOG_KEYS = {
TransferModalStore: {
path: 'modals/Transfer/store',
desc: 'Transfer Modal MobX Store'
}
};
export const getLogger = (LOG_KEY) => {
return LogLevel.getLogger(LOG_KEY.path);
};

View File

@ -14,7 +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 { bytesToHex, hex2Ascii } from '~/api/util/format';
import { bytesToHex, hexToAscii } from '~/api/util/format';
import ABI from './abi/certifier.json';
@ -62,7 +62,7 @@ export default class BadgeReg {
name = bytesToHex(name);
name = name === ZERO32
? null
: hex2Ascii(name);
: hexToAscii(name);
return this.fetchMeta(id)
.then(({ title, icon }) => {
@ -84,7 +84,7 @@ export default class BadgeReg {
})
.then(([ title, icon ]) => {
title = bytesToHex(title);
title = title === ZERO32 ? null : hex2Ascii(title);
title = title === ZERO32 ? null : hexToAscii(title);
if (bytesToHex(icon) === ZERO32) {
icon = null;

View File

@ -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']);

View File

@ -14,7 +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 subscribeToEvent from '../util/subscribe-to-event';
import subscribeToEvents from '../util/subscribe-to-events';
export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]);
@ -72,7 +72,7 @@ export const awaitPuzzle = (api, contract, account) => {
return blockNumber(api)
.then((block) => {
return new Promise((resolve, reject) => {
const subscription = subscribeToEvent(contract, 'Puzzled', {
const subscription = subscribeToEvents(contract, ['Puzzled'], {
from: block.toNumber(),
filter: (log) => log.params.who.value === account
});

View File

@ -118,7 +118,7 @@ export function attachInstances () {
]);
})
.then(([managerAddress, registryAddress, tokenregAddress]) => {
console.log(`contracts were found at basiccoinmgr=${managerAddress}, basiccoinreg=${registryAddress}, tokenreg=${registryAddress}`);
console.log(`contracts were found at basiccoinmgr=${managerAddress}, basiccoinreg=${registryAddress}, tokenreg=${tokenregAddress}`);
managerInstance = api.newContract(abis.basiccoinmanager, managerAddress).instance;
registryInstance = api.newContract(abis.tokenreg, registryAddress).instance;

View File

@ -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}`);

View File

@ -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);

View File

@ -54,7 +54,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
};
const values = [
sha3(name),
sha3.text(name),
key,
value
];

View File

@ -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

View File

@ -40,7 +40,7 @@ export default class CreateAccount extends Component {
accountNameError: ERRORS.noName,
accounts: null,
isValidName: false,
isValidPass: false,
isValidPass: true,
passwordHint: '',
password1: '',
password1Error: null,

View File

@ -37,7 +37,7 @@ export default class NewImport extends Component {
accountName: '',
accountNameError: ERRORS.noName,
isValidFile: false,
isValidPass: false,
isValidPass: true,
isValidName: false,
password: '',
passwordError: null,

View File

@ -36,7 +36,7 @@ export default class RawKey extends Component {
accountNameError: ERRORS.noName,
isValidKey: false,
isValidName: false,
isValidPass: false,
isValidPass: true,
passwordHint: '',
password1: '',
password1Error: null,
@ -119,8 +119,6 @@ export default class RawKey extends Component {
const rawKey = event.target.value;
let rawKeyError = null;
console.log(rawKey.length, rawKey);
if (!rawKey || !rawKey.trim().length) {
rawKeyError = ERRORS.noKey;
} else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !api.util.isHex(rawKey)) {

View File

@ -31,9 +31,9 @@ export default class RecoveryPhrase extends Component {
state = {
accountName: '',
accountNameError: ERRORS.noName,
isValidPass: false,
isValidPass: true,
isValidName: false,
isValidPhrase: false,
isValidPhrase: true,
passwordHint: '',
password1: '',
password1Error: null,

View File

@ -240,6 +240,7 @@ export default class CreateAccount extends Component {
if (createType === 'fromNew' || createType === 'fromPhrase') {
let phrase = this.state.phrase;
if (createType === 'fromPhrase' && windowsPhrase) {
phrase = phrase
.split(' ') // get the words
@ -271,7 +272,9 @@ export default class CreateAccount extends Component {
this.newError(error);
});
} else if (createType === 'fromRaw') {
}
if (createType === 'fromRaw') {
return api.parity
.newAccountFromSecret(this.state.rawKey, this.state.password)
.then((address) => {
@ -296,7 +299,9 @@ export default class CreateAccount extends Component {
this.newError(error);
});
} else if (createType === 'fromGeth') {
}
if (createType === 'fromGeth') {
return api.parity
.importGethAccounts(this.state.gethAddresses)
.then((result) => {

View File

@ -441,9 +441,7 @@ class DeployContract extends Component {
}
onCodeChange = (code) => {
const { api } = this.context;
this.setState(validateCode(code, api), this.estimateGas);
this.setState(validateCode(code), this.estimateGas);
}
onDeployStart = () => {
@ -457,10 +455,15 @@ class DeployContract extends Component {
this.setState({ step: 'DEPLOYMENT' });
api
.newContract(abiParsed)
const contract = api.newContract(abiParsed);
contract
.deploy(options, params, this.onDeploymentState)
.then((address) => {
const blockNumber = contract._receipt
? contract.receipt.blockNumber.toNumber()
: null;
return Promise.all([
api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, {
@ -468,8 +471,9 @@ class DeployContract extends Component {
contract: true,
timestamp: Date.now(),
deleted: false,
source,
description
blockNumber,
description,
source
})
])
.then(() => {

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { mount } from 'enzyme';
import { shallow } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import { ContextProvider, muiTheme } from '~/ui';
import DetailsStep from './';
import { createApi, STORE, CONTRACT } from '../executeContract.test.js';
import { CONTRACT } from '../executeContract.test.js';
let component;
let onAmountChange;
@ -40,8 +38,7 @@ function render (props) {
onGasEditClick = sinon.stub();
onValueChange = sinon.stub();
component = mount(
<ContextProvider api={ createApi() } muiTheme={ muiTheme } store={ STORE }>
component = shallow(
<DetailsStep
{ ...props }
contract={ CONTRACT }
@ -51,7 +48,6 @@ function render (props) {
onFuncChange={ onFuncChange }
onGasEditClick={ onGasEditClick }
onValueChange={ onValueChange } />
</ContextProvider>
);
return component;
@ -74,7 +70,7 @@ describe('modals/ExecuteContract/DetailsStep', () => {
describe('bool parameters', () => {
it('toggles from false to true', () => {
component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } });
component.find('TypedInput').last().shallow().simulate('change', { target: { value: 'true' } });
expect(onValueChange).to.have.been.calledWith(null, 0, true);
});

View File

@ -59,19 +59,22 @@ const STORE = {
},
settings: {
backgroundSeed: ''
},
registry: {
reverse: {}
}
};
}
};
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 }
};
}

View File

@ -133,7 +133,7 @@ export default class Store {
}
testPassword = (password) => {
this.setBusy(false);
this.setBusy(true);
return this._api.parity
.testPassword(this.address, password || this.validatePassword)

View File

@ -14,25 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import Value from '../Value';
import styles from '../shapeshift.css';
@observer
export default class AwaitingDepositStep extends Component {
static propTypes = {
coinSymbol: PropTypes.string.isRequired,
depositAddress: PropTypes.string,
price: PropTypes.shape({
rate: PropTypes.number.isRequired,
minimum: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired
}).isRequired
store: PropTypes.object.isRequired
}
render () {
const { coinSymbol, depositAddress, price } = this.props;
const { coinSymbol, depositAddress, price } = this.props.store;
const typeSymbol = (
<div className={ styles.symbol }>
{ coinSymbol }
@ -43,22 +39,38 @@ export default class AwaitingDepositStep extends Component {
return (
<div className={ styles.center }>
<div className={ styles.busy }>
Awaiting confirmation of the deposit address for your { typeSymbol } funds exchange
<FormattedMessage
id='shapeshift.awaitingDepositStep.awaitingConfirmation'
defaultMessage='Awaiting confirmation of the deposit address for your {typeSymbol} funds exchange'
values={ { typeSymbol } } />
</div>
</div>
);
}
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> is awaiting a { typeSymbol } deposit. Send the funds from your { typeSymbol } network client to -
<FormattedMessage
id='shapeshift.awaitingDepositStep.awaitingDeposit'
defaultMessage='{shapeshiftLink} is awaiting a {typeSymbol} deposit. Send the funds from your {typeSymbol} network client to -'
values={ {
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>,
typeSymbol
} } />
</div>
<div className={ styles.hero }>
{ depositAddress }
</div>
<div className={ styles.price }>
<div>
(<Value amount={ price.minimum } symbol={ coinSymbol } /> minimum, <Value amount={ price.limit } symbol={ coinSymbol } /> maximum)
<FormattedMessage
id='shapeshift.awaitingDepositStep.minimumMaximum'
defaultMessage='{minimum} minimum, {maximum} maximum'
values={ {
maximum: <Value amount={ price.limit } symbol={ coinSymbol } />,
minimum: <Value amount={ price.minimum } symbol={ coinSymbol } />
} } />
</div>
</div>
</div>

View File

@ -0,0 +1,50 @@
// 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 AwaitingDepositStep from './';
let component;
function render () {
component = shallow(
<AwaitingDepositStep
store={ {
coinSymbol: 'BTC',
price: { rate: 0.001, minimum: 0, limit: 1.999 }
} } />
);
return component;
}
describe('modals/Shapeshift/AwaitingDepositStep', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
it('displays waiting for address with empty depositAddress', () => {
render();
expect(component.find('FormattedMessage').props().id).to.match(/awaitingConfirmation/);
});
it('displays waiting for deposit with non-empty depositAddress', () => {
render({ depositAddress: 'xyz' });
expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/);
});
});

View File

@ -15,32 +15,39 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Value from '../Value';
import { FormattedMessage } from 'react-intl';
import { observer } from 'mobx-react';
import Value from '../Value';
import styles from '../shapeshift.css';
@observer
export default class AwaitingExchangeStep extends Component {
static propTypes = {
depositInfo: PropTypes.shape({
incomingCoin: PropTypes.number.isRequired,
incomingType: PropTypes.string.isRequired
}).isRequired
store: PropTypes.object.isRequired
}
render () {
const { depositInfo } = this.props;
const { depositInfo } = this.props.store;
const { incomingCoin, incomingType } = depositInfo;
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> has received a deposit of -
<FormattedMessage
id='shapeshift.awaitingExchangeStep.receivedInfo'
defaultMessage='{shapeshiftLink} has received a deposit of -'
values={ {
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
} } />
</div>
<div className={ styles.hero }>
<Value amount={ incomingCoin } symbol={ incomingType } />
</div>
<div className={ styles.info }>
Awaiting the completion of the funds exchange and transfer of funds to your Parity account.
<FormattedMessage
id='shapeshift.awaitingExchangeStep.awaitingCompletion'
defaultMessage='Awaiting the completion of the funds exchange and transfer of funds to your Parity account.' />
</div>
</div>
);

View File

@ -0,0 +1,39 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import AwaitingExchangeStep from './';
let component;
function render () {
component = shallow(
<AwaitingExchangeStep
store={ {
depositInfo: { incomingCoin: 0.01, incomingType: 'BTC' }
} } />
);
return component;
}
describe('modals/Shapeshift/AwaitingExchangeStep', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -14,39 +14,41 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import Value from '../Value';
import styles from '../shapeshift.css';
@observer
export default class CompletedStep extends Component {
static propTypes = {
depositInfo: PropTypes.shape({
incomingCoin: PropTypes.number.isRequired,
incomingType: PropTypes.string.isRequired
}).isRequired,
exchangeInfo: PropTypes.shape({
outgoingCoin: PropTypes.string.isRequired,
outgoingType: PropTypes.string.isRequired
}).isRequired
store: PropTypes.object.isRequired
}
render () {
const { depositInfo, exchangeInfo } = this.props;
const { depositInfo, exchangeInfo } = this.props.store;
const { incomingCoin, incomingType } = depositInfo;
const { outgoingCoin, outgoingType } = exchangeInfo;
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> has completed the funds exchange.
<FormattedMessage
id='shapeshift.completedStep.completed'
defaultMessage='{shapeshiftLink} has completed the funds exchange.'
values={ {
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
} } />
</div>
<div className={ styles.hero }>
<Value amount={ incomingCoin } symbol={ incomingType } /> => <Value amount={ outgoingCoin } symbol={ outgoingType } />
</div>
<div className={ styles.info }>
The change in funds will be reflected in your Parity account shortly.
<FormattedMessage
id='shapeshift.completedStep.parityFunds'
defaultMessage='The change in funds will be reflected in your Parity account shortly.' />
</div>
</div>
);

View File

@ -0,0 +1,40 @@
// 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 CompletedStep from './';
let component;
function render () {
component = shallow(
<CompletedStep
store={ {
depositInfo: { incomingCoin: 0.01, incomingType: 'BTC' },
exchangeInfo: { outgoingCoin: 0.1, outgoingType: 'ETH' }
} } />
);
return component;
}
describe('modals/Shapeshift/CompletedStep', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -14,25 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import styles from '../shapeshift.css';
@observer
export default class ErrorStep extends Component {
static propTypes = {
error: PropTypes.shape({
fatal: PropTypes.bool,
message: PropTypes.string.isRequired
}).isRequired
store: PropTypes.object.isRequired
}
render () {
const { error } = this.props;
const { error } = this.props.store;
return (
<div className={ styles.body }>
<div className={ styles.info }>
The funds shifting via <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> failed with a fatal error on the exchange. The error message received from the exchange is as follow:
<FormattedMessage
id='shapeshift.errorStep.info'
defaultMessage='The funds shifting via {shapeshiftLink} failed with a fatal error on the exchange. The error message received from the exchange is as follow:'
values={ {
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
} } />
</div>
<div className={ styles.error }>
{ error.message }

View File

@ -0,0 +1,39 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import ErrorStep from './';
let component;
function render () {
component = shallow(
<ErrorStep
store={ {
error: new Error('testing')
} } />
);
return component;
}
describe('modals/Shapeshift/ErrorStep', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -14,64 +14,93 @@
// 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 { Checkbox, MenuItem } from 'material-ui';
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Form, Input, Select } from '~/ui';
import { Form, Input, Select, Warning } from '~/ui';
import Price from '../Price';
import { WARNING_NO_PRICE } from '../store';
import styles from './optionsStep.css';
const WARNING_LABELS = {
[WARNING_NO_PRICE]: (
<FormattedMessage
id='shapeshift.warning.noPrice'
defaultMessage='No price match was found for the selected type' />
)
};
@observer
export default class OptionsStep extends Component {
static propTypes = {
refundAddress: PropTypes.string.isRequired,
coinSymbol: PropTypes.string.isRequired,
coins: PropTypes.array.isRequired,
price: PropTypes.object,
hasAccepted: PropTypes.bool.isRequired,
onChangeSymbol: PropTypes.func.isRequired,
onChangeRefund: PropTypes.func.isRequired,
onToggleAccept: PropTypes.func.isRequired
store: PropTypes.object.isRequired
};
render () {
const { coinSymbol, coins, refundAddress, hasAccepted, onToggleAccept } = this.props;
const label = `(optional) ${coinSymbol} return address`;
const { coinSymbol, coins, hasAcceptedTerms, price, refundAddress, warning } = this.props.store;
if (!coins.length) {
return (
<div className={ styles.empty }>
There are currently no exchange pairs/coins available to fund with.
<FormattedMessage
id='shapeshift.optionsStep.noPairs'
defaultMessage='There are currently no exchange pairs/coins available to fund with.' />
</div>
);
}
const items = coins.map(this.renderCoinSelectItem);
return (
<div className={ styles.body }>
<Form>
<Select
className={ styles.coinselector }
label='fund account from'
hint='the type of crypto conversion to do'
value={ coinSymbol }
onChange={ this.onSelectCoin }>
{ items }
hint={
<FormattedMessage
id='shapeshift.optionsStep.typeSelect.hint'
defaultMessage='the type of crypto conversion to do' />
}
label={
<FormattedMessage
id='shapeshift.optionsStep.typeSelect.label'
defaultMessage='fund account from' />
}
onChange={ this.onSelectCoin }
value={ coinSymbol }>
{
coins.map(this.renderCoinSelectItem)
}
</Select>
<Input
label={ label }
hint='the return address for send failures'
value={ refundAddress }
onSubmit={ this.onChangeRefund } />
hint={
<FormattedMessage
id='shapeshift.optionsStep.returnAddr.hint'
defaultMessage='the return address for send failures' />
}
label={
<FormattedMessage
id='shapeshift.optionsStep.returnAddr.label'
defaultMessage='(optional) {coinSymbol} return address'
values={ { coinSymbol } } />
}
onSubmit={ this.onChangeRefundAddress }
value={ refundAddress } />
<Checkbox
checked={ hasAcceptedTerms }
className={ styles.accept }
label='I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity'
checked={ hasAccepted }
onCheck={ onToggleAccept } />
label={
<FormattedMessage
id='shapeshift.optionsStep.terms.label'
defaultMessage='I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity' />
}
onCheck={ this.onToggleAcceptTerms } />
</Form>
<Price { ...this.props } />
<Warning warning={ WARNING_LABELS[warning] } />
<Price
coinSymbol={ coinSymbol }
price={ price } />
</div>
);
}
@ -81,7 +110,9 @@ export default class OptionsStep extends Component {
const item = (
<div className={ styles.coinselect }>
<img className={ styles.coinimage } src={ image } />
<img
className={ styles.coinimage }
src={ image } />
<div className={ styles.coindetails }>
<div className={ styles.coinsymbol }>
{ symbol }
@ -103,11 +134,15 @@ export default class OptionsStep extends Component {
);
}
onSelectCoin = (event, idx, value) => {
this.props.onChangeSymbol(event, value);
onChangeRefundAddress = (event, refundAddress) => {
this.props.store.setRefundAddress(refundAddress);
}
onChangeAddress = (event, value) => {
this.props.onChangeRefund(value);
onSelectCoin = (event, index, coinSymbol) => {
this.props.store.setCoinSymbol(coinSymbol);
}
onToggleAcceptTerms = () => {
this.props.store.toggleAcceptTerms();
}
}

View File

@ -0,0 +1,126 @@
// 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 sinon from 'sinon';
import Store, { WARNING_NO_PRICE } from '../store';
import OptionsStep from './';
const ADDRESS = '0x1234567890123456789012345678901234567890';
let component;
let instance;
let store;
function render () {
store = new Store(ADDRESS);
component = shallow(
<OptionsStep store={ store } />
);
instance = component.instance();
return component;
}
describe('modals/Shapeshift/OptionsStep', () => {
beforeEach(() => {
render();
});
it('renders defaults', () => {
expect(component).to.be.ok;
});
it('renders no coins when none available', () => {
expect(component.find('FormattedMessage').props().id).to.equal('shapeshift.optionsStep.noPairs');
});
describe('components', () => {
beforeEach(() => {
store.setCoins([{ symbol: 'BTC', name: 'Bitcoin' }]);
store.toggleAcceptTerms();
});
describe('terms Checkbox', () => {
it('shows the state of store.hasAcceptedTerms', () => {
expect(component.find('Checkbox').props().checked).to.be.true;
});
});
describe('warning', () => {
let warning;
beforeEach(() => {
store.setWarning(WARNING_NO_PRICE);
warning = component.find('Warning');
});
it('shows a warning message when available', () => {
expect(warning.props().warning.props.id).to.equal('shapeshift.warning.noPrice');
});
});
});
describe('events', () => {
describe('onChangeRefundAddress', () => {
beforeEach(() => {
sinon.stub(store, 'setRefundAddress');
});
afterEach(() => {
store.setRefundAddress.restore();
});
it('sets the refundAddress on the store', () => {
instance.onChangeRefundAddress(null, 'refundAddress');
expect(store.setRefundAddress).to.have.been.calledWith('refundAddress');
});
});
describe('onSelectCoin', () => {
beforeEach(() => {
sinon.stub(store, 'setCoinSymbol');
});
afterEach(() => {
store.setCoinSymbol.restore();
});
it('sets the coinSymbol on the store', () => {
instance.onSelectCoin(null, 0, 'XMR');
expect(store.setCoinSymbol).to.have.been.calledWith('XMR');
});
});
describe('onToggleAcceptTerms', () => {
beforeEach(() => {
sinon.stub(store, 'toggleAcceptTerms');
});
afterEach(() => {
store.toggleAcceptTerms.restore();
});
it('toggles the terms on the store', () => {
instance.onToggleAcceptTerms();
expect(store.toggleAcceptTerms).to.have.been.called;
});
});
});
});

View File

@ -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 Value from '../Value';
import styles from '../shapeshift.css';
@ -42,7 +43,13 @@ export default class Price extends Component {
<Value amount={ 1 } symbol={ coinSymbol } /> = <Value amount={ price.rate } />
</div>
<div>
(<Value amount={ price.minimum } symbol={ coinSymbol } /> minimum, <Value amount={ price.limit } symbol={ coinSymbol } /> maximum)
<FormattedMessage
id='shapeshift.price.minMax'
defaultMessage='({minimum} minimum, {maximum} maximum)'
values={ {
maximum: <Value amount={ price.limit } symbol={ coinSymbol } />,
minimum: <Value amount={ price.minimum } symbol={ coinSymbol } />
} } />
</div>
</div>
);

View File

@ -0,0 +1,40 @@
// 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 Price from './';
let component;
function render (props = {}) {
component = shallow(
<Price
coinSymbol='BTC'
price={ { rate: 0.1, minimum: 0.1, limit: 0.9 } }
error={ new Error('testing') }
{ ...props } />
);
return component;
}
describe('modals/Shapeshift/Price', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -0,0 +1,36 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import Value from './';
let component;
function render (props = {}) {
component = shallow(
<Value { ...props } />
);
return component;
}
describe('modals/Shapeshift/Value', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -14,26 +14,44 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { FormattedMessage } from 'react-intl';
import shapeshiftLogo from '~/../assets/images/shapeshift-logo.png';
import { Button, IdentityIcon, Modal } from '~/ui';
import initShapeshift from '~/3rdparty/shapeshift';
import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png';
import { CancelIcon, DoneIcon } from '~/ui/Icons';
import AwaitingDepositStep from './AwaitingDepositStep';
import AwaitingExchangeStep from './AwaitingExchangeStep';
import CompletedStep from './CompletedStep';
import ErrorStep from './ErrorStep';
import OptionsStep from './OptionsStep';
import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store';
import styles from './shapeshift.css';
const shapeshift = initShapeshift();
const STAGE_NAMES = ['details', 'awaiting deposit', 'awaiting exchange', 'completed'];
const STAGE_TITLES = [
<FormattedMessage
id='shapeshift.title.details'
defaultMessage='details' />,
<FormattedMessage
id='shapeshift.title.deposit'
defaultMessage='awaiting deposit' />,
<FormattedMessage
id='shapeshift.title.exchange'
defaultMessage='awaiting exchange' />,
<FormattedMessage
id='shapeshift.title.completed'
defaultMessage='completed' />
];
const ERROR_TITLE = (
<FormattedMessage
id='shapeshift.title.error'
defaultMessage='exchange failed' />
);
@observer
export default class Shapeshift extends Component {
static contextTypes = {
store: PropTypes.object.isRequired
@ -44,46 +62,38 @@ export default class Shapeshift extends Component {
onClose: PropTypes.func
}
state = {
stage: 0,
coinSymbol: 'BTC',
coinPair: 'btc_eth',
coins: [],
depositAddress: '',
refundAddress: '',
price: null,
depositInfo: null,
exchangeInfo: null,
error: {},
hasAccepted: false,
shifting: false
}
store = new Store(this.props.address);
componentDidMount () {
this.retrieveCoins();
this.store.retrieveCoins();
}
componentWillUnmount () {
this.unsubscribe();
}
unsubscribe () {
// Unsubscribe from Shapeshit
const { depositAddress } = this.state;
shapeshift.unsubscribe(depositAddress);
this.store.unsubscribe();
}
render () {
const { error, stage } = this.state;
const { error, stage } = this.store;
return (
<Modal
actions={ this.renderDialogActions() }
current={ stage }
steps={ error.fatal ? null : STAGE_NAMES }
title={ error.fatal ? 'exchange failed' : null }
waiting={ [1, 2] }
visible>
steps={
error
? null
: STAGE_TITLES
}
title={
error
? ERROR_TITLE
: null
}
visible
waiting={ [
STAGE_WAIT_DEPOSIT,
STAGE_WAIT_EXCHANGE
] }>
{ this.renderPage() }
</Modal>
);
@ -91,7 +101,7 @@ export default class Shapeshift extends Component {
renderDialogActions () {
const { address } = this.props;
const { coins, error, stage, hasAccepted, shifting } = this.state;
const { coins, error, hasAcceptedTerms, stage } = this.store;
const logo = (
<a href='http://shapeshift.io' target='_blank' className={ styles.shapeshift }>
@ -100,12 +110,16 @@ export default class Shapeshift extends Component {
);
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
icon={ <CancelIcon /> }
label={
<FormattedMessage
id='shapeshift.button.cancel'
defaultMessage='Cancel' />
}
onClick={ this.onClose } />
);
if (error.fatal) {
if (error) {
return [
logo,
cancelBtn
@ -113,208 +127,85 @@ export default class Shapeshift extends Component {
}
switch (stage) {
case 0:
case STAGE_OPTIONS:
return [
logo,
cancelBtn,
<Button
disabled={ !coins.length || !hasAccepted || shifting }
icon={ <IdentityIcon address={ address } button /> }
label='Shift Funds'
disabled={ !coins.length || !hasAcceptedTerms }
icon={
<IdentityIcon
address={ address }
button />
}
label={
<FormattedMessage
id='shapeshift.button.shift'
defaultMessage='Shift Funds' />
}
onClick={ this.onShift } />
];
case 1:
case 2:
case STAGE_WAIT_DEPOSIT:
case STAGE_WAIT_EXCHANGE:
return [
logo,
cancelBtn
];
case 3:
case STAGE_COMPLETED:
return [
logo,
<Button
icon={ <ActionDoneAll /> }
label='Close'
icon={ <DoneIcon /> }
label={
<FormattedMessage
id='shapeshift.button.done'
defaultMessage='Close' />
}
onClick={ this.onClose } />
];
}
}
renderPage () {
const { error, stage } = this.state;
const { error, stage } = this.store;
if (error.fatal) {
if (error) {
return (
<ErrorStep error={ error } />
<ErrorStep store={ this.store } />
);
}
switch (stage) {
case 0:
case STAGE_OPTIONS:
return (
<OptionsStep
{ ...this.state }
onChangeSymbol={ this.onChangeSymbol }
onChangeRefund={ this.onChangeRefund }
onToggleAccept={ this.onToggleAccept } />
<OptionsStep store={ this.store } />
);
case 1:
case STAGE_WAIT_DEPOSIT:
return (
<AwaitingDepositStep { ...this.state } />
<AwaitingDepositStep store={ this.store } />
);
case 2:
case STAGE_WAIT_EXCHANGE:
return (
<AwaitingExchangeStep { ...this.state } />
<AwaitingExchangeStep store={ this.store } />
);
case 3:
case STAGE_COMPLETED:
return (
<CompletedStep { ...this.state } />
<CompletedStep store={ this.store } />
);
}
}
setStage (stage) {
this.setState({
stage,
error: {}
});
}
setFatalError (message) {
this.setState({
stage: 0,
error: {
fatal: true,
message
}
});
}
onClose = () => {
this.setStage(0);
this.store.setStage(STAGE_OPTIONS);
this.props.onClose && this.props.onClose();
}
onShift = () => {
const { address } = this.props;
const { coinPair, refundAddress } = this.state;
this.setState({
stage: 1,
shifting: true
});
shapeshift
.shift(address, refundAddress, coinPair)
.then((result) => {
console.log('onShift', result);
const depositAddress = result.deposit;
if (this.state.depositAddress) {
this.unsubscribe();
}
shapeshift.subscribe(depositAddress, this.onExchangeInfo);
this.setState({ depositAddress });
})
.catch((error) => {
console.error('onShift', error);
const message = `Failed to start exchange: ${error.message}`;
this.newError(new Error(message));
this.setFatalError(message);
});
}
onChangeSymbol = (event, coinSymbol) => {
const coinPair = `${coinSymbol.toLowerCase()}_eth`;
this.setState({
coinPair,
coinSymbol,
price: null
});
this.getPrice(coinPair);
}
onChangeRefund = (event, refundAddress) => {
this.setState({ refundAddress });
}
onToggleAccept = () => {
const { hasAccepted } = this.state;
this.setState({
hasAccepted: !hasAccepted
});
}
onExchangeInfo = (error, result) => {
if (error) {
console.error('onExchangeInfo', error);
if (error.fatal) {
this.setFatalError(error.message);
}
this.newError(error);
return;
}
console.log('onExchangeInfo', result.status, result);
switch (result.status) {
case 'received':
this.setState({ depositInfo: result });
this.setStage(2);
return;
case 'complete':
this.setState({ exchangeInfo: result });
this.setStage(3);
return;
}
}
getPrice (coinPair) {
shapeshift
.getMarketInfo(coinPair)
.then((price) => {
this.setState({ price });
})
.catch((error) => {
console.error('getPrice', error);
});
}
retrieveCoins () {
const { coinPair } = this.state;
shapeshift
.getCoins()
.then((_coins) => {
const coins = Object.values(_coins).filter((coin) => coin.status === 'available');
this.getPrice(coinPair);
this.setState({ coins });
})
.catch((error) => {
console.error('retrieveCoins', error);
const message = `Failed to retrieve available coins from ShapeShift.io: ${error.message}`;
this.newError(new Error(message));
this.setFatalError(message);
});
}
newError (error) {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
return this.store.shift();
}
}

View File

@ -0,0 +1,159 @@
// 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 sinon from 'sinon';
import { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store';
import Shapeshift from './';
const ADDRESS = '0x0123456789012345678901234567890123456789';
let component;
let instance;
let onClose;
function render (props = {}) {
onClose = sinon.stub();
component = shallow(
<Shapeshift
address={ ADDRESS }
onClose={ onClose }
{ ...props } />,
{ context: { store: {} } }
);
instance = component.instance();
return component;
}
describe('modals/Shapeshift', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
describe('componentDidMount', () => {
beforeEach(() => {
render();
sinon.stub(instance.store, 'retrieveCoins');
return instance.componentDidMount();
});
afterEach(() => {
instance.store.retrieveCoins.restore();
});
it('retrieves the list of coins when mounting', () => {
expect(instance.store.retrieveCoins).to.have.been.called;
});
});
describe('componentWillUnmount', () => {
beforeEach(() => {
render();
sinon.stub(instance.store, 'unsubscribe');
return instance.componentWillUnmount();
});
afterEach(() => {
instance.store.unsubscribe.restore();
});
it('removes any subscriptions when unmounting', () => {
expect(instance.store.unsubscribe).to.have.been.called;
});
});
describe('renderDialogActions', () => {
beforeEach(() => {
render();
});
describe('shift button', () => {
beforeEach(() => {
sinon.stub(instance.store, 'shift').resolves();
instance.store.setCoins(['BTC']);
instance.store.toggleAcceptTerms();
});
afterEach(() => {
instance.store.shift.restore();
});
it('disabled shift button when not accepted', () => {
instance.store.toggleAcceptTerms();
expect(shallow(instance.renderDialogActions()[2]).props().disabled).to.be.true;
});
it('shows shift button when accepted', () => {
expect(shallow(instance.renderDialogActions()[2]).props().disabled).to.be.false;
});
it('calls the shift on store when clicked', () => {
shallow(instance.renderDialogActions()[2]).simulate('touchTap');
expect(instance.store.shift).to.have.been.called;
});
});
});
describe('renderPage', () => {
beforeEach(() => {
render();
});
it('renders ErrorStep on error, passing the store', () => {
instance.store.setError('testError');
const page = instance.renderPage();
expect(page.type).to.match(/ErrorStep/);
expect(page.props.store).to.equal(instance.store);
});
it('renders OptionsStep with STAGE_OPTIONS, passing the store', () => {
instance.store.setStage(STAGE_OPTIONS);
const page = instance.renderPage();
expect(page.type).to.match(/OptionsStep/);
expect(page.props.store).to.equal(instance.store);
});
it('renders AwaitingDepositStep with STAGE_WAIT_DEPOSIT, passing the store', () => {
instance.store.setStage(STAGE_WAIT_DEPOSIT);
const page = instance.renderPage();
expect(page.type).to.match(/AwaitingDepositStep/);
expect(page.props.store).to.equal(instance.store);
});
it('renders AwaitingExchangeStep with STAGE_WAIT_EXCHANGE, passing the store', () => {
instance.store.setStage(STAGE_WAIT_EXCHANGE);
const page = instance.renderPage();
expect(page.type).to.match(/AwaitingExchangeStep/);
expect(page.props.store).to.equal(instance.store);
});
it('renders CompletedStep with STAGE_COMPLETED, passing the store', () => {
instance.store.setStage(STAGE_COMPLETED);
const page = instance.renderPage();
expect(page.type).to.match(/CompletedStep/);
expect(page.props.store).to.equal(instance.store);
});
});
});

View File

@ -0,0 +1,199 @@
// 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 { action, observable, transaction } from 'mobx';
import initShapeshift from '~/3rdparty/shapeshift';
const STAGE_OPTIONS = 0;
const STAGE_WAIT_DEPOSIT = 1;
const STAGE_WAIT_EXCHANGE = 2;
const STAGE_COMPLETED = 3;
const WARNING_NONE = 0;
const WARNING_NO_PRICE = -1;
export default class Store {
@observable address = null;
@observable coinPair = 'btc_eth';
@observable coinSymbol = 'BTC';
@observable coins = [];
@observable depositAddress = '';
@observable depositInfo = null;
@observable exchangeInfo = null;
@observable error = null;
@observable hasAcceptedTerms = false;
@observable price = null;
@observable refundAddress = '';
@observable stage = STAGE_OPTIONS;
@observable warning = 0;
constructor (address) {
this._shapeshiftApi = initShapeshift();
this.address = address;
}
@action setCoins = (coins) => {
this.coins = coins;
}
@action setCoinSymbol = (coinSymbol) => {
transaction(() => {
this.coinSymbol = coinSymbol;
this.coinPair = `${coinSymbol.toLowerCase()}_eth`;
this.price = null;
});
return this.getCoinPrice();
}
@action setDepositAddress = (depositAddress) => {
this.depositAddress = depositAddress;
}
@action setDepositInfo = (depositInfo) => {
transaction(() => {
this.depositInfo = depositInfo;
this.setStage(STAGE_WAIT_EXCHANGE);
});
}
@action setError = (error) => {
this.error = error;
}
@action setExchangeInfo = (exchangeInfo) => {
transaction(() => {
this.exchangeInfo = exchangeInfo;
this.setStage(STAGE_COMPLETED);
});
}
@action setPrice = (price) => {
transaction(() => {
this.price = price;
this.setWarning();
});
}
@action setRefundAddress = (refundAddress) => {
this.refundAddress = refundAddress;
}
@action setStage = (stage) => {
this.stage = stage;
}
@action setWarning = (warning = WARNING_NONE) => {
this.warning = warning;
}
@action toggleAcceptTerms = () => {
this.hasAcceptedTerms = !this.hasAcceptedTerms;
}
getCoinPrice () {
return this._shapeshiftApi
.getMarketInfo(this.coinPair)
.then((price) => {
this.setPrice(price);
})
.catch((error) => {
console.warn('getCoinPrice', error);
this.setWarning(WARNING_NO_PRICE);
});
}
retrieveCoins () {
return this._shapeshiftApi
.getCoins()
.then((coins) => {
this.setCoins(Object.values(coins).filter((coin) => coin.status === 'available'));
return this.getCoinPrice();
})
.catch((error) => {
console.error('retrieveCoins', error);
const message = `Failed to retrieve available coins from ShapeShift.io: ${error.message}`;
this.setError(message);
});
}
shift () {
this.setStage(STAGE_WAIT_DEPOSIT);
return this._shapeshiftApi
.shift(this.address, this.refundAddress, this.coinPair)
.then((result) => {
console.log('onShift', result);
this.setDepositAddress(result.deposit);
return this.subscribe();
})
.catch((error) => {
console.error('onShift', error);
const message = `Failed to start exchange: ${error.message}`;
this.setError(new Error(message));
});
}
onExchangeInfo = (error, result) => {
if (error) {
console.error('onExchangeInfo', error);
if (error.fatal) {
this.setError(error);
}
return;
}
console.log('onExchangeInfo', result.status, result);
switch (result.status) {
case 'received':
if (this.stage !== STAGE_WAIT_EXCHANGE) {
this.setDepositInfo(result);
}
return;
case 'complete':
if (this.stage !== STAGE_COMPLETED) {
this.setExchangeInfo(result);
}
return;
}
}
subscribe () {
return this._shapeshiftApi.subscribe(this.depositAddress, this.onExchangeInfo);
}
unsubscribe () {
return this._shapeshiftApi.unsubscribe(this.depositAddress);
}
}
export {
STAGE_COMPLETED,
STAGE_OPTIONS,
STAGE_WAIT_DEPOSIT,
STAGE_WAIT_EXCHANGE,
WARNING_NONE,
WARNING_NO_PRICE
};

View File

@ -0,0 +1,355 @@
// 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 sinon from 'sinon';
import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE, WARNING_NONE, WARNING_NO_PRICE } from './store';
const ADDRESS = '0xabcdeffdecbaabcdeffdecbaabcdeffdecbaabcdeffdecba';
describe('modals/Shapeshift/Store', () => {
let store;
beforeEach(() => {
store = new Store(ADDRESS);
});
it('stores the ETH address', () => {
expect(store.address).to.equal(ADDRESS);
});
it('defaults to BTC-ETH pair', () => {
expect(store.coinSymbol).to.equal('BTC');
expect(store.coinPair).to.equal('btc_eth');
});
it('defaults to stage STAGE_OPTIONS', () => {
expect(store.stage).to.equal(STAGE_OPTIONS);
});
it('defaults to terms not accepted', () => {
expect(store.hasAcceptedTerms).to.be.false;
});
describe('@action', () => {
describe('setCoins', () => {
it('sets the available coins', () => {
const coins = ['BTC', 'ETC', 'XMR'];
store.setCoins(coins);
expect(store.coins.peek()).to.deep.equal(coins);
});
});
describe('setCoinSymbol', () => {
beforeEach(() => {
sinon.stub(store, 'getCoinPrice');
store.setCoinSymbol('XMR');
});
afterEach(() => {
store.getCoinPrice.restore();
});
it('sets the coinSymbol', () => {
expect(store.coinSymbol).to.equal('XMR');
});
it('sets the coinPair', () => {
expect(store.coinPair).to.equal('xmr_eth');
});
it('resets the price retrieved', () => {
expect(store.price).to.be.null;
});
it('retrieves the pair price', () => {
expect(store.getCoinPrice).to.have.been.called;
});
});
describe('setDepositAddress', () => {
it('sets the depositAddress', () => {
store.setDepositAddress('testing');
expect(store.depositAddress).to.equal('testing');
});
});
describe('setDepositInfo', () => {
beforeEach(() => {
store.setDepositInfo('testing');
});
it('sets the depositInfo', () => {
expect(store.depositInfo).to.equal('testing');
});
it('sets the stage to STAGE_WAIT_EXCHANGE', () => {
expect(store.stage).to.equal(STAGE_WAIT_EXCHANGE);
});
});
describe('setError', () => {
it('sets the error', () => {
store.setError(new Error('testing'));
expect(store.error).to.match(/testing/);
});
});
describe('setExchangeInfo', () => {
beforeEach(() => {
store.setExchangeInfo('testing');
});
it('sets the exchangeInfo', () => {
expect(store.exchangeInfo).to.equal('testing');
});
it('sets the stage to STAGE_COMPLETED', () => {
expect(store.stage).to.equal(STAGE_COMPLETED);
});
});
describe('setPrice', () => {
it('sets the price', () => {
store.setPrice('testing');
expect(store.price).to.equal('testing');
});
it('clears any warnings once set', () => {
store.setWarning(-999);
store.setPrice('testing');
expect(store.warning).to.equal(WARNING_NONE);
});
});
describe('setRefundAddress', () => {
it('sets the price', () => {
store.setRefundAddress('testing');
expect(store.refundAddress).to.equal('testing');
});
});
describe('setStage', () => {
it('sets the state', () => {
store.setStage('testing');
expect(store.stage).to.equal('testing');
});
});
describe('setWarning', () => {
it('sets the warning', () => {
store.setWarning(-999);
expect(store.warning).to.equal(-999);
});
it('clears the warning with no parameters', () => {
store.setWarning(-999);
store.setWarning();
expect(store.warning).to.equal(WARNING_NONE);
});
});
describe('toggleAcceptTerms', () => {
it('changes state on hasAcceptedTerms', () => {
store.toggleAcceptTerms();
expect(store.hasAcceptedTerms).to.be.true;
});
});
});
describe('operations', () => {
describe('getCoinPrice', () => {
beforeEach(() => {
sinon.stub(store._shapeshiftApi, 'getMarketInfo').resolves('testPrice');
return store.getCoinPrice();
});
afterEach(() => {
store._shapeshiftApi.getMarketInfo.restore();
});
it('retrieves the market info from ShapeShift', () => {
expect(store._shapeshiftApi.getMarketInfo).to.have.been.calledWith('btc_eth');
});
it('stores the price retrieved', () => {
expect(store.price).to.equal('testPrice');
});
it('sets a warning on failure', () => {
store._shapeshiftApi.getMarketInfo.restore();
sinon.stub(store._shapeshiftApi, 'getMarketInfo').rejects('someError');
return store.getCoinPrice().then(() => {
expect(store.warning).to.equal(WARNING_NO_PRICE);
});
});
});
describe('retrieveCoins', () => {
beforeEach(() => {
sinon.stub(store._shapeshiftApi, 'getCoins').resolves({
BTC: { symbol: 'BTC', status: 'available' },
ETC: { symbol: 'ETC' },
XMR: { symbol: 'XMR', status: 'available' }
});
sinon.stub(store, 'getCoinPrice');
return store.retrieveCoins();
});
afterEach(() => {
store._shapeshiftApi.getCoins.restore();
store.getCoinPrice.restore();
});
it('retrieves the coins from ShapeShift', () => {
expect(store._shapeshiftApi.getCoins).to.have.been.called;
});
it('sets the available coins', () => {
expect(store.coins.peek()).to.deep.equal([
{ status: 'available', symbol: 'BTC' },
{ status: 'available', symbol: 'XMR' }
]);
});
it('retrieves the price once resolved', () => {
expect(store.getCoinPrice).to.have.been.called;
});
});
describe('shift', () => {
beforeEach(() => {
sinon.stub(store, 'subscribe').resolves();
sinon.stub(store._shapeshiftApi, 'shift').resolves({ deposit: 'depositAddress' });
store.setRefundAddress('refundAddress');
return store.shift();
});
afterEach(() => {
store.subscribe.restore();
store._shapeshiftApi.shift.restore();
});
it('moves to stage STAGE_WAIT_DEPOSIT', () => {
expect(store.stage).to.equal(STAGE_WAIT_DEPOSIT);
});
it('calls ShapeShift with the correct parameters', () => {
expect(store._shapeshiftApi.shift).to.have.been.calledWith(ADDRESS, 'refundAddress', store.coinPair);
});
it('sets the depositAddress', () => {
expect(store.depositAddress).to.equal('depositAddress');
});
it('subscribes to updates', () => {
expect(store.subscribe).to.have.been.called;
});
it('sets error when shift fails', () => {
store._shapeshiftApi.shift.restore();
sinon.stub(store._shapeshiftApi, 'shift').rejects({ message: 'testingError' });
return store.shift().then(() => {
expect(store.error).to.match(/testingError/);
});
});
});
describe('subscribe', () => {
beforeEach(() => {
sinon.stub(store._shapeshiftApi, 'subscribe');
store.setDepositAddress('depositAddress');
return store.subscribe();
});
afterEach(() => {
store._shapeshiftApi.subscribe.restore();
});
it('calls into the ShapeShift subscribe', () => {
expect(store._shapeshiftApi.subscribe).to.have.been.calledWith('depositAddress', store.onExchangeInfo);
});
describe('onExchangeInfo', () => {
it('sets the error when fatal error retrieved', () => {
store.onExchangeInfo({ fatal: true, message: 'testing' });
expect(store.error.message).to.equal('testing');
});
it('does not set the error when non-fatal error retrieved', () => {
store.onExchangeInfo({ message: 'testing' });
expect(store.error).to.be.null;
});
describe('status received', () => {
const INFO = { status: 'received' };
beforeEach(() => {
store.onExchangeInfo(null, INFO);
});
it('sets the depositInfo', () => {
expect(store.depositInfo).to.deep.equal(INFO);
});
it('only advanced depositInfo once', () => {
store.onExchangeInfo(null, Object.assign({}, INFO, { state: 'secondTime' }));
expect(store.depositInfo).to.deep.equal(INFO);
});
});
describe('status completed', () => {
const INFO = { status: 'complete' };
beforeEach(() => {
store.onExchangeInfo(null, INFO);
});
it('sets the depositInfo', () => {
expect(store.exchangeInfo).to.deep.equal(INFO);
});
it('only advanced depositInfo once', () => {
store.onExchangeInfo(null, Object.assign({}, INFO, { state: 'secondTime' }));
expect(store.exchangeInfo).to.deep.equal(INFO);
});
});
});
});
describe('unsubscribe', () => {
beforeEach(() => {
sinon.stub(store._shapeshiftApi, 'unsubscribe');
store.setDepositAddress('depositAddress');
return store.unsubscribe();
});
afterEach(() => {
store._shapeshiftApi.unsubscribe.restore();
});
it('calls into the ShapeShift unsubscribe', () => {
expect(store._shapeshiftApi.unsubscribe).to.have.been.calledWith('depositAddress');
});
});
});
});

View File

@ -25,6 +25,9 @@ import ERRORS from './errors';
import { ERROR_CODES } from '~/api/transport/error';
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
import GasPriceStore from '~/ui/GasPriceEditor/store';
import { getLogger, LOG_KEYS } from '~/config';
const log = getLogger(LOG_KEYS.TransferModalStore);
const TITLES = {
transfer: 'transfer details',
@ -332,13 +335,12 @@ export default class TransferStore {
});
}
@action recalculateGas = () => {
@action recalculateGas = (redo = true) => {
if (!this.isValid) {
this.gasStore.setGas('0');
return this.recalculate();
return this.recalculate(redo);
}
this
return this
.estimateGas()
.then((gasEst) => {
let gas = gasEst;
@ -351,76 +353,215 @@ export default class TransferStore {
this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0));
this.recalculate();
this.recalculate(redo);
});
})
.catch((error) => {
console.warn('etimateGas', error);
this.recalculate();
this.recalculate(redo);
});
}
@action recalculate = () => {
const { account } = this;
if (!account || !this.balance) {
return;
getBalance (forceSender = false) {
if (this.isWallet && !forceSender) {
return this.balance;
}
const balance = this.senders
? this.sendersBalances[this.sender]
: this.balance;
return balance;
}
getToken (tag = this.tag, forceSender = false) {
const balance = this.getBalance(forceSender);
if (!balance) {
return null;
}
const _tag = tag.toLowerCase();
const token = balance.tokens.find((b) => b.token.tag.toLowerCase() === _tag);
return token;
}
/**
* Return the balance of the selected token
* (in WEI for ETH, without formating for other tokens)
*/
getTokenBalance (tag = this.tag, forceSender = false) {
const token = this.getToken(tag, forceSender);
if (!token) {
return new BigNumber(0);
}
const value = new BigNumber(token.value || 0);
return value;
}
getTokenValue (tag = this.tag, value = this.value, inverse = false) {
const token = this.getToken(tag);
if (!token) {
return new BigNumber(0);
}
const format = token.token
? new BigNumber(token.token.format || 1)
: new BigNumber(1);
let _value;
try {
_value = new BigNumber(value || 0);
} catch (error) {
_value = new BigNumber(0);
}
if (token.token && token.token.tag.toLowerCase() === 'eth') {
if (inverse) {
return this.api.util.fromWei(_value);
}
return this.api.util.toWei(_value);
}
if (inverse) {
return _value.div(format);
}
return _value.mul(format);
}
getValues (_gasTotal) {
const gasTotal = new BigNumber(_gasTotal || 0);
const { valueAll, isEth, isWallet } = this;
if (!valueAll) {
const value = this.getTokenValue();
// If it's a token or a wallet, eth is the estimated gas,
// and value is the user input
if (!isEth || isWallet) {
return {
eth: gasTotal,
token: value
};
}
// Otherwise, eth is the sum of the gas and the user input
const totalEthValue = gasTotal.plus(value);
return {
eth: totalEthValue,
token: value
};
}
// If it's the total balance that needs to be sent, send the total balance
// if it's not a proper ETH transfer
if (!isEth || isWallet) {
const tokenBalance = this.getTokenBalance();
return {
eth: gasTotal,
token: tokenBalance
};
}
// Otherwise, substract the gas estimate
const availableEth = this.getTokenBalance('ETH');
const totalEthValue = availableEth.gt(gasTotal)
? availableEth.minus(gasTotal)
: new BigNumber(0);
return {
eth: totalEthValue.plus(gasTotal),
token: totalEthValue
};
}
getFormattedTokenValue (tokenValue) {
const token = this.getToken();
if (!token) {
return new BigNumber(0);
}
const tag = token.token && token.token.tag || '';
return this.getTokenValue(tag, tokenValue, true);
}
@action recalculate = (redo = false) => {
const { account } = this;
if (!account || !this.balance) {
return;
}
const balance = this.getBalance();
if (!balance) {
return;
}
const { tag, valueAll, isEth, isWallet } = this;
const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0));
const availableEth = new BigNumber(balance.tokens[0].value);
const ethBalance = this.getTokenBalance('ETH', true);
const tokenBalance = this.getTokenBalance();
const { eth, token } = this.getValues(gasTotal);
const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
const format = new BigNumber(senderBalance.token.format || 1);
const available = new BigNumber(senderBalance.value).div(format);
let { value, valueError } = this;
let totalEth = gasTotal;
let totalError = null;
let valueError = null;
if (valueAll) {
if (isEth && !isWallet) {
const bn = this.api.util.fromWei(availableEth.minus(gasTotal));
value = (bn.lt(0) ? new BigNumber(0.0) : bn).toString();
} else if (isEth) {
value = (available.lt(0) ? new BigNumber(0.0) : available).toString();
} else {
value = available.toString();
}
}
if (isEth && !isWallet) {
totalEth = totalEth.plus(this.api.util.toWei(value || 0));
}
if (new BigNumber(value || 0).gt(available)) {
valueError = ERRORS.largeAmount;
} else if (valueError === ERRORS.largeAmount) {
valueError = null;
}
if (totalEth.gt(availableEth)) {
if (eth.gt(ethBalance)) {
totalError = ERRORS.largeAmount;
}
if (token && token.gt(tokenBalance)) {
valueError = ERRORS.largeAmount;
}
log.debug('@recalculate', {
eth: eth.toFormat(),
token: token.toFormat(),
ethBalance: ethBalance.toFormat(),
tokenBalance: tokenBalance.toFormat(),
gasTotal: gasTotal.toFormat()
});
transaction(() => {
this.total = this.api.util.fromWei(totalEth).toFixed();
this.totalError = totalError;
this.value = value;
this.valueError = valueError;
this.gasStore.setErrorTotal(totalError);
this.gasStore.setEthValue(totalEth);
this.total = this.api.util.fromWei(eth).toFixed();
const nextValue = this.getFormattedTokenValue(token);
let prevValue;
try {
prevValue = new BigNumber(this.value || 0);
} catch (error) {
prevValue = new BigNumber(0);
}
// Change the input only if necessary
if (!nextValue.eq(prevValue)) {
this.value = nextValue.toString();
}
// Re Calculate gas once more to be sure
if (redo) {
return this.recalculateGas(false);
}
});
}
@ -485,8 +626,10 @@ export default class TransferStore {
options.gas = MAX_GAS_ESTIMATION;
}
const { token } = this.getValues(options.gas);
if (isEth && !isWallet && !forceToken) {
options.value = this.api.util.toWei(this.value || 0);
options.value = token;
options.data = this._getData(gas);
return { options, values: [] };
@ -494,7 +637,7 @@ export default class TransferStore {
if (isWallet && !forceToken) {
const to = isEth ? this.recipient : this.token.contract.address;
const value = isEth ? this.api.util.toWei(this.value || 0) : new BigNumber(0);
const value = isEth ? token : new BigNumber(0);
const values = [
to, value,
@ -506,7 +649,7 @@ export default class TransferStore {
const values = [
this.recipient,
new BigNumber(this.value || 0).mul(this.token.format).toFixed(0)
token.toFixed(0)
];
return { options, values };

View File

@ -59,7 +59,7 @@ export default class EmailVerificationStore extends VerificationStore {
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
}
requestValues = () => [ sha3(this.email) ]
requestValues = () => [ sha3.text(this.email) ]
@action setEmail = (email) => {
this.email = email;

View File

@ -120,7 +120,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 +192,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 };

View File

@ -23,6 +23,7 @@ import SignerMiddleware from './providers/signerMiddleware';
import statusMiddleware from '~/views/Status/middleware';
import CertificationsMiddleware from './providers/certifications/middleware';
import ChainMiddleware from './providers/chainMiddleware';
import RegistryMiddleware from './providers/registry/middleware';
export default function (api, browserHistory) {
const errors = new ErrorsMiddleware();
@ -32,13 +33,15 @@ export default function (api, browserHistory) {
const certifications = new CertificationsMiddleware();
const routeMiddleware = routerMiddleware(browserHistory);
const chain = new ChainMiddleware();
const registry = new RegistryMiddleware(api);
const middleware = [
settings.toMiddleware(),
signer.toMiddleware(),
errors.toMiddleware(),
certifications.toMiddleware(),
chain.toMiddleware()
chain.toMiddleware(),
registry
];
return middleware.concat(status, routeMiddleware, thunk);

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