Merge branch 'master' into on-demand-les-request
This commit is contained in:
commit
5b8a7259c1
@ -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
236
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()));
|
||||
|
@ -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({
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ethash"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -6,12 +6,14 @@
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"stepDuration": 1,
|
||||
"startStep": 2,
|
||||
"authorities" : [
|
||||
"validators": {
|
||||
"list": [
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
|
||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x0",
|
||||
|
@ -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",
|
||||
|
@ -4,12 +4,14 @@
|
||||
"tendermint": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"authorities" : [
|
||||
"validators" : {
|
||||
"list": [
|
||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x0",
|
||||
|
42
ethcore/res/validator_contract.json
Normal file
42
ethcore/res/validator_contract.json
Normal 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" }
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
218
ethcore/src/engines/validator_set/contract.rs
Normal file
218
ethcore/src/engines/validator_set/contract.rs
Normal 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);
|
||||
}
|
||||
}
|
46
ethcore/src/engines/validator_set/mod.rs
Normal file
46
ethcore/src/engines/validator_set/mod.rs
Normal 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>) {}
|
||||
}
|
68
ethcore/src/engines/validator_set/simple_list.rs
Normal file
68
ethcore/src/engines/validator_set/simple_list.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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.");
|
||||
|
@ -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 {
|
||||
|
@ -82,7 +82,7 @@ impl PriceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test] #[ignore]
|
||||
fn should_get_price_info() {
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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") }
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -391,6 +391,14 @@ impl Res {
|
||||
Res::Call(_) | Res::FailedCall(_) | Res::FailedCreate(_) | Res::None => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Did this call fail?
|
||||
pub fn succeeded(&self) -> bool {
|
||||
match *self {
|
||||
Res::Call(_) | Res::Create(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -561,4 +569,3 @@ impl Decodable for VMTrace {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
//! Transaction data structure.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::cell::*;
|
||||
use rlp::*;
|
||||
use util::sha3::Hashable;
|
||||
@ -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();
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "evmjit"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
|
@ -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]
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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) => {
|
||||
|
@ -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/);
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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' });
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,10 @@ export function isFunction (test) {
|
||||
}
|
||||
|
||||
export function isHex (_test) {
|
||||
if (!isString(_test)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_test.substr(0, 2) === '0x') {
|
||||
return isHex(_test.slice(2));
|
||||
}
|
||||
|
@ -66,6 +66,12 @@ describe('api/util/types', () => {
|
||||
it('correctly identifies non-hex values', () => {
|
||||
expect(isHex('123j')).to.be.false;
|
||||
});
|
||||
|
||||
it('correctly indentifies non-string values', () => {
|
||||
expect(isHex(false)).to.be.false;
|
||||
expect(isHex()).to.be.false;
|
||||
expect(isHex([1, 2, 3])).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInstanceOf', () => {
|
||||
|
28
js/src/config.js
Normal file
28
js/src/config.js
Normal 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);
|
||||
};
|
@ -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;
|
||||
|
@ -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']);
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -39,7 +39,7 @@ export const lookup = (name, key) => (dispatch, getState) => {
|
||||
name = name.toLowerCase();
|
||||
dispatch(lookupStart(name, key));
|
||||
|
||||
getAddress.call({}, [ sha3(name), key ])
|
||||
getAddress.call({}, [ sha3.text(name), key ])
|
||||
.then((address) => dispatch(success('lookup', address)))
|
||||
.catch((err) => {
|
||||
console.error(`could not lookup ${key} for ${name}`);
|
||||
|
@ -62,7 +62,7 @@ export const reserve = (name) => (dispatch, getState) => {
|
||||
value: fee
|
||||
};
|
||||
const values = [
|
||||
sha3(name)
|
||||
sha3.text(name)
|
||||
];
|
||||
|
||||
return postTx(api, reserve, options, values);
|
||||
@ -116,7 +116,7 @@ export const drop = (name) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const values = [
|
||||
sha3(name)
|
||||
sha3.text(name)
|
||||
];
|
||||
|
||||
return postTx(api, drop, options, values);
|
||||
|
@ -54,7 +54,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
const values = [
|
||||
sha3(name),
|
||||
sha3.text(name),
|
||||
key,
|
||||
value
|
||||
];
|
||||
|
@ -17,7 +17,7 @@
|
||||
export const getOwner = (contract, name) => {
|
||||
const { address, api } = contract;
|
||||
|
||||
const key = api.util.sha3(name) + '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const key = api.util.sha3.text(name) + '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const position = api.util.sha3(key, { encoding: 'hex' });
|
||||
|
||||
return api
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)) {
|
||||
|
@ -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,
|
||||
|
@ -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) => {
|
||||
|
@ -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(() => {
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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 }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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/);
|
||||
});
|
||||
});
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
});
|
||||
});
|
@ -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>
|
||||
);
|
||||
|
40
js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js
Normal file
40
js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js
Normal 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;
|
||||
});
|
||||
});
|
@ -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 }
|
||||
|
39
js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js
Normal file
39
js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js
Normal 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;
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
126
js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js
Normal file
126
js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js
Normal 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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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>
|
||||
);
|
||||
|
40
js/src/modals/Shapeshift/Price/price.spec.js
Normal file
40
js/src/modals/Shapeshift/Price/price.spec.js
Normal 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;
|
||||
});
|
||||
});
|
36
js/src/modals/Shapeshift/Value/value.spec.js
Normal file
36
js/src/modals/Shapeshift/Value/value.spec.js
Normal 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;
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
159
js/src/modals/Shapeshift/shapeshift.spec.js
Normal file
159
js/src/modals/Shapeshift/shapeshift.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
199
js/src/modals/Shapeshift/store.js
Normal file
199
js/src/modals/Shapeshift/store.js
Normal 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
|
||||
};
|
355
js/src/modals/Shapeshift/store.spec.js
Normal file
355
js/src/modals/Shapeshift/store.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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 };
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user