Merge branch 'master' into better-timeouts

This commit is contained in:
Robert Habermeier 2017-01-11 18:47:09 +01:00
commit 7b3c648d3e
350 changed files with 11661 additions and 4103 deletions

View File

@ -38,7 +38,8 @@ linux-stable:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
tags:
- rust
- rust-stable
@ -106,7 +107,8 @@ linux-centos:
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-centos
@ -144,7 +146,8 @@ linux-i686:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-i686
@ -189,7 +192,8 @@ linux-armv7:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
@ -234,7 +238,8 @@ linux-arm:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
@ -272,7 +277,8 @@ linux-armv6:
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
@ -316,7 +322,8 @@ linux-aarch64:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
@ -352,7 +359,8 @@ darwin:
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- osx
artifacts:
@ -413,7 +421,8 @@ windows:
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://icarus.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
# - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
tags:
- rust-windows
artifacts:
@ -526,6 +535,7 @@ push-release:
- triggers
image: ethcore/rust:stable
script:
- curl --data "secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
# - curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
tags:
- curl

236
Cargo.lock generated
View File

@ -1,6 +1,6 @@
[root]
name = "parity"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -9,22 +9,22 @@ dependencies = [
"daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.5.0",
"ethcore-dapps 1.5.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore 1.6.0",
"ethcore-dapps 1.6.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-hypervisor 1.2.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-ipc-tests 0.1.0",
"ethcore-light 1.5.0",
"ethcore-logger 1.5.0",
"ethcore-rpc 1.5.0",
"ethcore-signer 1.5.0",
"ethcore-stratum 1.5.0",
"ethcore-util 1.5.0",
"ethsync 1.5.0",
"ethcore-light 1.6.0",
"ethcore-logger 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-signer 1.6.0",
"ethcore-stratum 1.6.0",
"ethcore-util 1.6.0",
"ethsync 1.6.0",
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -32,10 +32,10 @@ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.5.0",
"parity-hash-fetch 1.6.0",
"parity-reactor 0.1.0",
"parity-rpc-client 1.4.0",
"parity-updater 1.5.0",
"parity-updater 1.6.0",
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -336,7 +336,7 @@ dependencies = [
[[package]]
name = "ethash"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -346,7 +346,7 @@ dependencies = [
[[package]]
name = "ethcore"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -355,18 +355,18 @@ dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethash 1.5.0",
"ethash 1.6.0",
"ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.0",
"ethjson 0.1.0",
"ethkey 0.2.0",
"ethstore 0.1.0",
"evmjit 1.5.0",
"evmjit 1.6.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -403,13 +403,13 @@ dependencies = [
[[package]]
name = "ethcore-dapps"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.5.0",
"ethcore-rpc 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-util 1.6.0",
"fetch 0.1.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
@ -420,9 +420,9 @@ dependencies = [
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.5.0",
"parity-hash-fetch 1.6.0",
"parity-reactor 0.1.0",
"parity-ui 1.5.0",
"parity-ui 1.6.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -436,14 +436,14 @@ dependencies = [
[[package]]
name = "ethcore-devtools"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-io"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -454,17 +454,17 @@ dependencies = [
[[package]]
name = "ethcore-ipc"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore-devtools 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-util 1.6.0",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-ipc-codegen"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -477,9 +477,9 @@ dependencies = [
name = "ethcore-ipc-hypervisor"
version = "1.2.0"
dependencies = [
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -488,9 +488,9 @@ dependencies = [
[[package]]
name = "ethcore-ipc-nano"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore-ipc 1.5.0",
"ethcore-ipc 1.6.0",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
@ -500,11 +500,11 @@ dependencies = [
name = "ethcore-ipc-tests"
version = "0.1.0"
dependencies = [
"ethcore-devtools 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-ipc-nano 1.5.0",
"ethcore-util 1.5.0",
"ethcore-devtools 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -512,14 +512,14 @@ dependencies = [
[[package]]
name = "ethcore-light"
version = "1.5.0"
version = "1.6.0"
dependencies = [
"ethcore 1.5.0",
"ethcore-io 1.5.0",
"ethcore-ipc 1.5.0",
"ethcore-ipc-codegen 1.5.0",
"ethcore-network 1.5.0",
"ethcore-util 1.5.0",
"ethcore 1.6.0",
"ethcore-io 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-network 1.6.0",
"ethcore-util 1.6.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
"smallvec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -528,10 +528,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)",
@ -541,13 +541,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)",
@ -566,20 +566,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)",
@ -588,7 +588,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)",
@ -602,18 +602,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)",
@ -621,14 +621,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)",
@ -639,7 +639,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)",
@ -649,7 +649,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)",
@ -688,7 +688,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)",
@ -732,19 +732,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)",
@ -757,7 +757,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)",
]
@ -919,11 +919,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)",
]
@ -1440,10 +1440,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)",
@ -1466,9 +1466,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)",
@ -1484,7 +1484,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)",
@ -1501,24 +1501,24 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#d95a7dd2cc7469dc58af77743ec3ebc65e51cf36"
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)",
]
@ -1793,8 +1793,8 @@ name = "rpc-cli"
version = "1.4.0"
dependencies = [
"ethcore-bigint 0.1.2",
"ethcore-rpc 1.5.0",
"ethcore-util 1.5.0",
"ethcore-rpc 1.6.0",
"ethcore-util 1.6.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-rpc-client 1.4.0",
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
@ -43,7 +44,8 @@ pub fn utils() -> Box<Endpoint> {
}
pub fn all_endpoints<F: Fetch>(
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
@ -51,6 +53,13 @@ pub fn all_endpoints<F: Fetch>(
) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -150,6 +150,11 @@ impl AccountProvider {
Ok(Address::from(address).into())
}
/// Checks whether an account with a given address is present.
pub fn has_account(&self, address: Address) -> Result<bool, Error> {
Ok(self.accounts()?.iter().any(|&a| a == address))
}
/// Returns addresses of all accounts.
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
let accounts = self.sstore.accounts()?;

View File

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

View File

@ -378,7 +378,8 @@ impl BlockProvider for BlockChain {
.enumerate()
.flat_map(move |(index, (mut logs, tx_hash))| {
let current_log_index = log_index;
log_index -= logs.len();
let no_of_logs = logs.len();
log_index -= no_of_logs;
logs.reverse();
logs.into_iter()
@ -390,6 +391,7 @@ impl BlockProvider for BlockChain {
transaction_hash: tx_hash,
// iterating in reverse order
transaction_index: receipts_len - index - 1,
transaction_log_index: no_of_logs - i - 1,
log_index: current_log_index - i - 1,
})
})
@ -1936,6 +1938,7 @@ mod tests {
block_number: block1.header().number(),
transaction_hash: tx_hash1.clone(),
transaction_index: 0,
transaction_log_index: 0,
log_index: 0,
},
LocalizedLogEntry {
@ -1944,6 +1947,7 @@ mod tests {
block_number: block1.header().number(),
transaction_hash: tx_hash1.clone(),
transaction_index: 0,
transaction_log_index: 1,
log_index: 1,
},
LocalizedLogEntry {
@ -1952,6 +1956,7 @@ mod tests {
block_number: block1.header().number(),
transaction_hash: tx_hash2.clone(),
transaction_index: 1,
transaction_log_index: 0,
log_index: 2,
},
LocalizedLogEntry {
@ -1960,6 +1965,7 @@ mod tests {
block_number: block2.header().number(),
transaction_hash: tx_hash3.clone(),
transaction_index: 0,
transaction_log_index: 0,
log_index: 0,
}
]);
@ -1970,6 +1976,7 @@ mod tests {
block_number: block2.header().number(),
transaction_hash: tx_hash3.clone(),
transaction_index: 0,
transaction_log_index: 0,
log_index: 0,
}
]);

View File

@ -53,13 +53,13 @@ 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;
use env_info::EnvInfo;
use executive::{Executive, Executed, TransactOptions, contract_address};
use receipt::LocalizedReceipt;
use receipt::{Receipt, LocalizedReceipt};
use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase};
use trace;
use trace::FlatTransactionTraces;
@ -837,7 +837,6 @@ impl snapshot::DatabaseRestore for Client {
}
}
impl BlockChainClient for Client {
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError> {
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
@ -849,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)?;
@ -874,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)?;
@ -1134,53 +1208,23 @@ impl BlockChainClient for Client {
let chain = self.chain.read();
self.transaction_address(id)
.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| {
let t = chain.block_body(&address.block_hash)
.and_then(|body| {
body.view().localized_transaction_at(&address.block_hash, block_number, address.index)
});
let transaction = chain.block_body(&address.block_hash)
.and_then(|body| body.view().localized_transaction_at(&address.block_hash, block_number, address.index));
let tx_and_sender = t.and_then(|tx| tx.sender().ok().map(|sender| (tx, sender)));
match (tx_and_sender, chain.transaction_receipt(&address)) {
(Some((tx, sender)), Some(receipt)) => {
let block_hash = tx.block_hash.clone();
let block_number = tx.block_number.clone();
let transaction_hash = tx.hash();
let transaction_index = tx.transaction_index;
let prior_gas_used = match tx.transaction_index {
0 => U256::zero(),
i => {
let prior_address = TransactionAddress { block_hash: address.block_hash, index: i - 1 };
let prior_receipt = chain.transaction_receipt(&prior_address).expect("Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed");
prior_receipt.gas_used
}
};
Some(LocalizedReceipt {
transaction_hash: tx.hash(),
transaction_index: tx.transaction_index,
block_hash: tx.block_hash,
block_number: tx.block_number,
cumulative_gas_used: receipt.gas_used,
gas_used: receipt.gas_used - prior_gas_used,
contract_address: match tx.action {
Action::Call(_) => None,
Action::Create => Some(contract_address(&sender, &tx.nonce))
},
logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry {
entry: log,
block_hash: block_hash.clone(),
block_number: block_number,
transaction_hash: transaction_hash.clone(),
transaction_index: transaction_index,
log_index: i
}).collect(),
log_bloom: receipt.log_bloom,
state_root: receipt.state_root,
let previous_receipts = (0..address.index + 1)
.map(|index| {
let mut address = address.clone();
address.index = index;
chain.transaction_receipt(&address)
})
},
_ => None
}
}))
.collect();
match (transaction, previous_receipts) {
(Some(transaction), Some(previous_receipts)) => {
Some(transaction_receipt(transaction, previous_receipts))
},
_ => None,
}
}))
}
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> {
@ -1346,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())
}
@ -1445,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(
@ -1502,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);
@ -1535,6 +1580,49 @@ impl Drop for Client {
}
}
/// Returns `LocalizedReceipt` given `LocalizedTransaction`
/// and a vector of receipts from given block up to transaction index.
fn transaction_receipt(tx: LocalizedTransaction, mut receipts: Vec<Receipt>) -> LocalizedReceipt {
assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided.");
let sender = tx.sender()
.expect("LocalizedTransaction is part of the blockchain; We have only valid transactions in chain; qed");
let receipt = receipts.pop().expect("Current receipt is provided; qed");
let prior_gas_used = match tx.transaction_index {
0 => 0.into(),
i => receipts.get(i - 1).expect("All previous receipts are provided; qed").gas_used,
};
let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::<usize>();
let transaction_hash = tx.hash();
let block_hash = tx.block_hash;
let block_number = tx.block_number;
let transaction_index = tx.transaction_index;
LocalizedReceipt {
transaction_hash: transaction_hash,
transaction_index: transaction_index,
block_hash: block_hash,
block_number:block_number,
cumulative_gas_used: receipt.gas_used,
gas_used: receipt.gas_used - prior_gas_used,
contract_address: match tx.action {
Action::Call(_) => None,
Action::Create => Some(contract_address(&sender, &tx.nonce))
},
logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry {
entry: log,
block_hash: block_hash,
block_number: block_number,
transaction_hash: transaction_hash,
transaction_index: transaction_index,
transaction_log_index: i,
log_index: no_of_logs + i,
}).collect(),
log_bloom: receipt.log_bloom,
state_root: receipt.state_root,
}
}
#[cfg(test)]
mod tests {
@ -1570,4 +1658,91 @@ mod tests {
assert!(client.tree_route(&genesis, &new_hash).is_none());
}
#[test]
fn should_return_correct_log_index() {
use super::transaction_receipt;
use ethkey::KeyPair;
use log_entry::{LogEntry, LocalizedLogEntry};
use receipt::{Receipt, LocalizedReceipt};
use transaction::{Transaction, LocalizedTransaction, Action};
use util::Hashable;
// given
let key = KeyPair::from_secret("test".sha3()).unwrap();
let secret = key.secret();
let block_number = 1;
let block_hash = 5.into();
let state_root = 99.into();
let gas_used = 10.into();
let raw_tx = Transaction {
nonce: 0.into(),
gas_price: 0.into(),
gas: 21000.into(),
action: Action::Call(10.into()),
value: 0.into(),
data: vec![],
};
let tx1 = raw_tx.clone().sign(secret, None);
let transaction = LocalizedTransaction {
signed: tx1.clone(),
block_number: block_number,
block_hash: block_hash,
transaction_index: 1,
};
let logs = vec![LogEntry {
address: 5.into(),
topics: vec![],
data: vec![],
}, LogEntry {
address: 15.into(),
topics: vec![],
data: vec![],
}];
let receipts = vec![Receipt {
state_root: state_root,
gas_used: 5.into(),
log_bloom: Default::default(),
logs: vec![logs[0].clone()],
}, Receipt {
state_root: state_root,
gas_used: gas_used,
log_bloom: Default::default(),
logs: logs.clone(),
}];
// when
let receipt = transaction_receipt(transaction, receipts);
// then
assert_eq!(receipt, LocalizedReceipt {
transaction_hash: tx1.hash(),
transaction_index: 1,
block_hash: block_hash,
block_number: block_number,
cumulative_gas_used: gas_used,
gas_used: gas_used - 5.into(),
contract_address: None,
logs: vec![LocalizedLogEntry {
entry: logs[0].clone(),
block_hash: block_hash,
block_number: block_number,
transaction_hash: tx1.hash(),
transaction_index: 1,
transaction_log_index: 0,
log_index: 1,
}, LocalizedLogEntry {
entry: logs[1].clone(),
block_hash: block_hash,
block_number: block_number,
transaction_hash: tx1.hash(),
transaction_index: 1,
transaction_log_index: 1,
log_index: 2,
}],
log_bloom: Default::default(),
state_root: state_root,
});
}
}

View File

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

View File

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

View File

@ -184,6 +184,9 @@ pub trait BlockChainClient : Sync + Send {
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError>;
/// Estimates how much gas will be necessary for a call.
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError>;
/// Replays a given transaction for inspection.
fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError>;
@ -208,9 +211,6 @@ pub trait BlockChainClient : Sync + Send {
/// Queue conensus engine message.
fn queue_consensus_message(&self, message: Bytes);
/// Used by PoA to communicate with peers.
fn broadcast_consensus_message(&self, message: Bytes);
/// List all transactions that are allowed into the next block.
fn ready_transactions(&self) -> Vec<PendingTransaction>;
@ -294,12 +294,6 @@ pub trait MiningBlockChainClient: BlockChainClient {
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;
/// Used by PoA to try sealing on period change.
fn update_sealing(&self);
/// Used by PoA to submit gathered signatures.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a block proposal.
fn broadcast_proposal_block(&self, block: SealedBlock);
@ -310,6 +304,18 @@ pub trait MiningBlockChainClient: BlockChainClient {
fn latest_schedule(&self) -> Schedule;
}
/// Client facilities used by internally sealing Engines.
pub trait EngineClient: MiningBlockChainClient {
/// Make a new block and seal it.
fn update_sealing(&self);
/// Submit a seal for a block in the mining queue.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a consensus message to the network.
fn broadcast_consensus_message(&self, message: Bytes);
}
/// Extended client interface for providing proofs of the state.
pub trait ProvingBlockChainClient: BlockChainClient {
/// Prove account storage at a specific block id.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,46 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Validator lists.
mod simple_list;
mod contract;
use std::sync::Weak;
use util::{Address, Arc};
use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client;
use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))),
}
}
pub trait ValidatorSet {
/// Checks if a given address is a validator.
fn contains(&self, address: &Address) -> bool;
/// Draws an validator nonce modulo number of validators.
fn get(&self, nonce: usize) -> Address;
/// Returns the current number of validators.
fn count(&self) -> usize;
/// Allows blockchain state access.
fn register_call_contract(&self, _client: Weak<Client>) {}
}

View File

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

View File

@ -45,7 +45,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
}
/// Transaction execution options.
#[derive(Default)]
#[derive(Default, Copy, Clone, PartialEq)]
pub struct TransactOptions {
/// Enable call tracing.
pub tracing: bool,

View File

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

View File

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

View File

@ -417,15 +417,12 @@ impl Miner {
let block = open_block.close();
let fetch_account = |a: &Address| AccountDetails {
nonce: chain.latest_nonce(a),
balance: chain.latest_balance(a),
};
let fetch_nonce = |a: &Address| chain.latest_nonce(a);
{
let mut queue = self.transaction_queue.lock();
for hash in invalid_transactions {
queue.remove_invalid(&hash, &fetch_account);
queue.remove_invalid(&hash, &fetch_nonce);
}
for hash in transactions_to_penalize {
queue.penalize(&hash);
@ -597,6 +594,8 @@ impl Miner {
let schedule = chain.latest_schedule();
let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into();
let best_block_header = chain.best_block_header().decode();
let insertion_time = chain.chain_info().best_block_number;
transactions.into_iter()
.map(|tx| {
if chain.transaction_block(TransactionId::Hash(tx.hash())).is_some() {
@ -618,10 +617,10 @@ impl Miner {
match origin {
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
transaction_queue.add(tx, origin, min_block, &fetch_account, &gas_required)
transaction_queue.add(tx, origin, insertion_time, min_block, &fetch_account, &gas_required)
},
TransactionOrigin::External => {
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
transaction_queue.add_with_banlist(tx, insertion_time, &fetch_account, &gas_required)
}
}
},
@ -674,7 +673,7 @@ impl MinerService for Miner {
}
}
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
let sealing_work = self.sealing_work.lock();
match sealing_work.queue.peek_last_ref() {
Some(work) => {
@ -682,7 +681,7 @@ impl MinerService for Miner {
// TODO: merge this code with client.rs's fn call somwhow.
let header = block.header();
let last_hashes = Arc::new(chain.last_hashes());
let last_hashes = Arc::new(client.last_hashes());
let env_info = EnvInfo {
number: header.number(),
author: *header.author(),
@ -707,16 +706,14 @@ impl MinerService for Miner {
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
}
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)?;
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, client.vm_factory()).transact(t, options)?;
// TODO gav move this into Executive.
ret.state_diff = original_state.map(|original| state.diff_from(original));
Ok(ret)
},
None => {
chain.call(t, BlockId::Latest, analytics)
}
None => client.call(t, BlockId::Latest, analytics)
}
}
@ -765,9 +762,17 @@ impl MinerService for Miner {
if let Some(ref ap) = self.accounts {
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
}
let mut sealing_work = self.sealing_work.lock();
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
*self.author.write() = address;
// Limit the scope of the locks.
{
let mut sealing_work = self.sealing_work.lock();
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
*self.author.write() = address;
}
// --------------------------------------------------------------------------
// | NOTE Code below may require author and sealing_work locks |
// | (some `Engine`s call `EngineClient.update_sealing()`) |.
// | Make sure to release the locks before calling that method. |
// --------------------------------------------------------------------------
self.engine.set_signer(address, password);
}
Ok(())
@ -1141,8 +1146,13 @@ impl MinerService for Miner {
// ...and at the end remove the old ones
{
let fetch_account = |a: &Address| AccountDetails {
nonce: chain.latest_nonce(a),
balance: chain.latest_balance(a),
};
let time = chain.chain_info().best_block_number;
let mut transaction_queue = self.transaction_queue.lock();
transaction_queue.remove_old(|sender| chain.latest_nonce(sender));
transaction_queue.remove_old(&fetch_account, time);
}
if enacted.len() > 0 {

View File

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

View File

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

View File

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

View File

@ -17,16 +17,17 @@
//! Account state encoding and decoding
use account_db::{AccountDB, AccountDBMut};
use basic_account::BasicAccount;
use snapshot::Error;
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP};
use util::trie::{TrieDB, Trie};
use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View};
use rlp::{RlpStream, Stream, UntrustedRlp, View};
use std::collections::HashSet;
// An empty account -- these are replaced with RLP null data for a space optimization.
const ACC_EMPTY: Account = Account {
const ACC_EMPTY: BasicAccount = BasicAccount {
nonce: U256([0, 0, 0, 0]),
balance: U256([0, 0, 0, 0]),
storage_root: SHA3_NULL_RLP,
@ -59,165 +60,121 @@ impl CodeState {
}
}
// An alternate account structure from ::account::Account.
#[derive(PartialEq, Clone, Debug)]
pub struct Account {
nonce: U256,
balance: U256,
storage_root: H256,
code_hash: H256,
// walk the account's storage trie, returning an RLP item containing the
// account properties and the storage.
pub fn to_fat_rlp(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
if acc == &ACC_EMPTY {
return Ok(::rlp::NULL_RLP.to_vec());
}
let db = TrieDB::new(acct_db, &acc.storage_root)?;
let mut pairs = Vec::new();
for item in db.iter()? {
let (k, v) = item?;
pairs.push((k, v));
}
let mut stream = RlpStream::new_list(pairs.len());
for (k, v) in pairs {
stream.begin_list(2).append(&k).append(&&*v);
}
let pairs_rlp = stream.out();
let mut account_stream = RlpStream::new_list(5);
account_stream.append(&acc.nonce)
.append(&acc.balance);
// [has_code, code_hash].
if acc.code_hash == SHA3_EMPTY {
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
} else if used_code.contains(&acc.code_hash) {
account_stream.append(&CodeState::Hash.raw()).append(&acc.code_hash);
} else {
match acct_db.get(&acc.code_hash) {
Some(c) => {
used_code.insert(acc.code_hash.clone());
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
}
None => {
warn!("code lookup failed during snapshot");
account_stream.append(&false).append_empty_data();
}
}
}
account_stream.append_raw(&pairs_rlp, 1);
Ok(account_stream.out())
}
impl Account {
// decode the account from rlp.
pub fn from_thin_rlp(rlp: &[u8]) -> Self {
let r: Rlp = Rlp::new(rlp);
// decode a fat rlp, and rebuild the storage trie as we go.
// returns the account structure along with its newly recovered code,
// if it exists.
pub fn from_fat_rlp(
acct_db: &mut AccountDBMut,
rlp: UntrustedRlp,
) -> Result<(BasicAccount, Option<Bytes>), Error> {
use util::{TrieDBMut, TrieMut};
Account {
nonce: r.val_at(0),
balance: r.val_at(1),
storage_root: r.val_at(2),
code_hash: r.val_at(3),
// check for special case of empty account.
if rlp.is_empty() {
return Ok((ACC_EMPTY, None));
}
let nonce = rlp.val_at(0)?;
let balance = rlp.val_at(1)?;
let code_state: CodeState = {
let raw: u8 = rlp.val_at(2)?;
CodeState::from(raw)?
};
// load the code if it exists.
let (code_hash, new_code) = match code_state {
CodeState::Empty => (SHA3_EMPTY, None),
CodeState::Inline => {
let code: Bytes = rlp.val_at(3)?;
let code_hash = acct_db.insert(&code);
(code_hash, Some(code))
}
CodeState::Hash => {
let code_hash = rlp.val_at(3)?;
(code_hash, None)
}
};
let mut storage_root = H256::zero();
{
let mut storage_trie = TrieDBMut::new(acct_db, &mut storage_root);
let pairs = rlp.at(4)?;
for pair_rlp in pairs.iter() {
let k: Bytes = pair_rlp.val_at(0)?;
let v: Bytes = pair_rlp.val_at(1)?;
storage_trie.insert(&k, &v)?;
}
}
// encode the account to a standard rlp.
pub fn to_thin_rlp(&self) -> Bytes {
let mut stream = RlpStream::new_list(4);
stream
.append(&self.nonce)
.append(&self.balance)
.append(&self.storage_root)
.append(&self.code_hash);
let acc = BasicAccount {
nonce: nonce,
balance: balance,
storage_root: storage_root,
code_hash: code_hash,
};
stream.out()
}
// walk the account's storage trie, returning an RLP item containing the
// account properties and the storage.
pub fn to_fat_rlp(&self, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
if self == &ACC_EMPTY {
return Ok(::rlp::NULL_RLP.to_vec());
}
let db = TrieDB::new(acct_db, &self.storage_root)?;
let mut pairs = Vec::new();
for item in db.iter()? {
let (k, v) = item?;
pairs.push((k, v));
}
let mut stream = RlpStream::new_list(pairs.len());
for (k, v) in pairs {
stream.begin_list(2).append(&k).append(&&*v);
}
let pairs_rlp = stream.out();
let mut account_stream = RlpStream::new_list(5);
account_stream.append(&self.nonce)
.append(&self.balance);
// [has_code, code_hash].
if self.code_hash == SHA3_EMPTY {
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
} else if used_code.contains(&self.code_hash) {
account_stream.append(&CodeState::Hash.raw()).append(&self.code_hash);
} else {
match acct_db.get(&self.code_hash) {
Some(c) => {
used_code.insert(self.code_hash.clone());
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
}
None => {
warn!("code lookup failed during snapshot");
account_stream.append(&false).append_empty_data();
}
}
}
account_stream.append_raw(&pairs_rlp, 1);
Ok(account_stream.out())
}
// decode a fat rlp, and rebuild the storage trie as we go.
// returns the account structure along with its newly recovered code,
// if it exists.
pub fn from_fat_rlp(
acct_db: &mut AccountDBMut,
rlp: UntrustedRlp,
) -> Result<(Self, Option<Bytes>), Error> {
use util::{TrieDBMut, TrieMut};
// check for special case of empty account.
if rlp.is_empty() {
return Ok((ACC_EMPTY, None));
}
let nonce = rlp.val_at(0)?;
let balance = rlp.val_at(1)?;
let code_state: CodeState = {
let raw: u8 = rlp.val_at(2)?;
CodeState::from(raw)?
};
// load the code if it exists.
let (code_hash, new_code) = match code_state {
CodeState::Empty => (SHA3_EMPTY, None),
CodeState::Inline => {
let code: Bytes = rlp.val_at(3)?;
let code_hash = acct_db.insert(&code);
(code_hash, Some(code))
}
CodeState::Hash => {
let code_hash = rlp.val_at(3)?;
(code_hash, None)
}
};
let mut storage_root = H256::zero();
{
let mut storage_trie = TrieDBMut::new(acct_db, &mut storage_root);
let pairs = rlp.at(4)?;
for pair_rlp in pairs.iter() {
let k: Bytes = pair_rlp.val_at(0)?;
let v: Bytes = pair_rlp.val_at(1)?;
storage_trie.insert(&k, &v)?;
}
}
let acc = Account {
nonce: nonce,
balance: balance,
storage_root: storage_root,
code_hash: code_hash,
};
Ok((acc, new_code))
}
/// Get the account's code hash.
pub fn code_hash(&self) -> &H256 {
&self.code_hash
}
#[cfg(test)]
pub fn storage_root_mut(&mut self) -> &mut H256 {
&mut self.storage_root
}
Ok((acc, new_code))
}
#[cfg(test)]
mod tests {
use account_db::{AccountDB, AccountDBMut};
use basic_account::BasicAccount;
use tests::helpers::get_temp_state_db;
use snapshot::tests::helpers::fill_storage;
@ -227,26 +184,26 @@ mod tests {
use std::collections::HashSet;
use super::{ACC_EMPTY, Account};
use super::{ACC_EMPTY, to_fat_rlp, from_fat_rlp};
#[test]
fn encoding_basic() {
let mut db = get_temp_state_db();
let addr = Address::random();
let account = Account {
let account = BasicAccount {
nonce: 50.into(),
balance: 123456789.into(),
storage_root: SHA3_NULL_RLP,
code_hash: SHA3_EMPTY,
};
let thin_rlp = account.to_thin_rlp();
assert_eq!(Account::from_thin_rlp(&thin_rlp), account);
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
let fat_rlp = account.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlp);
assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
}
#[test]
@ -258,7 +215,7 @@ mod tests {
let acct_db = AccountDBMut::new(db.as_hashdb_mut(), &addr);
let mut root = SHA3_NULL_RLP;
fill_storage(acct_db, &mut root, &mut H256::zero());
Account {
BasicAccount {
nonce: 25.into(),
balance: 987654321.into(),
storage_root: root,
@ -266,12 +223,12 @@ mod tests {
}
};
let thin_rlp = account.to_thin_rlp();
assert_eq!(Account::from_thin_rlp(&thin_rlp), account);
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
let fat_rlp = account.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlp);
assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
}
#[test]
@ -291,14 +248,14 @@ mod tests {
acct_db.emplace(code_hash.clone(), DBValue::from_slice(b"this is definitely code"));
}
let account1 = Account {
let account1 = BasicAccount {
nonce: 50.into(),
balance: 123456789.into(),
storage_root: SHA3_NULL_RLP,
code_hash: code_hash,
};
let account2 = Account {
let account2 = BasicAccount {
nonce: 400.into(),
balance: 98765432123456789usize.into(),
storage_root: SHA3_NULL_RLP,
@ -307,18 +264,18 @@ mod tests {
let mut used_code = HashSet::new();
let fat_rlp1 = account1.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr1), &mut used_code).unwrap();
let fat_rlp2 = account2.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr2), &mut used_code).unwrap();
let fat_rlp1 = to_fat_rlp(&account1, &AccountDB::new(db.as_hashdb(), &addr1), &mut used_code).unwrap();
let fat_rlp2 = to_fat_rlp(&account2, &AccountDB::new(db.as_hashdb(), &addr2), &mut used_code).unwrap();
assert_eq!(used_code.len(), 1);
let fat_rlp1 = UntrustedRlp::new(&fat_rlp1);
let fat_rlp2 = UntrustedRlp::new(&fat_rlp2);
let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2).unwrap();
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2).unwrap();
assert!(maybe_code.is_none());
assert_eq!(acc, account2);
let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1).unwrap();
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1).unwrap();
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
assert_eq!(acc, account1);
}
@ -328,7 +285,7 @@ mod tests {
let mut db = get_temp_state_db();
let mut used_code = HashSet::new();
assert_eq!(ACC_EMPTY.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP)).unwrap(), (ACC_EMPTY, None));
assert_eq!(to_fat_rlp(&ACC_EMPTY, &AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP)).unwrap(), (ACC_EMPTY, None));
}
}

View File

@ -40,7 +40,6 @@ use util::sha3::SHA3_NULL_RLP;
use rlp::{RlpStream, Stream, UntrustedRlp, View};
use bloom_journal::Bloom;
use self::account::Account;
use self::block::AbridgedBlock;
use self::io::SnapshotWriter;
@ -368,12 +367,12 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex<SnapshotWriter +
// account_key here is the address' hash.
for item in account_trie.iter()? {
let (account_key, account_data) = item?;
let account = Account::from_thin_rlp(&*account_data);
let account = ::rlp::decode(&*account_data);
let account_key_hash = H256::from_slice(&account_key);
let account_db = AccountDB::from_hash(db, account_key_hash);
let fat_rlp = account.to_fat_rlp(&account_db, &mut used_code)?;
let fat_rlp = account::to_fat_rlp(&account, &account_db, &mut used_code)?;
chunker.push(account_key, fat_rlp)?;
}
@ -507,10 +506,10 @@ fn rebuild_accounts(
// fill out the storage trie and code while decoding.
let (acc, maybe_code) = {
let mut acct_db = AccountDBMut::from_hash(db, hash);
Account::from_fat_rlp(&mut acct_db, fat_rlp)?
account::from_fat_rlp(&mut acct_db, fat_rlp)?
};
let code_hash = acc.code_hash().clone();
let code_hash = acc.code_hash.clone();
match maybe_code {
// new inline code
Some(code) => status.new_code.push((code_hash, code, hash)),
@ -534,7 +533,7 @@ fn rebuild_accounts(
}
}
acc.to_thin_rlp()
::rlp::encode(&acc).to_vec()
};
*out = (hash, thin_rlp);

View File

@ -17,9 +17,9 @@
//! Snapshot test helpers. These are used to build blockchains and state tries
//! which can be queried before and after a full snapshot/restore cycle.
use basic_account::BasicAccount;
use account_db::AccountDBMut;
use rand::Rng;
use snapshot::account::Account;
use util::DBValue;
use util::hash::{FixedHash, H256};
@ -64,10 +64,10 @@ impl StateProducer {
// sweep once to alter storage tries.
for &mut (ref mut address_hash, ref mut account_data) in &mut accounts_to_modify {
let mut account = Account::from_thin_rlp(&*account_data);
let mut account: BasicAccount = ::rlp::decode(&*account_data);
let acct_db = AccountDBMut::from_hash(db, *address_hash);
fill_storage(acct_db, account.storage_root_mut(), &mut self.storage_seed);
*account_data = DBValue::from_vec(account.to_thin_rlp());
fill_storage(acct_db, &mut account.storage_root, &mut self.storage_seed);
*account_data = DBValue::from_vec(::rlp::encode(&account).to_vec());
}
// sweep again to alter account trie.

View File

@ -16,8 +16,9 @@
//! State snapshotting tests.
use basic_account::BasicAccount;
use snapshot::account;
use snapshot::{chunk_state, Error as SnapshotError, Progress, StateRebuilder};
use snapshot::account::Account;
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
use super::helpers::{compare_dbs, StateProducer};
@ -113,22 +114,21 @@ fn get_code_from_prev_chunk() {
// first one will have code inlined,
// second will just have its hash.
let thin_rlp = acc_stream.out();
let acc1 = Account::from_thin_rlp(&thin_rlp);
let acc2 = Account::from_thin_rlp(&thin_rlp);
let acc: BasicAccount = ::rlp::decode(&thin_rlp);
let mut make_chunk = |acc: Account, hash| {
let mut make_chunk = |acc, hash| {
let mut db = MemoryDB::new();
AccountDBMut::from_hash(&mut db, hash).insert(&code[..]);
let fat_rlp = acc.to_fat_rlp(&AccountDB::from_hash(&db, hash), &mut used_code).unwrap();
let fat_rlp = account::to_fat_rlp(&acc, &AccountDB::from_hash(&db, hash), &mut used_code).unwrap();
let mut stream = RlpStream::new_list(1);
stream.begin_list(2).append(&hash).append_raw(&fat_rlp, 1);
stream.out()
};
let chunk1 = make_chunk(acc1, h1);
let chunk2 = make_chunk(acc2, h2);
let chunk1 = make_chunk(acc.clone(), h1);
let chunk2 = make_chunk(acc, h2);
let db_path = RandomTempPath::create_dir();
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
@ -190,4 +190,4 @@ fn checks_flag() {
}
}
}
}
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{Address, H256, Uint, U256};
use util::{Address, H256, Uint, U256, FixedHash};
use util::sha3::SHA3_NULL_RLP;
use ethjson;
use super::seal::Seal;
@ -50,9 +50,9 @@ impl From<ethjson::spec::Genesis> for Genesis {
Genesis {
seal: From::from(g.seal),
difficulty: g.difficulty.into(),
author: g.author.into(),
timestamp: g.timestamp.into(),
parent_hash: g.parent_hash.into(),
author: g.author.map_or_else(Address::zero, Into::into),
timestamp: g.timestamp.map_or(0, Into::into),
parent_hash: g.parent_hash.map_or_else(H256::zero, Into::into),
gas_limit: g.gas_limit.into(),
transactions_root: g.transactions_root.map_or_else(|| SHA3_NULL_RLP.clone(), Into::into),
receipts_root: g.receipts_root.map_or_else(|| SHA3_NULL_RLP.clone(), Into::into),

View File

@ -58,7 +58,7 @@ pub struct CommonParams {
impl From<ethjson::spec::Params> for CommonParams {
fn from(p: ethjson::spec::Params) -> Self {
CommonParams {
account_start_nonce: p.account_start_nonce.into(),
account_start_nonce: p.account_start_nonce.map_or_else(U256::zero, Into::into),
maximum_extra_data_size: p.maximum_extra_data_size.into(),
network_id: p.network_id.into(),
chain_id: if let Some(n) = p.chain_id { n.into() } else { p.network_id.into() },
@ -337,9 +337,14 @@ impl Spec {
pub fn new_instant() -> Spec { load_bundled!("instant_seal") }
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
/// Accounts with secrets "0".sha3() and "1".sha3() are the validators.
pub fn new_test_round() -> Self { load_bundled!("authority_round") }
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators.
/// Accounts with secrets "0".sha3() and "1".sha3() are initially the validators.
/// Second validator can be removed with "0xf94e18670000000000000000000000000000000000000000000000000000000000000001" and added back in using "0x4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work).
/// Account "0".sha3() and "1".sha3() are a authorities.
pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") }

View File

@ -20,6 +20,7 @@ use util::*;
use pod_account::*;
use rlp::*;
use lru_cache::LruCache;
use basic_account::BasicAccount;
use std::cell::{RefCell, Cell};
@ -53,6 +54,23 @@ pub struct Account {
address_hash: Cell<Option<H256>>,
}
impl From<BasicAccount> for Account {
fn from(basic: BasicAccount) -> Self {
Account {
balance: basic.balance,
nonce: basic.nonce,
storage_root: basic.storage_root,
storage_cache: Self::empty_storage_cache(),
storage_changes: HashMap::new(),
code_hash: basic.code_hash,
code_size: None,
code_cache: Arc::new(vec![]),
code_filth: Filth::Clean,
address_hash: Cell::new(None),
}
}
}
impl Account {
#[cfg(test)]
/// General constructor.
@ -109,19 +127,8 @@ impl Account {
/// Create a new account from RLP.
pub fn from_rlp(rlp: &[u8]) -> Account {
let r: Rlp = Rlp::new(rlp);
Account {
nonce: r.val_at(0),
balance: r.val_at(1),
storage_root: r.val_at(2),
storage_cache: Self::empty_storage_cache(),
storage_changes: HashMap::new(),
code_hash: r.val_at(3),
code_cache: Arc::new(vec![]),
code_size: None,
code_filth: Filth::Clean,
address_hash: Cell::new(None),
}
let basic: BasicAccount = ::rlp::decode(rlp);
basic.into()
}
/// Create a new contract account.
@ -171,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();
@ -446,12 +453,12 @@ impl Account {
/// omitted.
pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
use util::trie::{Trie, TrieDB};
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
use util::trie::recorder::Recorder;
let mut recorder = TrieRecorder::with_depth(from_level);
let mut recorder = Recorder::with_depth(from_level);
let trie = TrieDB::new(db, &self.storage_root)?;
let _ = trie.get_recorded(&storage_key, &mut recorder)?;
let _ = trie.get_with(&storage_key, &mut recorder)?;
Ok(recorder.drain().into_iter().map(|r| r.data).collect())
}

View File

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

View File

@ -320,4 +320,3 @@ fn does_not_propagate_delayed_transactions() {
assert_eq!(2, client.ready_transactions().len());
assert_eq!(2, client.miner().pending_transactions().len());
}

View File

@ -0,0 +1,55 @@
// 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/>.
//! Basic account type -- the decoded RLP from the state trie.
use rlp::*;
use util::{U256, H256};
/// Basic account type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicAccount {
/// Nonce of the account.
pub nonce: U256,
/// Balance of the account.
pub balance: U256,
/// Storage root of the account.
pub storage_root: H256,
/// Code hash of the account.
pub code_hash: H256,
}
impl Encodable for BasicAccount {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4)
.append(&self.nonce)
.append(&self.balance)
.append(&self.storage_root)
.append(&self.code_hash);
}
}
impl Decodable for BasicAccount {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let rlp = decoder.as_rlp();
Ok(BasicAccount {
nonce: rlp.val_at(0)?,
balance: rlp.val_at(1)?,
storage_root: rlp.val_at(2)?,
code_hash: rlp.val_at(3)?,
})
}
}

View File

@ -97,6 +97,8 @@ pub struct LocalizedLogEntry {
pub transaction_index: usize,
/// Log position in the block.
pub log_index: usize,
/// Log position in the transaction.
pub transaction_log_index: usize,
}
impl Deref for LocalizedLogEntry {

View File

@ -37,3 +37,4 @@ pub mod mode;
pub mod pruning_info;
pub mod security_level;
pub mod encoded;
pub mod basic_account;

View File

@ -391,6 +391,14 @@ impl Res {
Res::Call(_) | Res::FailedCall(_) | Res::FailedCreate(_) | Res::None => Default::default(),
}
}
/// Did this call fail?
pub fn succeeded(&self) -> bool {
match *self {
Res::Call(_) | Res::Create(_) => true,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
@ -561,4 +569,3 @@ impl Decodable for VMTrace {
Ok(res)
}
}

View File

@ -16,7 +16,7 @@
//! Transaction data structure.
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
use std::cell::*;
use rlp::*;
use util::sha3::Hashable;
@ -239,6 +239,12 @@ impl Deref for SignedTransaction {
}
}
impl DerefMut for SignedTransaction {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.unsigned
}
}
impl Decodable for SignedTransaction {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
@ -373,7 +379,7 @@ impl SignedTransaction {
}
/// Signed Transaction that is a part of canon blockchain.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "ipc", binary)]
pub struct LocalizedTransaction {
/// Signed part.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.2.152",
"version": "0.2.182",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",
@ -26,9 +26,9 @@
],
"scripts": {
"build": "npm run build:lib && npm run build:dll && npm run build:app",
"build:app": "webpack --config webpack/app --progress",
"build:lib": "webpack --config webpack/libraries --progress",
"build:dll": "webpack --config webpack/vendor --progress",
"build:app": "webpack --config webpack/app",
"build:lib": "webpack --config webpack/libraries",
"build:dll": "webpack --config webpack/vendor",
"ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app",
"ci:build:app": "NODE_ENV=production webpack --config webpack/app",
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
@ -43,27 +43,27 @@
"lint:css": "stylelint ./src/**/*.css",
"lint:js": "eslint --ignore-path .gitignore ./src/",
"lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/",
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
"test": "NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'",
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && (rm -rf .npmjs/node_modules)",
"prepush": "npm run lint:cached"
},
"devDependencies": {
"babel-cli": "6.18.0",
"babel-core": "6.20.0",
"babel-core": "6.21.0",
"babel-eslint": "7.1.1",
"babel-loader": "6.2.10",
"babel-plugin-lodash": "3.2.10",
"babel-plugin-lodash": "3.2.11",
"babel-plugin-react-intl": "2.2.0",
"babel-plugin-transform-class-properties": "6.18.0",
"babel-plugin-transform-class-properties": "6.19.0",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-object-rest-spread": "6.20.2",
"babel-plugin-transform-react-remove-prop-types": "0.2.11",
"babel-plugin-transform-runtime": "6.15.0",
"babel-plugin-webpack-alias": "2.1.2",
"babel-polyfill": "6.20.0",
"babel-preset-env": "1.0.2",
"babel-preset-env": "1.1.4",
"babel-preset-es2015": "6.18.0",
"babel-preset-es2016": "6.16.0",
"babel-preset-es2017": "6.16.0",
@ -80,57 +80,59 @@
"coveralls": "2.11.15",
"css-loader": "0.26.1",
"ejs-loader": "0.3.0",
"enzyme": "2.6.0",
"ejsify": "1.0.0",
"enzyme": "2.7.0",
"eslint": "3.11.1",
"eslint-config-semistandard": "7.0.0",
"eslint-config-standard": "6.2.1",
"eslint-config-standard-react": "4.2.0",
"eslint-plugin-promise": "3.4.0",
"eslint-plugin-react": "6.7.1",
"eslint-plugin-react": "6.8.0",
"eslint-plugin-standard": "2.0.1",
"express": "4.14.0",
"extract-loader": "0.1.0",
"extract-text-webpack-plugin": "2.0.0-beta.4",
"file-loader": "0.9.0",
"happypack": "3.0.0",
"happypack": "3.0.2",
"html-loader": "0.4.4",
"html-webpack-plugin": "2.24.1",
"http-proxy-middleware": "0.17.2",
"http-proxy-middleware": "0.17.3",
"husky": "0.11.9",
"ignore-styles": "5.0.1",
"image-webpack-loader": "3.0.0",
"image-webpack-loader": "3.1.0",
"istanbul": "1.0.0-alpha.2",
"jsdom": "9.8.3",
"jsdom": "9.9.1",
"json-loader": "0.5.4",
"mocha": "3.2.0",
"mock-local-storage": "1.0.2",
"mock-socket": "6.0.3",
"mock-socket": "6.0.4",
"nock": "9.0.2",
"postcss-import": "9.0.0",
"postcss-loader": "1.2.0",
"postcss-loader": "1.2.1",
"postcss-nested": "1.0.0",
"postcss-simple-vars": "3.0.0",
"progress": "1.1.8",
"progress-bar-webpack-plugin": "1.9.1",
"raw-loader": "0.5.1",
"react-addons-perf": "15.4.1",
"react-addons-test-utils": "15.4.1",
"react-hot-loader": "3.0.0-beta.6",
"react-intl-aggregate-webpack-plugin": "0.0.1",
"rucksack-css": "0.9.1",
"script-ext-html-webpack-plugin": "1.3.4",
"script-ext-html-webpack-plugin": "1.3.5",
"serviceworker-webpack-plugin": "0.1.7",
"sinon": "1.17.6",
"sinon-as-promised": "4.0.2",
"sinon-chai": "2.8.0",
"style-loader": "0.13.1",
"stylelint": "7.6.0",
"stylelint-config-standard": "15.0.0",
"stylelint": "7.7.0",
"stylelint-config-standard": "15.0.1",
"url-loader": "0.5.7",
"webpack": "2.2.0-rc.2",
"webpack-dev-middleware": "1.8.4",
"webpack-dev-middleware": "1.9.0",
"webpack-error-notification": "0.1.6",
"webpack-hot-middleware": "2.13.2",
"websocket": "1.0.23"
"webpack-hot-middleware": "2.14.0",
"websocket": "1.0.24"
},
"dependencies": {
"bignumber.js": "3.0.1",
@ -150,6 +152,7 @@
"isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5",
"lodash": "4.17.2",
"loglevel": "1.4.1",
"marked": "0.3.6",
"material-ui": "0.16.5",
"material-ui-chip-input": "0.11.1",

View File

@ -49,17 +49,17 @@ function transactions (address, page, test = false) {
// page offset from 0
return _call('txlist', {
address: address,
page: (page || 0) + 1,
offset: PAGE_SIZE,
page: (page || 0) + 1,
sort: 'desc'
}, test).then((transactions) => {
return transactions.map((tx) => {
return {
blockNumber: new BigNumber(tx.blockNumber || 0),
from: util.toChecksumAddress(tx.from),
to: util.toChecksumAddress(tx.to),
hash: tx.hash,
blockNumber: new BigNumber(tx.blockNumber),
timeStamp: tx.timeStamp,
to: util.toChecksumAddress(tx.to),
value: tx.value
};
});

View File

@ -0,0 +1,38 @@
// 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 nock from 'nock';
import { stringify } from 'qs';
import { url } from './links';
function mockget (requests, test) {
let scope = nock(url(test));
requests.forEach((request) => {
scope = scope
.get(`/api?${stringify(request.query)}`)
.reply(request.code || 200, () => {
return { result: request.reply };
});
});
return scope;
}
export {
mockget
};

View File

@ -16,16 +16,10 @@
const nock = require('nock');
const ShapeShift = require('./');
const initShapeshift = (ShapeShift.default || ShapeShift);
const APIKEY = '0x123454321';
const shapeshift = initShapeshift(APIKEY);
const rpc = shapeshift.getRpc();
function mockget (requests) {
let scope = nock(rpc.ENDPOINT);
function mockget (shapeshift, requests) {
let scope = nock(shapeshift.getRpc().ENDPOINT);
requests.forEach((request) => {
scope = scope
@ -38,8 +32,8 @@ function mockget (requests) {
return scope;
}
function mockpost (requests) {
let scope = nock(rpc.ENDPOINT);
function mockpost (shapeshift, requests) {
let scope = nock(shapeshift.getRpc().ENDPOINT);
requests.forEach((request) => {
scope = scope
@ -58,7 +52,5 @@ function mockpost (requests) {
module.exports = {
APIKEY,
mockget,
mockpost,
shapeshift,
rpc
mockpost
};

View File

@ -16,12 +16,21 @@
const helpers = require('./helpers.spec.js');
const APIKEY = helpers.APIKEY;
const ShapeShift = require('./');
const initShapeshift = (ShapeShift.default || ShapeShift);
const mockget = helpers.mockget;
const mockpost = helpers.mockpost;
const rpc = helpers.rpc;
describe('shapeshift/rpc', () => {
let rpc;
let shapeshift;
beforeEach(() => {
shapeshift = initShapeshift(helpers.APIKEY);
rpc = shapeshift.getRpc();
});
describe('GET', () => {
const REPLY = { test: 'this is some result' };
@ -29,7 +38,7 @@ describe('shapeshift/rpc', () => {
let result;
beforeEach(() => {
scope = mockget([{ path: 'test', reply: REPLY }]);
scope = mockget(shapeshift, [{ path: 'test', reply: REPLY }]);
return rpc
.get('test')
@ -54,7 +63,7 @@ describe('shapeshift/rpc', () => {
let result;
beforeEach(() => {
scope = mockpost([{ path: 'test', reply: REPLY }]);
scope = mockpost(shapeshift, [{ path: 'test', reply: REPLY }]);
return rpc
.post('test', { input: 'stuff' })
@ -76,7 +85,7 @@ describe('shapeshift/rpc', () => {
});
it('passes the apikey specified', () => {
expect(scope.body.test.apiKey).to.equal(APIKEY);
expect(scope.body.test.apiKey).to.equal(helpers.APIKEY);
});
});
});

View File

@ -15,8 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default function (rpc) {
let subscriptions = [];
let pollStatusIntervalId = null;
let _subscriptions = [];
let _pollStatusIntervalId = null;
let _subscriptionPromises = null;
function getCoins () {
return rpc.get('getcoins');
@ -36,75 +37,93 @@ export default function (rpc) {
function shift (toAddress, returnAddress, pair) {
return rpc.post('shift', {
withdrawal: toAddress,
pair: pair,
returnAddress: returnAddress
pair,
returnAddress,
withdrawal: toAddress
});
}
function subscribe (depositAddress, callback) {
const idx = subscriptions.length;
if (!depositAddress || !callback) {
return;
}
subscriptions.push({
depositAddress,
const index = _subscriptions.length;
_subscriptions.push({
callback,
idx
depositAddress,
index
});
// Only poll if there are subscriptions...
if (!pollStatusIntervalId) {
pollStatusIntervalId = setInterval(_pollStatus, 2000);
if (_pollStatusIntervalId === null) {
_pollStatusIntervalId = setInterval(_pollStatus, 2000);
}
}
function unsubscribe (depositAddress) {
const newSubscriptions = []
.concat(subscriptions)
.filter((sub) => sub.depositAddress !== depositAddress);
_subscriptions = _subscriptions.filter((sub) => sub.depositAddress !== depositAddress);
subscriptions = newSubscriptions;
if (subscriptions.length === 0) {
clearInterval(pollStatusIntervalId);
pollStatusIntervalId = null;
if (_subscriptions.length === 0) {
clearInterval(_pollStatusIntervalId);
_pollStatusIntervalId = null;
}
return true;
}
function _getSubscriptionStatus (subscription) {
if (!subscription) {
return;
return Promise.resolve();
}
getStatus(subscription.depositAddress)
return getStatus(subscription.depositAddress)
.then((result) => {
switch (result.status) {
case 'no_deposits':
case 'received':
subscription.callback(null, result);
return;
return true;
case 'complete':
subscription.callback(null, result);
subscriptions[subscription.idx] = null;
return;
unsubscribe(subscription.depositAddress);
return true;
case 'failed':
subscription.callback({
message: status.error,
fatal: true
});
subscriptions[subscription.idx] = null;
return;
unsubscribe(subscription.depositAddress);
return true;
}
})
.catch(subscription.callback);
.catch(() => {
return true;
});
}
function _pollStatus () {
subscriptions.forEach(_getSubscriptionStatus);
_subscriptionPromises = Promise.all(_subscriptions.map(_getSubscriptionStatus));
}
function _getSubscriptions () {
return _subscriptions;
}
function _getSubscriptionPromises () {
return _subscriptionPromises;
}
function _isPolling () {
return _pollStatusIntervalId !== null;
}
return {
_getSubscriptions,
_getSubscriptionPromises,
_isPolling,
getCoins,
getMarketInfo,
getRpc,

View File

@ -14,13 +14,29 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const sinon = require('sinon');
const ShapeShift = require('./');
const initShapeshift = (ShapeShift.default || ShapeShift);
const helpers = require('./helpers.spec.js');
const mockget = helpers.mockget;
const mockpost = helpers.mockpost;
const shapeshift = helpers.shapeshift;
describe('shapeshift/calls', () => {
let clock;
let shapeshift;
beforeEach(() => {
clock = sinon.useFakeTimers();
shapeshift = initShapeshift(helpers.APIKEY);
});
afterEach(() => {
clock.restore();
});
describe('getCoins', () => {
const REPLY = {
BTC: {
@ -39,8 +55,8 @@ describe('shapeshift/calls', () => {
let scope;
before(() => {
scope = mockget([{ path: 'getcoins', reply: REPLY }]);
beforeEach(() => {
scope = mockget(shapeshift, [{ path: 'getcoins', reply: REPLY }]);
return shapeshift.getCoins();
});
@ -61,8 +77,8 @@ describe('shapeshift/calls', () => {
let scope;
before(() => {
scope = mockget([{ path: 'marketinfo/btc_ltc', reply: REPLY }]);
beforeEach(() => {
scope = mockget(shapeshift, [{ path: 'marketinfo/btc_ltc', reply: REPLY }]);
return shapeshift.getMarketInfo('btc_ltc');
});
@ -80,8 +96,8 @@ describe('shapeshift/calls', () => {
let scope;
before(() => {
scope = mockget([{ path: 'txStat/0x123', reply: REPLY }]);
beforeEach(() => {
scope = mockget(shapeshift, [{ path: 'txStat/0x123', reply: REPLY }]);
return shapeshift.getStatus('0x123');
});
@ -101,8 +117,8 @@ describe('shapeshift/calls', () => {
let scope;
before(() => {
scope = mockpost([{ path: 'shift', reply: REPLY }]);
beforeEach(() => {
scope = mockpost(shapeshift, [{ path: 'shift', reply: REPLY }]);
return shapeshift.shift('0x456', '1BTC', 'btc_eth');
});
@ -125,4 +141,80 @@ describe('shapeshift/calls', () => {
});
});
});
describe('subscriptions', () => {
const ADDRESS = '0123456789abcdef';
const REPLY = {
status: 'complete',
address: ADDRESS
};
let callback;
beforeEach(() => {
mockget(shapeshift, [{ path: `txStat/${ADDRESS}`, reply: REPLY }]);
callback = sinon.stub();
shapeshift.subscribe(ADDRESS, callback);
});
describe('subscribe', () => {
it('adds the depositAddress to the list', () => {
const subscriptions = shapeshift._getSubscriptions();
expect(subscriptions.length).to.equal(1);
expect(subscriptions[0].depositAddress).to.equal(ADDRESS);
});
it('starts the polling timer', () => {
expect(shapeshift._isPolling()).to.be.true;
});
it('calls the callback once the timer has elapsed', () => {
clock.tick(2222);
return shapeshift._getSubscriptionPromises().then(() => {
expect(callback).to.have.been.calledWith(null, REPLY);
});
});
it('auto-unsubscribes on completed', () => {
clock.tick(2222);
return shapeshift._getSubscriptionPromises().then(() => {
expect(shapeshift._getSubscriptions().length).to.equal(0);
});
});
});
describe('unsubscribe', () => {
it('unbsubscribes when requested', () => {
expect(shapeshift._getSubscriptions().length).to.equal(1);
shapeshift.unsubscribe(ADDRESS);
expect(shapeshift._getSubscriptions().length).to.equal(0);
});
it('clears the polling on no subscriptions', () => {
shapeshift.unsubscribe(ADDRESS);
expect(shapeshift._isPolling()).to.be.false;
});
it('handles unsubscribe of auto-unsubscribe', () => {
clock.tick(2222);
return shapeshift._getSubscriptionPromises().then(() => {
expect(shapeshift.unsubscribe(ADDRESS)).to.be.true;
});
});
it('handles unsubscribe when multiples listed', () => {
const ADDRESS2 = 'abcdef0123456789';
shapeshift.subscribe(ADDRESS2, sinon.stub());
expect(shapeshift._getSubscriptions().length).to.equal(2);
expect(shapeshift._getSubscriptions()[0].depositAddress).to.equal(ADDRESS);
shapeshift.unsubscribe(ADDRESS);
expect(shapeshift._getSubscriptions()[0].depositAddress).to.equal(ADDRESS2);
});
});
});
});

View File

@ -25,7 +25,7 @@ export default class Encoder {
throw new Error('tokens should be array of Token');
}
const mediates = tokens.map((token) => Encoder.encodeToken(token));
const mediates = tokens.map((token, index) => Encoder.encodeToken(token, index));
const inits = mediates
.map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx)))
.join('');
@ -36,37 +36,40 @@ export default class Encoder {
return `${inits}${closings}`;
}
static encodeToken (token) {
static encodeToken (token, index = 0) {
if (!isInstanceOf(token, Token)) {
throw new Error('token should be instanceof Token');
}
switch (token.type) {
case 'address':
return new Mediate('raw', padAddress(token.value));
try {
switch (token.type) {
case 'address':
return new Mediate('raw', padAddress(token.value));
case 'int':
case 'uint':
return new Mediate('raw', padU32(token.value));
case 'int':
case 'uint':
return new Mediate('raw', padU32(token.value));
case 'bool':
return new Mediate('raw', padBool(token.value));
case 'bool':
return new Mediate('raw', padBool(token.value));
case 'fixedBytes':
return new Mediate('raw', padFixedBytes(token.value));
case 'fixedBytes':
return new Mediate('raw', padFixedBytes(token.value));
case 'bytes':
return new Mediate('prefixed', padBytes(token.value));
case 'bytes':
return new Mediate('prefixed', padBytes(token.value));
case 'string':
return new Mediate('prefixed', padString(token.value));
case 'string':
return new Mediate('prefixed', padString(token.value));
case 'fixedArray':
case 'array':
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
default:
throw new Error(`Invalid token type ${token.type} in encodeToken`);
case 'fixedArray':
case 'array':
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
}
} catch (e) {
throw new Error(`Cannot encode token #${index} [${token.type}: ${token.value}]. ${e.message}`);
}
throw new Error(`Invalid token type ${token.type} in encodeToken`);
}
}

View File

@ -41,6 +41,10 @@ export default class Interface {
}
encodeTokens (paramTypes, values) {
return Interface.encodeTokens(paramTypes, values);
}
static encodeTokens (paramTypes, values) {
const createToken = function (paramType, value) {
if (paramType.subtype) {
return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry)));

View File

@ -31,6 +31,12 @@ export default class Param {
}
static toParams (params) {
return params.map((param) => new Param(param.name, param.type));
return params.map((param) => {
if (param instanceof Param) {
return param;
}
return new Param(param.name, param.type);
});
}
}

View File

@ -34,5 +34,14 @@ describe('abi/spec/Param', () => {
expect(params[0].name).to.equal('foo');
expect(params[0].kind.type).to.equal('uint');
});
it('converts only if needed', () => {
const _params = Param.toParams([{ name: 'foo', type: 'uint' }]);
const params = Param.toParams(_params);
expect(params.length).to.equal(1);
expect(params[0].name).to.equal('foo');
expect(params[0].kind.type).to.equal('uint');
});
});
});

View File

@ -114,7 +114,11 @@ export default class Api {
}
})
.catch((error) => {
console.error('pollMethod', error);
// Don't print if the request is rejected: that's ok
if (error.type !== 'REQUEST_REJECTED') {
console.error('pollMethod', error);
}
reject(error);
});
};

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Abi from '../../abi';
import Abi from '~/abi';
let nextSubscriptionId = 0;
@ -53,6 +53,10 @@ export default class Contract {
this._subscribedToBlock = false;
this._blockSubscriptionId = null;
if (api && api.patch && api.patch.contract) {
api.patch.contract(this);
}
}
get address () {
@ -71,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;
@ -90,8 +98,10 @@ export default class Contract {
}
deployEstimateGas (options, values) {
const _options = this._encodeOptions(this.constructors[0], options, values);
return this._api.eth
.estimateGas(this._encodeOptions(this.constructors[0], options, values))
.estimateGas(_options)
.then((gasEst) => {
return [gasEst, gasEst.mul(1.2)];
});
@ -115,8 +125,10 @@ export default class Contract {
setState({ state: 'postTransaction', gas });
const _options = this._encodeOptions(this.constructors[0], options, values);
return this._api.parity
.postTransaction(this._encodeOptions(this.constructors[0], options, values))
.postTransaction(_options)
.then((requestId) => {
setState({ state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId);
@ -131,6 +143,7 @@ export default class Contract {
}
setState({ state: 'hasReceipt', receipt });
this._receipt = receipt;
this._address = receipt.contractAddress;
return this._address;
});
@ -199,7 +212,7 @@ export default class Contract {
getCallData = (func, options, values) => {
let data = options.data;
const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null;
const tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null;
const call = tokens ? func.encodeCall(tokens) : null;
if (data && data.substr(0, 2) === '0x') {
@ -210,17 +223,24 @@ 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) => {
func.contract = this;
func.call = (options, values = []) => {
const callParams = this._encodeOptions(func, this._addOptionsTo(options), values);
@ -233,13 +253,13 @@ export default class Contract {
if (!func.constant) {
func.postTransaction = (options, values = []) => {
return this._api.parity
.postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values));
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
return this._api.parity.postTransaction(_options);
};
func.estimateGas = (options, values = []) => {
return this._api.eth
.estimateGas(this._encodeOptions(func, this._addOptionsTo(options), values));
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
return this._api.eth.estimateGas(_options);
};
}

View File

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

View File

@ -16,7 +16,6 @@
import BigNumber from 'bignumber.js';
import sinon from 'sinon';
import 'sinon-as-promised';
import Eth from './eth';

View File

@ -15,7 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import sinon from 'sinon';
import 'sinon-as-promised';
import Personal from './personal';

View File

@ -209,7 +209,10 @@ export default class Ws extends JsonRpcBase {
if (result.error) {
this.error(event.data);
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
// Don't print error if request rejected...
if (!/rejected/.test(result.error.message)) {
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
}
const error = new TransportError(method, result.error.code, result.error.message);
reject(error);

View File

@ -26,7 +26,9 @@ export function decodeCallData (data) {
if (data.substr(0, 2) === '0x') {
return decodeCallData(data.slice(2));
} else if (data.length < 8) {
}
if (data.length < 8) {
throw new Error('Input to decodeCallData should be method signature + data');
}
@ -42,13 +44,15 @@ export function decodeCallData (data) {
export function decodeMethodInput (methodAbi, paramdata) {
if (!methodAbi) {
throw new Error('decodeMethodInput should receive valid method-specific ABI');
} else if (paramdata && paramdata.length) {
}
if (paramdata && paramdata.length) {
if (!isHex(paramdata)) {
throw new Error('Input to decodeMethodInput should be a hex value');
} else if (paramdata.substr(0, 2) === '0x') {
}
if (paramdata.substr(0, 2) === '0x') {
return decodeMethodInput(methodAbi, paramdata.slice(2));
} else if (paramdata.length % 64 !== 0) {
throw new Error('Parameter length in decodeMethodInput not a multiple of 64 characters');
}
}

View File

@ -48,10 +48,6 @@ describe('api/util/decode', () => {
expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/);
});
it('throws on invalid lengths', () => {
expect(() => decodeMethodInput({}, DATA.slice(-32))).to.throw(/not a multiple of/);
});
it('correctly decodes valid inputs', () => {
expect(decodeMethodInput({
type: 'function',

View File

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

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { bytesToHex } from './format';
import { bytesToHex, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format';
describe('api/util/format', () => {
describe('bytesToHex', () => {
@ -26,4 +26,46 @@ describe('api/util/format', () => {
expect(bytesToHex([0, 15, 16])).to.equal('0x000f10');
});
});
describe('hexToBytes', () => {
it('correctly converts an empty string', () => {
expect(hexToBytes('')).to.deep.equal([]);
expect(hexToBytes('0x')).to.deep.equal([]);
});
it('correctly converts a non-empty string', () => {
expect(hexToBytes('0x000f10')).to.deep.equal([0, 15, 16]);
});
});
describe('asciiToHex', () => {
it('correctly converts an empty string', () => {
expect(asciiToHex('')).to.equal('0x');
});
it('correctly converts a non-empty string', () => {
expect(asciiToHex('abc')).to.equal('0x616263');
});
});
describe('hexToAscii', () => {
it('correctly converts an empty string', () => {
expect(hexToAscii('')).to.equal('');
expect(hexToAscii('0x')).to.equal('');
});
it('correctly converts a non-empty string', () => {
expect(hexToAscii('0x616263')).to.equal('abc');
});
});
describe('bytesToAscii', () => {
it('correctly converts an empty string', () => {
expect(bytesToAscii([])).to.equal('');
});
it('correctly converts a non-empty string', () => {
expect(bytesToAscii([97, 98, 99])).to.equal('abc');
});
});
});

View File

@ -21,7 +21,7 @@ const TEST_ENV = process.env.NODE_ENV === 'test';
export function createIdentityImg (address, scale = 8) {
return TEST_ENV
? ''
? 'test-createIdentityImg'
: blockies({
seed: (address || '').toLowerCase(),
size: 8,

View File

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

View File

@ -14,8 +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 { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
import { keccak_256 } from 'js-sha3'; // eslint-disable-line
export function sha3 (value) {
return `0x${keccak_256(value)}`;
import { hexToBytes } from './format';
import { isHex } from './types';
export function sha3 (value, options) {
const forceHex = options && options.encoding === 'hex';
if (forceHex || (!options && isHex(value))) {
const bytes = hexToBytes(value);
return sha3(bytes);
}
const hash = keccak_256(value);
return `0x${hash}`;
}
sha3.text = (val) => sha3(val, { encoding: 'raw' });

View File

@ -21,5 +21,25 @@ describe('api/util/sha3', () => {
it('constructs a correct sha3 value', () => {
expect(sha3('jacogr')).to.equal('0x2f4ff4b5a87abbd2edfed699db48a97744e028c7f7ce36444d40d29d792aa4dc');
});
it('constructs a correct sha3 encoded as hex', () => {
const key = '000000000000000000000000391694e7e0b0cce554cb130d723a9d27458f9298' + '0000000000000000000000000000000000000000000000000000000000000001';
expect(sha3(key, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9');
expect(sha3(`0x${key}`, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9');
});
it('constructs a correct sha3 from Uint8Array', () => {
expect(sha3('01020304', { encoding: 'hex' })).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
expect(sha3(Uint8Array.from([1, 2, 3, 4]))).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
});
it('should interpret as bytes by default', () => {
expect(sha3('0x01020304')).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
});
it('should force text if option is passed', () => {
expect(sha3('0x01020304', { encoding: 'raw' })).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
expect(sha3.text('0x01020304')).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
});
});
});

View File

@ -29,6 +29,10 @@ export function isFunction (test) {
}
export function isHex (_test) {
if (!isString(_test)) {
return false;
}
if (_test.substr(0, 2) === '0x') {
return isHex(_test.slice(2));
}

View File

@ -66,6 +66,12 @@ describe('api/util/types', () => {
it('correctly identifies non-hex values', () => {
expect(isHex('123j')).to.be.false;
});
it('correctly indentifies non-string values', () => {
expect(isHex(false)).to.be.false;
expect(isHex()).to.be.false;
expect(isHex([1, 2, 3])).to.be.false;
});
});
describe('isInstanceOf', () => {

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

@ -0,0 +1,28 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import LogLevel from 'loglevel';
export const LOG_KEYS = {
TransferModalStore: {
path: 'modals/Transfer/store',
desc: 'Transfer Modal MobX Store'
}
};
export const getLogger = (LOG_KEY) => {
return LogLevel.getLogger(LOG_KEY.path);
};

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { bytesToHex, hex2Ascii } from '~/api/util/format';
import { bytesToHex, hexToAscii } from '~/api/util/format';
import ABI from './abi/certifier.json';
@ -62,7 +62,7 @@ export default class BadgeReg {
name = bytesToHex(name);
name = name === ZERO32
? null
: hex2Ascii(name);
: hexToAscii(name);
return this.fetchMeta(id)
.then(({ title, icon }) => {
@ -84,7 +84,7 @@ export default class BadgeReg {
})
.then(([ title, icon ]) => {
title = bytesToHex(title);
title = title === ZERO32 ? null : hex2Ascii(title);
title = title === ZERO32 ? null : hexToAscii(title);
if (bytesToHex(icon) === ZERO32) {
icon = null;

View File

@ -91,7 +91,7 @@ export default class Registry {
lookupAddress (_name) {
const name = _name.toLowerCase();
const sha3 = this._api.util.sha3(name);
const sha3 = this._api.util.sha3.text(name);
return this.getInstance().then((instance) => {
return instance.getAddress.call({}, [sha3, 'A']);

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import subscribeToEvent from '../util/subscribe-to-event';
import subscribeToEvents from '../util/subscribe-to-events';
export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]);
@ -72,7 +72,7 @@ export const awaitPuzzle = (api, contract, account) => {
return blockNumber(api)
.then((block) => {
return new Promise((resolve, reject) => {
const subscription = subscribeToEvent(contract, 'Puzzled', {
const subscription = subscribeToEvents(contract, ['Puzzled'], {
from: block.toNumber(),
filter: (log) => log.params.who.value === account
});

View File

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

View File

@ -34,3 +34,16 @@ ReactDOM.render(
</Provider>,
document.querySelector('#container')
);
if (module.hot) {
module.hot.accept('./registry/Container', () => {
require('./registry/Container');
ReactDOM.render(
<Provider store={ store }>
<Container />
</Provider>,
document.querySelector('#container')
);
});
}

View File

@ -44,8 +44,8 @@ export default class Application extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
contract: nullableProptype(PropTypes.object).isRequired,
fee: nullableProptype(PropTypes.object).isRequired
contract: nullableProptype(PropTypes.object.isRequired),
fee: nullableProptype(PropTypes.object.isRequired)
};
render () {

View File

@ -15,11 +15,13 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { sha3 } from '../parity.js';
import { getOwner } from '../util/registry';
export const clear = () => ({ type: 'lookup clear' });
export const lookupStart = (name, key) => ({ type: 'lookup start', name, key });
export const reverseLookupStart = (address) => ({ type: 'reverseLookup start', address });
export const ownerLookupStart = (name) => ({ type: 'ownerLookup start', name });
export const success = (action, result) => ({ type: `${action} success`, result: result });
@ -37,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}`);
@ -48,24 +50,50 @@ export const lookup = (name, key) => (dispatch, getState) => {
});
};
export const reverseLookup = (address) => (dispatch, getState) => {
export const reverseLookup = (lookupAddress) => (dispatch, getState) => {
const { contract } = getState();
if (!contract) {
return;
}
const reverse = contract.functions
.find((f) => f.name === 'reverse');
dispatch(reverseLookupStart(lookupAddress));
dispatch(reverseLookupStart(address));
reverse.call({}, [ address ])
.then((address) => dispatch(success('reverseLookup', address)))
contract.instance
.reverse
.call({}, [ lookupAddress ])
.then((address) => {
dispatch(success('reverseLookup', address));
})
.catch((err) => {
console.error(`could not lookup reverse for ${address}`);
console.error(`could not lookup reverse for ${lookupAddress}`);
if (err) {
console.error(err.stack);
}
dispatch(fail('reverseLookup'));
});
};
export const ownerLookup = (name) => (dispatch, getState) => {
const { contract } = getState();
if (!contract) {
return;
}
dispatch(ownerLookupStart(name));
return getOwner(contract, name)
.then((owner) => {
dispatch(success('ownerLookup', owner));
})
.catch((err) => {
console.error(`could not lookup owner for ${name}`);
if (err) {
console.error(err.stack);
}
dispatch(fail('ownerLookup'));
});
};

View File

@ -23,13 +23,14 @@ import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import RaisedButton from 'material-ui/RaisedButton';
import SearchIcon from 'material-ui/svg-icons/action/search';
import keycode from 'keycode';
import { nullableProptype } from '~/util/proptypes';
import Address from '../ui/address.js';
import renderImage from '../ui/image.js';
import { clear, lookup, reverseLookup } from './actions';
import { clear, lookup, ownerLookup, reverseLookup } from './actions';
import styles from './lookup.css';
class Lookup extends Component {
@ -39,6 +40,7 @@ class Lookup extends Component {
clear: PropTypes.func.isRequired,
lookup: PropTypes.func.isRequired,
ownerLookup: PropTypes.func.isRequired,
reverseLookup: PropTypes.func.isRequired
}
@ -50,33 +52,6 @@ class Lookup extends Component {
const { input, type } = this.state;
const { result } = this.props;
let output = '';
if (result) {
if (type === 'A') {
output = (
<code>
<Address
address={ result }
shortenHash={ false }
/>
</code>
);
} else if (type === 'IMG') {
output = renderImage(result);
} else if (type === 'CONTENT') {
output = (
<div>
<code>{ result }</code>
<p>Keep in mind that this is most likely the hash of the content you are looking for.</p>
</div>
);
} else {
output = (
<code>{ result }</code>
);
}
}
return (
<Card className={ styles.lookup }>
<CardHeader title={ 'Query the Registry' } />
@ -85,6 +60,7 @@ class Lookup extends Component {
hintText={ type === 'reverse' ? 'address' : 'name' }
value={ input }
onChange={ this.onInputChange }
onKeyDown={ this.onKeyDown }
/>
<DropDownMenu
value={ type }
@ -94,6 +70,7 @@ class Lookup extends Component {
<MenuItem value='IMG' primaryText='IMG  hash of a picture in the blockchain' />
<MenuItem value='CONTENT' primaryText='CONTENT  hash of a data in the blockchain' />
<MenuItem value='reverse' primaryText='reverse find a name for an address' />
<MenuItem value='owner' primaryText='owner find a the owner' />
</DropDownMenu>
<RaisedButton
label='Lookup'
@ -102,35 +79,102 @@ class Lookup extends Component {
onTouchTap={ this.onLookupClick }
/>
</div>
<CardText>{ output }</CardText>
<CardText>
{ this.renderOutput(type, result) }
</CardText>
</Card>
);
}
renderOutput (type, result) {
if (result === null) {
return null;
}
if (type === 'A') {
return (
<code>
<Address
address={ result }
shortenHash={ false }
/>
</code>
);
}
if (type === 'owner') {
if (!result) {
return (
<code>Not reserved yet</code>
);
}
return (
<code>
<Address
address={ result }
shortenHash={ false }
/>
</code>
);
}
if (type === 'IMG') {
return renderImage(result);
}
if (type === 'CONTENT') {
return (
<div>
<code>{ result }</code>
<p>Keep in mind that this is most likely the hash of the content you are looking for.</p>
</div>
);
}
return (
<code>{ result || 'No data' }</code>
);
}
onInputChange = (e) => {
this.setState({ input: e.target.value });
};
}
onKeyDown = (event) => {
const codeName = keycode(event);
if (codeName !== 'enter') {
return;
}
this.onLookupClick();
}
onTypeChange = (e, i, type) => {
this.setState({ type });
this.props.clear();
};
}
onLookupClick = () => {
const { input, type } = this.state;
if (type === 'reverse') {
this.props.reverseLookup(input);
} else {
this.props.lookup(input, type);
return this.props.reverseLookup(input);
}
};
if (type === 'owner') {
return this.props.ownerLookup(input);
}
return this.props.lookup(input, type);
}
}
const mapStateToProps = (state) => state.lookup;
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
clear, lookup, reverseLookup
clear, lookup, ownerLookup, reverseLookup
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Lookup);

View File

@ -24,7 +24,7 @@ const initialState = {
export default (state = initialState, action) => {
const { type } = action;
if (type.slice(0, 7) !== 'lookup ' && type.slice(0, 14) !== 'reverseLookup ') {
if (!/^(lookup|reverseLookup|ownerLookup)/.test(type)) {
return state;
}

View File

@ -15,8 +15,13 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { sha3, api } from '../parity.js';
import { getOwner, isOwned } from '../util/registry';
import postTx from '../util/post-tx';
export const clearError = () => ({
type: 'clearError'
});
const alreadyQueued = (queue, action, name) =>
!!queue.find((entry) => entry.action === action && entry.name === name);
@ -24,13 +29,14 @@ export const reserveStart = (name) => ({ type: 'names reserve start', name });
export const reserveSuccess = (name) => ({ type: 'names reserve success', name });
export const reserveFail = (name) => ({ type: 'names reserve fail', name });
export const reserveFail = (name, error) => ({ type: 'names reserve fail', name, error });
export const reserve = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
const fee = state.fee;
if (!contract || !account) {
return;
}
@ -40,27 +46,34 @@ export const reserve = (name) => (dispatch, getState) => {
if (alreadyQueued(state.names.queue, 'reserve', name)) {
return;
}
const reserve = contract.functions.find((f) => f.name === 'reserve');
dispatch(reserveStart(name));
const options = {
from: account.address,
value: fee
};
const values = [
sha3(name)
];
return isOwned(contract, name)
.then((owned) => {
if (owned) {
throw new Error(`"${name}" has already been reserved`);
}
postTx(api, reserve, options, values)
const { reserve } = contract.instance;
const options = {
from: account.address,
value: fee
};
const values = [
sha3.text(name)
];
return postTx(api, reserve, options, values);
})
.then((txHash) => {
dispatch(reserveSuccess(name));
})
.catch((err) => {
console.error(`could not reserve ${name}`);
if (err) {
console.error(err.stack);
if (err.type !== 'REQUEST_REJECTED') {
console.error(`error rerserving ${name}`, err);
return dispatch(reserveFail(name, err));
}
dispatch(reserveFail(name));
@ -71,43 +84,52 @@ export const dropStart = (name) => ({ type: 'names drop start', name });
export const dropSuccess = (name) => ({ type: 'names drop success', name });
export const dropFail = (name) => ({ type: 'names drop fail', name });
export const dropFail = (name, error) => ({ type: 'names drop fail', name, error });
export const drop = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
if (!contract || !account) {
return;
}
name = name.toLowerCase();
if (alreadyQueued(state.names.queue, 'drop', name)) {
return;
}
const drop = contract.functions.find((f) => f.name === 'drop');
dispatch(dropStart(name));
const options = {
from: account.address
};
const values = [
sha3(name)
];
return getOwner(contract, name)
.then((owner) => {
if (owner.toLowerCase() !== account.address.toLowerCase()) {
throw new Error(`you are not the owner of "${name}"`);
}
postTx(api, drop, options, values)
const { drop } = contract.instance;
const options = {
from: account.address
};
const values = [
sha3.text(name)
];
return postTx(api, drop, options, values);
})
.then((txhash) => {
dispatch(dropSuccess(name));
})
.catch((err) => {
console.error(`could not drop ${name}`);
if (err) {
console.error(err.stack);
if (err.type !== 'REQUEST_REJECTED') {
console.error(`error dropping ${name}`, err);
return dispatch(dropFail(name, err));
}
dispatch(reserveFail(name));
dispatch(dropFail(name));
});
};

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