diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b05c1e01..9985358d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 776c26c42..45aecc9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "parity" -version = "1.5.0" +version = "1.6.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -9,22 +9,22 @@ dependencies = [ "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.5.0", - "ethcore-dapps 1.5.0", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", + "ethcore 1.6.0", + "ethcore-dapps 1.6.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", "ethcore-ipc-hypervisor 1.2.0", - "ethcore-ipc-nano 1.5.0", + "ethcore-ipc-nano 1.6.0", "ethcore-ipc-tests 0.1.0", - "ethcore-light 1.5.0", - "ethcore-logger 1.5.0", - "ethcore-rpc 1.5.0", - "ethcore-signer 1.5.0", - "ethcore-stratum 1.5.0", - "ethcore-util 1.5.0", - "ethsync 1.5.0", + "ethcore-light 1.6.0", + "ethcore-logger 1.6.0", + "ethcore-rpc 1.6.0", + "ethcore-signer 1.6.0", + "ethcore-stratum 1.6.0", + "ethcore-util 1.6.0", + "ethsync 1.6.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -32,10 +32,10 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.5.0", + "parity-hash-fetch 1.6.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", - "parity-updater 1.5.0", + "parity-updater 1.6.0", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "ethash" -version = "1.5.0" +version = "1.6.0" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -346,7 +346,7 @@ dependencies = [ [[package]] name = "ethcore" -version = "1.5.0" +version = "1.6.0" dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -355,18 +355,18 @@ dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.5.0", + "ethash 1.6.0", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-ipc-nano 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-util 1.6.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "evmjit 1.5.0", + "evmjit 1.6.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -403,13 +403,13 @@ dependencies = [ [[package]] name = "ethcore-dapps" -version = "1.5.0" +version = "1.6.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.5.0", - "ethcore-rpc 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-rpc 1.6.0", + "ethcore-util 1.6.0", "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", @@ -420,9 +420,9 @@ dependencies = [ "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.5.0", + "parity-hash-fetch 1.6.0", "parity-reactor 0.1.0", - "parity-ui 1.5.0", + "parity-ui 1.6.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -436,14 +436,14 @@ dependencies = [ [[package]] name = "ethcore-devtools" -version = "1.5.0" +version = "1.6.0" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-io" -version = "1.5.0" +version = "1.6.0" dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -454,17 +454,17 @@ dependencies = [ [[package]] name = "ethcore-ipc" -version = "1.5.0" +version = "1.6.0" dependencies = [ - "ethcore-devtools 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-util 1.6.0", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-ipc-codegen" -version = "1.5.0" +version = "1.6.0" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -477,9 +477,9 @@ dependencies = [ name = "ethcore-ipc-hypervisor" version = "1.2.0" dependencies = [ - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-ipc-nano 1.5.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -488,9 +488,9 @@ dependencies = [ [[package]] name = "ethcore-ipc-nano" -version = "1.5.0" +version = "1.6.0" dependencies = [ - "ethcore-ipc 1.5.0", + "ethcore-ipc 1.6.0", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", @@ -500,11 +500,11 @@ dependencies = [ name = "ethcore-ipc-tests" version = "0.1.0" dependencies = [ - "ethcore-devtools 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-ipc-nano 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-util 1.6.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -512,14 +512,14 @@ dependencies = [ [[package]] name = "ethcore-light" -version = "1.5.0" +version = "1.6.0" dependencies = [ - "ethcore 1.5.0", - "ethcore-io 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-network 1.5.0", - "ethcore-util 1.5.0", + "ethcore 1.6.0", + "ethcore-io 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-network 1.6.0", + "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -530,10 +530,10 @@ dependencies = [ [[package]] name = "ethcore-logger" -version = "1.5.0" +version = "1.6.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.5.0", + "ethcore-util 1.6.0", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -543,13 +543,13 @@ dependencies = [ [[package]] name = "ethcore-network" -version = "1.5.0" +version = "1.6.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-util 1.6.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -568,20 +568,20 @@ dependencies = [ [[package]] name = "ethcore-rpc" -version = "1.5.0" +version = "1.6.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.5.0", - "ethcore 1.5.0", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-util 1.5.0", + "ethash 1.6.0", + "ethcore 1.6.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-util 1.6.0", "ethcrypto 0.1.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "ethsync 1.5.0", + "ethsync 1.6.0", "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -590,7 +590,7 @@ dependencies = [ "jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", - "parity-updater 1.5.0", + "parity-updater 1.6.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -604,18 +604,18 @@ dependencies = [ [[package]] name = "ethcore-signer" -version = "1.5.0" +version = "1.6.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-rpc 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-rpc 1.6.0", + "ethcore-util 1.6.0", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-ui 1.5.0", + "parity-ui 1.6.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)", @@ -623,14 +623,14 @@ dependencies = [ [[package]] name = "ethcore-stratum" -version = "1.5.0" +version = "1.6.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-ipc-nano 1.5.0", - "ethcore-util 1.5.0", + "ethcore-devtools 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-util 1.6.0", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -641,7 +641,7 @@ dependencies = [ [[package]] name = "ethcore-util" -version = "1.5.0" +version = "1.6.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -651,7 +651,7 @@ dependencies = [ "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.5.0", + "ethcore-devtools 1.6.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -690,7 +690,7 @@ dependencies = [ name = "ethjson" version = "0.1.0" dependencies = [ - "ethcore-util 1.5.0", + "ethcore-util 1.6.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -734,19 +734,19 @@ dependencies = [ [[package]] name = "ethsync" -version = "1.5.0" +version = "1.6.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.5.0", - "ethcore-devtools 1.5.0", - "ethcore-io 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-ipc-nano 1.5.0", - "ethcore-light 1.5.0", - "ethcore-network 1.5.0", - "ethcore-util 1.5.0", + "ethcore 1.6.0", + "ethcore-devtools 1.6.0", + "ethcore-io 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-ipc-nano 1.6.0", + "ethcore-light 1.6.0", + "ethcore-network 1.6.0", + "ethcore-util 1.6.0", "ethkey 0.2.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "evmjit" -version = "1.5.0" +version = "1.6.0" dependencies = [ "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -921,11 +921,11 @@ dependencies = [ [[package]] name = "ipc-common-types" -version = "1.5.0" +version = "1.6.0" dependencies = [ - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-util 1.5.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-util 1.6.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1442,10 +1442,10 @@ dependencies = [ [[package]] name = "parity-hash-fetch" -version = "1.5.0" +version = "1.6.0" dependencies = [ "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.5.0", + "ethcore-util 1.6.0", "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1468,9 +1468,9 @@ dependencies = [ name = "parity-rpc-client" version = "1.4.0" dependencies = [ - "ethcore-rpc 1.5.0", - "ethcore-signer 1.5.0", - "ethcore-util 1.5.0", + "ethcore-rpc 1.6.0", + "ethcore-signer 1.6.0", + "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1486,7 +1486,7 @@ dependencies = [ [[package]] name = "parity-ui" -version = "1.5.0" +version = "1.6.0" dependencies = [ "parity-ui-dev 1.4.0", "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", @@ -1503,24 +1503,24 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#257b3ce8aaa6797507592200dc78b29b8a305c3f" +source = "git+https://github.com/ethcore/js-precompiled.git#e21ae69190fa390f5550e5cf17f5ea362ba4db41" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parity-updater" -version = "1.5.0" +version = "1.6.0" dependencies = [ "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.5.0", - "ethcore-ipc 1.5.0", - "ethcore-ipc-codegen 1.5.0", - "ethcore-util 1.5.0", - "ethsync 1.5.0", - "ipc-common-types 1.5.0", + "ethcore 1.6.0", + "ethcore-ipc 1.6.0", + "ethcore-ipc-codegen 1.6.0", + "ethcore-util 1.6.0", + "ethsync 1.6.0", + "ipc-common-types 1.6.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.5.0", + "parity-hash-fetch 1.6.0", "parity-reactor 0.1.0", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1795,8 +1795,8 @@ name = "rpc-cli" version = "1.4.0" dependencies = [ "ethcore-bigint 0.1.2", - "ethcore-rpc 1.5.0", - "ethcore-util 1.5.0", + "ethcore-rpc 1.6.0", + "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc-client 1.4.0", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 18ada1185..8026662d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 5b044fb89..09c72cb47 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/dapps/js-glue/Cargo.toml b/dapps/js-glue/Cargo.toml index 8832624a8..1074330be 100644 --- a/dapps/js-glue/Cargo.toml +++ b/dapps/js-glue/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index 12bad2e3d..9984a112a 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -17,7 +17,7 @@ use std::io; use std::io::Read; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use page::{LocalPageEndpoint, PageCache}; use endpoint::{Endpoints, EndpointInfo}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; @@ -28,10 +28,79 @@ struct LocalDapp { info: EndpointInfo, } -fn local_dapps(dapps_path: String) -> Vec { - 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>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box)> { + 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>(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 { + 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 { } 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 -} diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 51f8f5572..f32cf9042 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::path::PathBuf; use std::sync::Arc; use endpoint::{Endpoints, Endpoint}; use page::PageEndpoint; @@ -43,7 +44,8 @@ pub fn utils() -> Box { } pub fn all_endpoints( - dapps_path: String, + dapps_path: PathBuf, + extra_dapps: Vec, signer_address: Option<(String, u16)>, web_proxy_tokens: Arc, remote: Remote, @@ -51,6 +53,13 @@ pub fn all_endpoints( ) -> 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::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index effa29fcc..64ee0c341 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -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 WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync { /// Webapps HTTP+RPC server build. pub struct ServerBuilder { - dapps_path: String, + dapps_path: PathBuf, + extra_dapps: Vec, handler: Arc, registrar: Arc, sync_status: Arc, @@ -141,9 +143,10 @@ impl Extendable for ServerBuilder { impl ServerBuilder { /// Construct new dapps server - pub fn new(dapps_path: String, registrar: Arc, remote: Remote) -> Self { + pub fn new>(dapps_path: P, registrar: Arc, 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 ServerBuilder { pub fn fetch(self, fetch: X) -> ServerBuilder { 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 ServerBuilder { self } + /// Change extra dapps paths (apart from `dapps_path`) + pub fn extra_dapps>(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>) -> Result { @@ -197,6 +207,7 @@ impl ServerBuilder { 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 ServerBuilder { 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>, authorization: A, handler: Arc, - dapps_path: String, + dapps_path: PathBuf, + extra_dapps: Vec, signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, @@ -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({ diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index d3f97b35b..5cc367fcc 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -51,7 +51,7 @@ pub fn init_server(hosts: Option>, 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() } diff --git a/dapps/ui/Cargo.toml b/dapps/ui/Cargo.toml index e835bd820..e17948204 100644 --- a/dapps/ui/Cargo.toml +++ b/dapps/ui/Cargo.toml @@ -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 "] [build-dependencies] diff --git a/db/Cargo.toml b/db/Cargo.toml index d0e45a489..fcceaa17d 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 163bd7baf..0cf5a6b2e 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -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 "] [dependencies] diff --git a/ethash/Cargo.toml b/ethash/Cargo.toml index 98ba22f5b..36909a525 100644 --- a/ethash/Cargo.toml +++ b/ethash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethash" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies "] [lib] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index a815bddd6..da124073c 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index e0a1b581f..534b0e5c1 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index 2dd38c755..ac7eb5041 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -6,10 +6,12 @@ "gasLimitBoundDivisor": "0x0400", "stepDuration": 1, "startStep": 2, - "authorities" : [ - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" - ] + "validators": { + "list": [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } } } }, diff --git a/ethcore/res/basic_authority.json b/ethcore/res/basic_authority.json index 623590bfa..6b9f4c0ed 100644 --- a/ethcore/res/basic_authority.json +++ b/ethcore/res/basic_authority.json @@ -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", diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index da62448e5..a1262fa33 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,10 +4,12 @@ "tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "authorities" : [ - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" - ] + "validators" : { + "list": [ + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ] + } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json new file mode 100644 index 000000000..4bf120b54 --- /dev/null +++ b/ethcore/res/validator_contract.json @@ -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" } + } +} diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 1eec9110e..81c32674d 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -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) } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f57b1248a..cd074a88c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -53,7 +53,7 @@ use verification::queue::BlockQueue; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, - MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, + MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, PruningInfo, }; use client::Error as ClientError; @@ -848,7 +848,7 @@ impl BlockChainClient for Client { difficulty: header.difficulty(), last_hashes: last_hashes, gas_used: U256::zero(), - gas_limit: U256::max_value(), + gas_limit: header.gas_limit(), }; // that's just a copy of the state. let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; @@ -873,6 +873,81 @@ impl BlockChainClient for Client { Ok(ret) } + fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result { + 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(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 { let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?; let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; @@ -1315,11 +1390,6 @@ impl BlockChainClient for Client { } } - fn broadcast_consensus_message(&self, message: Bytes) { - self.notify(|notify| notify.broadcast(message.clone())); - } - - fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } @@ -1414,16 +1484,6 @@ impl MiningBlockChainClient for Client { &self.factories.vm } - fn update_sealing(&self) { - self.miner.update_sealing(self) - } - - fn submit_seal(&self, block_hash: H256, seal: Vec) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") - } - } - fn broadcast_proposal_block(&self, block: SealedBlock) { self.notify(|notify| { notify.new_blocks( @@ -1471,6 +1531,22 @@ impl MiningBlockChainClient for Client { } } +impl EngineClient for Client { + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + 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(&self, closure: F) where F: OnPanicListener { self.panic_handler.on_panic(closure); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index d8e5f19e5..8af20f516 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -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; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index da7425a3d..22e61ab09 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -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) { - 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 { + Ok(21000.into()) + } + fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result { 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 { 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
{ None } } + +impl EngineClient for TestBlockChainClient { + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + 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) {} +} diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7e39d5002..8122aadef 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -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; + /// Estimates how much gas will be necessary for a call. + fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result; + /// Replays a given transaction for inspection. fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result; @@ -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; @@ -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); - /// 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); + + /// 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. diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 72dacddf4..0986a5aa7 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -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
, - /// Number of authorities. - pub authority_n: usize, + /// Block reward. + pub block_reward: U256, /// Starting step, pub start_step: Option, + /// Valid validators. + pub validators: ethjson::spec::ValidatorSet, } impl From for AuthorityRoundParams { @@ -58,8 +60,8 @@ impl From 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::>(), + 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 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, transition_service: IoService<()>, - message_channel: Mutex>>, step: AtomicUsize, proposed: AtomicBool, - account_provider: Mutex>>, + client: RwLock>>, + account_provider: Mutex>, password: RwLock>, + validators: Box, } fn header_step(header: &Header) -> Result { @@ -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 { - 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) { - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak) { + *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) { - *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()); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 8112a574e..61e25e58f 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -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
, + pub validators: ethjson::spec::ValidatorSet, } impl From 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::>(), + validators: p.validators, } } } @@ -56,10 +55,11 @@ impl From 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, account_provider: Mutex>>, password: RwLock>, + validators: Box, } impl BasicAuthority { @@ -67,8 +67,9 @@ impl BasicAuthority { pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap) -> 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 { - 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::()?; 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) { + self.validators.register_call_contract(client); + } + fn set_signer(&self, _address: Address, password: String) { *self.password.write() = Some(password); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index a3e57dd65..b82fce1b8 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -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) {} + /// Add Client which can be used for sealing, querying the state and sending messages. + fn register_client(&self, _client: Weak) {} /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc) {} + /// 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) {} } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e124445e9..5fe9a0248 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -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, step_service: IoService, + client: RwLock>>, + block_reward: U256, /// Address to be used as authority. authority: RwLock
, /// Password used for signing messages. @@ -89,8 +94,6 @@ pub struct Tendermint { step: RwLock, /// Vote accumulator. votes: VoteCollector, - /// Channel for updating the sealing. - message_channel: Mutex>>, /// Used to sign messages and proposals. account_provider: Mutex>>, /// 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>, + /// Set used to determine the current validators. + validators: Box, } 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::::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) { - 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) { - trace!(target: "poa", "Register the IoChannel."); - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak) { + *self.client.write() = Some(client.clone()); + self.validators.register_call_contract(client); } fn register_account_provider(&self, account_provider: Arc) { @@ -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) { 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) { + fn propose_default(spec: &Spec, proposer: Address) -> (ClosedBlock, Vec) { 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(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) -> Bytes where F: FnOnce(H256) -> Result { + fn vote(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option) -> Bytes where F: FnOnce(H256) -> Result { 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, height: Height, round: Round, bare_hash: Option, 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, 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, engine: &Arc, acc: &str) -> Address { + fn insert_and_register(tap: &Arc, engine: &Engine, acc: &str) -> Address { let addr = insert_and_unlock(tap, acc); engine.set_signer(addr.clone(), acc.into()); addr } - struct TestIo { - received: RwLock> + #[derive(Default)] + struct TestNotify { + messages: RwLock>, } - impl TestIo { - fn new() -> Arc { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) } - } - - impl IoHandler for TestIo { - fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - self.received.write().push(net_message.clone()); + impl ChainNotify for TestNotify { + fn broadcast(&self, data: Vec) { + 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>))); - 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>))); - 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(); } } diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index cf723713b..aad3a4b7a 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -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
, - /// 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 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), } } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 83b390d74..62ae7b03f 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -23,7 +23,17 @@ use super::{Tendermint, Step}; use engines::Engine; pub struct TransitionHandler { - pub engine: Weak, + engine: Weak, + timeouts: TendermintTimeouts, +} + +impl TransitionHandler { + pub fn new(engine: Weak, timeouts: TendermintTimeouts) -> Self { + TransitionHandler { + engine: engine, + timeouts: timeouts, + } + } } /// Base timeout of each step in ms. @@ -67,9 +77,7 @@ fn set_timeout(io: &IoContext, timeout: Duration) { impl IoHandler for TransitionHandler { fn initialize(&self, io: &IoContext) { - 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, timer: TimerToken) { @@ -81,16 +89,14 @@ impl IoHandler for TransitionHandler { } fn message(&self, io: &IoContext, 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), + }; } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs new file mode 100644 index 000000000..b8b63112c --- /dev/null +++ b/ethcore/src/engines/validator_set/contract.rs @@ -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 . + +/// 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, + provider: RwLock>, +} + +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, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _duration: u64) { + self.update(); + } +} + +impl ValidatorSet for Arc { + 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) { + 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) -> Result, String> + Send + Sync + 'static>, + } + impl Contract { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, 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(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, 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::>(); + 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::>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::>() })) + } + } +} + +#[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); + } +} diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs new file mode 100644 index 000000000..566dccabb --- /dev/null +++ b/ethcore/src/engines/validator_set/mod.rs @@ -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 . + +/// 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 { + 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) {} +} diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs new file mode 100644 index 000000000..9244ce477 --- /dev/null +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -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 . + +/// Preconfigured validator list. + +use util::Address; +use super::ValidatorSet; + +#[derive(Debug, PartialEq, Eq, Default)] +pub struct SimpleList { + validators: Vec
, + validator_n: usize, +} + +impl SimpleList { + pub fn new(validators: Vec
) -> 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); + } +} diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 57c63d7db..72427c668 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -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, diff --git a/ethcore/src/json_tests/trie.rs b/ethcore/src/json_tests/trie.rs index 065985b0a..9107675d5 100644 --- a/ethcore/src/json_tests/trie.rs +++ b/ethcore/src/json_tests/trie.rs @@ -33,7 +33,7 @@ fn test_trie(json: &[u8], trie: TrieSpec) -> Vec { let key: Vec = key.into(); let value: Vec = 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() { diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs index 1fe345614..7d5f5c299 100644 --- a/ethcore/src/miner/banning_queue.rs +++ b/ethcore/src/miner/banning_queue.rs @@ -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( &mut self, transaction: SignedTransaction, + time: QueuingInstant, account_details: &F, gas_estimator: &G, ) -> Result 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."); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c11f34ecb..59237ed6a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -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 { + fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result { 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 { diff --git a/ethcore/src/miner/price_info.rs b/ethcore/src/miner/price_info.rs index 897440ebe..9cd97e287 100644 --- a/ethcore/src/miner/price_info.rs +++ b/ethcore/src/miner/price_info.rs @@ -82,7 +82,7 @@ impl PriceInfo { } } -#[test] +#[test] #[ignore] fn should_get_price_info() { use std::sync::Arc; use std::time::Duration; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index a51c70bc0..fe3af79dc 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -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, } impl VerifiedTransaction { - fn new(transaction: SignedTransaction, origin: TransactionOrigin, min_block: Option) -> Result { + fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option) -> Result { 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, 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, 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(&mut self, fetch_nonce: F) where - F: Fn(&Address) -> U256, + pub fn remove_old(&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::>(); + .map(|sender| (*sender, fetch_account(sender))) + .collect::>(); - 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::>(); + 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(&mut self, transaction_hash: &H256, fetch_account: &T) - where T: Fn(&Address) -> AccountDetails { + pub fn remove_invalid(&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 { - 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]); + } + } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b0f1df420..d55a228e4 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -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), - /// 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 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); }, diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f9a4b6f6a..2f957098d 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -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") } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 63e8ff9de..87b52a894 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -178,8 +178,8 @@ impl Account { SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \ using it will not fail."); - let item: U256 = match db.get(key){ - Ok(x) => x.map_or_else(U256::zero, |v| decode(&*v)), + let item: U256 = match db.get_with(key, ::rlp::decode) { + Ok(x) => x.unwrap_or_else(U256::zero), Err(e) => panic!("Encountered potential DB corruption: {}", e), }; let value: H256 = item.into(); @@ -453,12 +453,12 @@ impl Account { /// omitted. pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result, Box> { 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()) } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index c9730c1c3..cfd53053e 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -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, Box> { - 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, Box> { 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), }; diff --git a/ethcore/src/types/trace_types/trace.rs b/ethcore/src/types/trace_types/trace.rs index 2636caa52..619e1bd9f 100644 --- a/ethcore/src/types/trace_types/trace.rs +++ b/ethcore/src/types/trace_types/trace.rs @@ -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) } } - diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 4bd34da18..8dd7391bb 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -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(decoder: &D) -> Result where D: Decoder { let d = decoder.as_rlp(); diff --git a/evmjit/Cargo.toml b/evmjit/Cargo.toml index a849b9323..0d5d16ea1 100644 --- a/evmjit/Cargo.toml +++ b/evmjit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evmjit" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies "] [lib] diff --git a/hash-fetch/Cargo.toml b/hash-fetch/Cargo.toml index 328d53eb1..48d0a3f36 100644 --- a/hash-fetch/Cargo.toml +++ b/hash-fetch/Cargo.toml @@ -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 "] [dependencies] diff --git a/ipc-common-types/Cargo.toml b/ipc-common-types/Cargo.toml index a483edadc..9b8316ef3 100644 --- a/ipc-common-types/Cargo.toml +++ b/ipc-common-types/Cargo.toml @@ -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 "] build = "build.rs" diff --git a/ipc/codegen/Cargo.toml b/ipc/codegen/Cargo.toml index 5717453ef..a257fd98c 100644 --- a/ipc/codegen/Cargo.toml +++ b/ipc/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-codegen" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies "] license = "GPL-3.0" description = "Macros to auto-generate implementations for ipc call" diff --git a/ipc/nano/Cargo.toml b/ipc/nano/Cargo.toml index 7d559dbab..07ebf41ef 100644 --- a/ipc/nano/Cargo.toml +++ b/ipc/nano/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-nano" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml index a89d1a9ee..fdfc4ba80 100644 --- a/ipc/rpc/Cargo.toml +++ b/ipc/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/js/package.json b/js/package.json index 79e8d473e..413d1515a 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.168", + "version": "0.2.182", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -139,7 +139,6 @@ "blockies": "0.0.2", "brace": "0.9.0", "bytes": "2.4.0", - "crypto-js": "3.1.9-1", "debounce": "1.0.0", "es6-error": "4.0.0", "es6-promise": "4.0.5", @@ -153,6 +152,7 @@ "isomorphic-fetch": "2.2.1", "js-sha3": "0.5.5", "lodash": "4.17.2", + "loglevel": "1.4.1", "marked": "0.3.6", "material-ui": "0.16.5", "material-ui-chip-input": "0.11.1", diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index af22191e5..9c3b02b72 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -75,6 +75,10 @@ export default class Contract { return this._functions; } + get receipt () { + return this._receipt; + } + get instance () { this._instance.address = this._address; return this._instance; @@ -139,6 +143,7 @@ export default class Contract { } setState({ state: 'hasReceipt', receipt }); + this._receipt = receipt; this._address = receipt.contractAddress; return this._address; }); @@ -218,14 +223,19 @@ export default class Contract { } _encodeOptions (func, options, values) { - options.data = this.getCallData(func, options, values); - return options; + const data = this.getCallData(func, options, values); + + return { + ...options, + data + }; } _addOptionsTo (options = {}) { - return Object.assign({ - to: this._address - }, options); + return { + to: this._address, + ...options + }; } _bindFunction = (func) => { diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 87a7cf558..0258aea58 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -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/); }); diff --git a/js/src/api/util/format.js b/js/src/api/util/format.js index 531dfc549..f6976c13b 100644 --- a/js/src/api/util/format.js +++ b/js/src/api/util/format.js @@ -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; } diff --git a/js/src/api/util/format.spec.js b/js/src/api/util/format.spec.js index cfb07dee7..384c26d64 100644 --- a/js/src/api/util/format.spec.js +++ b/js/src/api/util/format.spec.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -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'); + }); + }); }); diff --git a/js/src/api/util/index.js b/js/src/api/util/index.js index e33bb9273..414816cdd 100644 --- a/js/src/api/util/index.js +++ b/js/src/api/util/index.js @@ -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, diff --git a/js/src/api/util/sha3.js b/js/src/api/util/sha3.js index 5a2c7c273..f07f88068 100644 --- a/js/src/api/util/sha3.js +++ b/js/src/api/util/sha3.js @@ -14,21 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import CryptoJS from 'crypto-js'; -import CryptoSha3 from 'crypto-js/sha3'; +import { keccak_256 } from 'js-sha3'; // eslint-disable-line + +import { hexToBytes } from './format'; +import { isHex } from './types'; export function sha3 (value, options) { - if (options && options.encoding === 'hex') { - if (value.length > 2 && value.substr(0, 2) === '0x') { - value = value.substr(2); - } + const forceHex = options && options.encoding === 'hex'; - value = CryptoJS.enc.Hex.parse(value); + if (forceHex || (!options && isHex(value))) { + const bytes = hexToBytes(value); + return sha3(bytes); } - const hash = CryptoSha3(value, { - outputLength: 256 - }).toString(); + const hash = keccak_256(value); return `0x${hash}`; } + +sha3.text = (val) => sha3(val, { encoding: 'raw' }); diff --git a/js/src/api/util/sha3.spec.js b/js/src/api/util/sha3.spec.js index 20cd0e7d5..2945ce51a 100644 --- a/js/src/api/util/sha3.spec.js +++ b/js/src/api/util/sha3.spec.js @@ -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'); + }); }); }); diff --git a/js/src/api/util/types.js b/js/src/api/util/types.js index 6fe442e93..96d7251d5 100644 --- a/js/src/api/util/types.js +++ b/js/src/api/util/types.js @@ -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)); } diff --git a/js/src/api/util/types.spec.js b/js/src/api/util/types.spec.js index df9bf2f1f..1436eb160 100644 --- a/js/src/api/util/types.spec.js +++ b/js/src/api/util/types.spec.js @@ -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', () => { diff --git a/js/src/config.js b/js/src/config.js new file mode 100644 index 000000000..87fedecb8 --- /dev/null +++ b/js/src/config.js @@ -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 . + +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); +}; diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 370236a26..0813caa47 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -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; diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js index 04c562f50..ffb14c626 100644 --- a/js/src/contracts/registry.js +++ b/js/src/contracts/registry.js @@ -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']); diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js index 05b7ea35f..3940e0e18 100644 --- a/js/src/contracts/verification.js +++ b/js/src/contracts/verification.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -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 }); diff --git a/js/src/dapps/basiccoin/services.js b/js/src/dapps/basiccoin/services.js index f87259f30..0344df805 100644 --- a/js/src/dapps/basiccoin/services.js +++ b/js/src/dapps/basiccoin/services.js @@ -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; diff --git a/js/src/dapps/registry/Lookup/actions.js b/js/src/dapps/registry/Lookup/actions.js index 1e8ed5898..3a8ef515e 100644 --- a/js/src/dapps/registry/Lookup/actions.js +++ b/js/src/dapps/registry/Lookup/actions.js @@ -39,7 +39,7 @@ export const lookup = (name, key) => (dispatch, getState) => { name = name.toLowerCase(); dispatch(lookupStart(name, key)); - getAddress.call({}, [ sha3(name), key ]) + getAddress.call({}, [ sha3.text(name), key ]) .then((address) => dispatch(success('lookup', address))) .catch((err) => { console.error(`could not lookup ${key} for ${name}`); diff --git a/js/src/dapps/registry/Names/actions.js b/js/src/dapps/registry/Names/actions.js index 2396278cb..12600ff80 100644 --- a/js/src/dapps/registry/Names/actions.js +++ b/js/src/dapps/registry/Names/actions.js @@ -62,7 +62,7 @@ export const reserve = (name) => (dispatch, getState) => { value: fee }; const values = [ - sha3(name) + sha3.text(name) ]; return postTx(api, reserve, options, values); @@ -116,7 +116,7 @@ export const drop = (name) => (dispatch, getState) => { }; const values = [ - sha3(name) + sha3.text(name) ]; return postTx(api, drop, options, values); diff --git a/js/src/dapps/registry/Records/actions.js b/js/src/dapps/registry/Records/actions.js index f85304d5f..8318c9dcc 100644 --- a/js/src/dapps/registry/Records/actions.js +++ b/js/src/dapps/registry/Records/actions.js @@ -54,7 +54,7 @@ export const update = (name, key, value) => (dispatch, getState) => { }; const values = [ - sha3(name), + sha3.text(name), key, value ]; diff --git a/js/src/dapps/registry/util/registry.js b/js/src/dapps/registry/util/registry.js index 371b29aec..ed3039b4d 100644 --- a/js/src/dapps/registry/util/registry.js +++ b/js/src/dapps/registry/util/registry.js @@ -17,7 +17,7 @@ export const getOwner = (contract, name) => { const { address, api } = contract; - const key = api.util.sha3(name) + '0000000000000000000000000000000000000000000000000000000000000001'; + const key = api.util.sha3.text(name) + '0000000000000000000000000000000000000000000000000000000000000001'; const position = api.util.sha3(key, { encoding: 'hex' }); return api diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index c0bcca91a..ed2c24612 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -40,7 +40,7 @@ export default class CreateAccount extends Component { accountNameError: ERRORS.noName, accounts: null, isValidName: false, - isValidPass: false, + isValidPass: true, passwordHint: '', password1: '', password1Error: null, diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 17a1cfd44..91ef39d95 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -37,7 +37,7 @@ export default class NewImport extends Component { accountName: '', accountNameError: ERRORS.noName, isValidFile: false, - isValidPass: false, + isValidPass: true, isValidName: false, password: '', passwordError: null, diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index f284cf323..d0b3a4c71 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -36,7 +36,7 @@ export default class RawKey extends Component { accountNameError: ERRORS.noName, isValidKey: false, isValidName: false, - isValidPass: false, + isValidPass: true, passwordHint: '', password1: '', password1Error: null, @@ -119,8 +119,6 @@ export default class RawKey extends Component { const rawKey = event.target.value; let rawKeyError = null; - console.log(rawKey.length, rawKey); - if (!rawKey || !rawKey.trim().length) { rawKeyError = ERRORS.noKey; } else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !api.util.isHex(rawKey)) { diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index fd5043024..dc80e27ae 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -31,9 +31,9 @@ export default class RecoveryPhrase extends Component { state = { accountName: '', accountNameError: ERRORS.noName, - isValidPass: false, + isValidPass: true, isValidName: false, - isValidPhrase: false, + isValidPhrase: true, passwordHint: '', password1: '', password1Error: null, diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index 569d374cc..53be1f918 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -240,6 +240,7 @@ export default class CreateAccount extends Component { if (createType === 'fromNew' || createType === 'fromPhrase') { let phrase = this.state.phrase; + if (createType === 'fromPhrase' && windowsPhrase) { phrase = phrase .split(' ') // get the words @@ -271,7 +272,9 @@ export default class CreateAccount extends Component { this.newError(error); }); - } else if (createType === 'fromRaw') { + } + + if (createType === 'fromRaw') { return api.parity .newAccountFromSecret(this.state.rawKey, this.state.password) .then((address) => { @@ -296,7 +299,9 @@ export default class CreateAccount extends Component { this.newError(error); }); - } else if (createType === 'fromGeth') { + } + + if (createType === 'fromGeth') { return api.parity .importGethAccounts(this.state.gethAddresses) .then((result) => { diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 4d9c4f40e..1bda0dddf 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -441,9 +441,7 @@ class DeployContract extends Component { } onCodeChange = (code) => { - const { api } = this.context; - - this.setState(validateCode(code, api), this.estimateGas); + this.setState(validateCode(code), this.estimateGas); } onDeployStart = () => { @@ -457,10 +455,15 @@ class DeployContract extends Component { this.setState({ step: 'DEPLOYMENT' }); - api - .newContract(abiParsed) + const contract = api.newContract(abiParsed); + + contract .deploy(options, params, this.onDeploymentState) .then((address) => { + const blockNumber = contract._receipt + ? contract.receipt.blockNumber.toNumber() + : null; + return Promise.all([ api.parity.setAccountName(address, name), api.parity.setAccountMeta(address, { @@ -468,8 +471,9 @@ class DeployContract extends Component { contract: true, timestamp: Date.now(), deleted: false, - source, - description + blockNumber, + description, + source }) ]) .then(() => { diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js index 3622b9805..060b25c17 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js @@ -14,15 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; import sinon from 'sinon'; -import { ContextProvider, muiTheme } from '~/ui'; - import DetailsStep from './'; -import { createApi, STORE, CONTRACT } from '../executeContract.test.js'; +import { CONTRACT } from '../executeContract.test.js'; let component; let onAmountChange; @@ -40,18 +38,16 @@ function render (props) { onGasEditClick = sinon.stub(); onValueChange = sinon.stub(); - component = mount( - - - + component = shallow( + ); return component; @@ -74,7 +70,7 @@ describe('modals/ExecuteContract/DetailsStep', () => { describe('bool parameters', () => { it('toggles from false to true', () => { - component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } }); + component.find('TypedInput').last().shallow().simulate('change', { target: { value: 'true' } }); expect(onValueChange).to.have.been.calledWith(null, 0, true); }); diff --git a/js/src/modals/ExecuteContract/executeContract.test.js b/js/src/modals/ExecuteContract/executeContract.test.js index ce196408d..ac1138a17 100644 --- a/js/src/modals/ExecuteContract/executeContract.test.js +++ b/js/src/modals/ExecuteContract/executeContract.test.js @@ -59,19 +59,22 @@ const STORE = { }, settings: { backgroundSeed: '' + }, + registry: { + reverse: {} } }; } }; function createApi (result = true) { + const sha3 = sinon.stub().resolves('0x0000000000000000000000000000000000000000'); + sha3.text = sha3; return { parity: { registryAddress: sinon.stub().resolves('0x0000000000000000000000000000000000000000') }, - util: { - sha3: sinon.stub().resolves('0x0000000000000000000000000000000000000000') - } + util: { sha3 } }; } diff --git a/js/src/modals/PasswordManager/store.js b/js/src/modals/PasswordManager/store.js index c60576e0f..659543c28 100644 --- a/js/src/modals/PasswordManager/store.js +++ b/js/src/modals/PasswordManager/store.js @@ -133,7 +133,7 @@ export default class Store { } testPassword = (password) => { - this.setBusy(false); + this.setBusy(true); return this._api.parity .testPassword(this.address, password || this.validatePassword) diff --git a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js index 8dfd29f33..3644852e4 100644 --- a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js +++ b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js @@ -14,25 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; - import styles from '../shapeshift.css'; +@observer export default class AwaitingDepositStep extends Component { static propTypes = { - coinSymbol: PropTypes.string.isRequired, - depositAddress: PropTypes.string, - price: PropTypes.shape({ - rate: PropTypes.number.isRequired, - minimum: PropTypes.number.isRequired, - limit: PropTypes.number.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { coinSymbol, depositAddress, price } = this.props; + const { coinSymbol, depositAddress, price } = this.props.store; const typeSymbol = (
{ coinSymbol } @@ -43,22 +39,38 @@ export default class AwaitingDepositStep extends Component { return (
- Awaiting confirmation of the deposit address for your { typeSymbol } funds exchange +
); } + return (
- ShapeShift.io is awaiting a { typeSymbol } deposit. Send the funds from your { typeSymbol } network client to - + ShapeShift.io, + typeSymbol + } } />
{ depositAddress }
- ( minimum, maximum) + , + minimum: + } } />
diff --git a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js new file mode 100644 index 000000000..65fdefb07 --- /dev/null +++ b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js @@ -0,0 +1,50 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import AwaitingDepositStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/AwaitingDepositStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + it('displays waiting for address with empty depositAddress', () => { + render(); + expect(component.find('FormattedMessage').props().id).to.match(/awaitingConfirmation/); + }); + + it('displays waiting for deposit with non-empty depositAddress', () => { + render({ depositAddress: 'xyz' }); + expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/); + }); +}); diff --git a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js index d3e0ce93c..d3760355f 100644 --- a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js +++ b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js @@ -15,32 +15,39 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import Value from '../Value'; +import { FormattedMessage } from 'react-intl'; +import { observer } from 'mobx-react'; +import Value from '../Value'; import styles from '../shapeshift.css'; +@observer export default class AwaitingExchangeStep extends Component { static propTypes = { - depositInfo: PropTypes.shape({ - incomingCoin: PropTypes.number.isRequired, - incomingType: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { depositInfo } = this.props; + const { depositInfo } = this.props.store; const { incomingCoin, incomingType } = depositInfo; return (
- ShapeShift.io has received a deposit of - + ShapeShift.io + } } />
- Awaiting the completion of the funds exchange and transfer of funds to your Parity account. +
); diff --git a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js new file mode 100644 index 000000000..a9ed6e5f2 --- /dev/null +++ b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js @@ -0,0 +1,39 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import AwaitingExchangeStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/AwaitingExchangeStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/CompletedStep/completedStep.js b/js/src/modals/Shapeshift/CompletedStep/completedStep.js index 2b5a6b162..681e26c55 100644 --- a/js/src/modals/Shapeshift/CompletedStep/completedStep.js +++ b/js/src/modals/Shapeshift/CompletedStep/completedStep.js @@ -14,39 +14,41 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; - import styles from '../shapeshift.css'; +@observer export default class CompletedStep extends Component { static propTypes = { - depositInfo: PropTypes.shape({ - incomingCoin: PropTypes.number.isRequired, - incomingType: PropTypes.string.isRequired - }).isRequired, - exchangeInfo: PropTypes.shape({ - outgoingCoin: PropTypes.string.isRequired, - outgoingType: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { depositInfo, exchangeInfo } = this.props; + const { depositInfo, exchangeInfo } = this.props.store; const { incomingCoin, incomingType } = depositInfo; const { outgoingCoin, outgoingType } = exchangeInfo; return (
- ShapeShift.io has completed the funds exchange. + ShapeShift.io + } } />
=>
- The change in funds will be reflected in your Parity account shortly. +
); diff --git a/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js b/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js new file mode 100644 index 000000000..c14da892a --- /dev/null +++ b/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js @@ -0,0 +1,40 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import CompletedStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/CompletedStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/ErrorStep/errorStep.js b/js/src/modals/Shapeshift/ErrorStep/errorStep.js index 092494399..441904f22 100644 --- a/js/src/modals/Shapeshift/ErrorStep/errorStep.js +++ b/js/src/modals/Shapeshift/ErrorStep/errorStep.js @@ -14,25 +14,30 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import styles from '../shapeshift.css'; +@observer export default class ErrorStep extends Component { static propTypes = { - error: PropTypes.shape({ - fatal: PropTypes.bool, - message: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { error } = this.props; + const { error } = this.props.store; return (
- The funds shifting via ShapeShift.io failed with a fatal error on the exchange. The error message received from the exchange is as follow: + ShapeShift.io + } } />
{ error.message } diff --git a/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js b/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js new file mode 100644 index 000000000..7cb148334 --- /dev/null +++ b/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js @@ -0,0 +1,39 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import ErrorStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/ErrorStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/OptionsStep/optionsStep.js b/js/src/modals/Shapeshift/OptionsStep/optionsStep.js index 4314d2b5c..5a7afdf7e 100644 --- a/js/src/modals/Shapeshift/OptionsStep/optionsStep.js +++ b/js/src/modals/Shapeshift/OptionsStep/optionsStep.js @@ -14,64 +14,93 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React, { Component, PropTypes } from 'react'; import { Checkbox, MenuItem } from 'material-ui'; +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; -import { Form, Input, Select } from '~/ui'; +import { Form, Input, Select, Warning } from '~/ui'; import Price from '../Price'; - +import { WARNING_NO_PRICE } from '../store'; import styles from './optionsStep.css'; +const WARNING_LABELS = { + [WARNING_NO_PRICE]: ( + + ) +}; + +@observer export default class OptionsStep extends Component { static propTypes = { - refundAddress: PropTypes.string.isRequired, - coinSymbol: PropTypes.string.isRequired, - coins: PropTypes.array.isRequired, - price: PropTypes.object, - hasAccepted: PropTypes.bool.isRequired, - onChangeSymbol: PropTypes.func.isRequired, - onChangeRefund: PropTypes.func.isRequired, - onToggleAccept: PropTypes.func.isRequired + store: PropTypes.object.isRequired }; render () { - const { coinSymbol, coins, refundAddress, hasAccepted, onToggleAccept } = this.props; - const label = `(optional) ${coinSymbol} return address`; + const { coinSymbol, coins, hasAcceptedTerms, price, refundAddress, warning } = this.props.store; if (!coins.length) { return (
- There are currently no exchange pairs/coins available to fund with. +
); } - const items = coins.map(this.renderCoinSelectItem); - return (
+ hint={ + + } + label={ + + } + onSubmit={ this.onChangeRefundAddress } + value={ refundAddress } /> + label={ + + } + onCheck={ this.onToggleAcceptTerms } /> - + +
); } @@ -81,7 +110,9 @@ export default class OptionsStep extends Component { const item = (
- +
{ symbol } @@ -103,11 +134,15 @@ export default class OptionsStep extends Component { ); } - onSelectCoin = (event, idx, value) => { - this.props.onChangeSymbol(event, value); + onChangeRefundAddress = (event, refundAddress) => { + this.props.store.setRefundAddress(refundAddress); } - onChangeAddress = (event, value) => { - this.props.onChangeRefund(value); + onSelectCoin = (event, index, coinSymbol) => { + this.props.store.setCoinSymbol(coinSymbol); + } + + onToggleAcceptTerms = () => { + this.props.store.toggleAcceptTerms(); } } diff --git a/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js b/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js new file mode 100644 index 000000000..3b49d90de --- /dev/null +++ b/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js @@ -0,0 +1,126 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Store, { WARNING_NO_PRICE } from '../store'; + +import OptionsStep from './'; + +const ADDRESS = '0x1234567890123456789012345678901234567890'; + +let component; +let instance; +let store; + +function render () { + store = new Store(ADDRESS); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/Shapeshift/OptionsStep', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders no coins when none available', () => { + expect(component.find('FormattedMessage').props().id).to.equal('shapeshift.optionsStep.noPairs'); + }); + + describe('components', () => { + beforeEach(() => { + store.setCoins([{ symbol: 'BTC', name: 'Bitcoin' }]); + store.toggleAcceptTerms(); + }); + + describe('terms Checkbox', () => { + it('shows the state of store.hasAcceptedTerms', () => { + expect(component.find('Checkbox').props().checked).to.be.true; + }); + }); + + describe('warning', () => { + let warning; + + beforeEach(() => { + store.setWarning(WARNING_NO_PRICE); + warning = component.find('Warning'); + }); + + it('shows a warning message when available', () => { + expect(warning.props().warning.props.id).to.equal('shapeshift.warning.noPrice'); + }); + }); + }); + + describe('events', () => { + describe('onChangeRefundAddress', () => { + beforeEach(() => { + sinon.stub(store, 'setRefundAddress'); + }); + + afterEach(() => { + store.setRefundAddress.restore(); + }); + + it('sets the refundAddress on the store', () => { + instance.onChangeRefundAddress(null, 'refundAddress'); + expect(store.setRefundAddress).to.have.been.calledWith('refundAddress'); + }); + }); + + describe('onSelectCoin', () => { + beforeEach(() => { + sinon.stub(store, 'setCoinSymbol'); + }); + + afterEach(() => { + store.setCoinSymbol.restore(); + }); + + it('sets the coinSymbol on the store', () => { + instance.onSelectCoin(null, 0, 'XMR'); + expect(store.setCoinSymbol).to.have.been.calledWith('XMR'); + }); + }); + + describe('onToggleAcceptTerms', () => { + beforeEach(() => { + sinon.stub(store, 'toggleAcceptTerms'); + }); + + afterEach(() => { + store.toggleAcceptTerms.restore(); + }); + + it('toggles the terms on the store', () => { + instance.onToggleAcceptTerms(); + expect(store.toggleAcceptTerms).to.have.been.called; + }); + }); + }); +}); diff --git a/js/src/modals/Shapeshift/Price/price.js b/js/src/modals/Shapeshift/Price/price.js index 206587448..e8eb21e52 100644 --- a/js/src/modals/Shapeshift/Price/price.js +++ b/js/src/modals/Shapeshift/Price/price.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; import styles from '../shapeshift.css'; @@ -42,7 +43,13 @@ export default class Price extends Component { =
- ( minimum, maximum) + , + minimum: + } } />
); diff --git a/js/src/modals/Shapeshift/Price/price.spec.js b/js/src/modals/Shapeshift/Price/price.spec.js new file mode 100644 index 000000000..9b144746b --- /dev/null +++ b/js/src/modals/Shapeshift/Price/price.spec.js @@ -0,0 +1,40 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Price from './'; + +let component; + +function render (props = {}) { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/Price', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/Value/value.spec.js b/js/src/modals/Shapeshift/Value/value.spec.js new file mode 100644 index 000000000..8c5ab5d9c --- /dev/null +++ b/js/src/modals/Shapeshift/Value/value.spec.js @@ -0,0 +1,36 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Value from './'; + +let component; + +function render (props = {}) { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/Value', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/shapeshift.js b/js/src/modals/Shapeshift/shapeshift.js index fc76a8968..bf450122d 100644 --- a/js/src/modals/Shapeshift/shapeshift.js +++ b/js/src/modals/Shapeshift/shapeshift.js @@ -14,26 +14,44 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; -import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; -import ContentClear from 'material-ui/svg-icons/content/clear'; +import { FormattedMessage } from 'react-intl'; +import shapeshiftLogo from '~/../assets/images/shapeshift-logo.png'; import { Button, IdentityIcon, Modal } from '~/ui'; -import initShapeshift from '~/3rdparty/shapeshift'; -import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; +import { CancelIcon, DoneIcon } from '~/ui/Icons'; import AwaitingDepositStep from './AwaitingDepositStep'; import AwaitingExchangeStep from './AwaitingExchangeStep'; import CompletedStep from './CompletedStep'; import ErrorStep from './ErrorStep'; import OptionsStep from './OptionsStep'; +import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store'; import styles from './shapeshift.css'; -const shapeshift = initShapeshift(); - -const STAGE_NAMES = ['details', 'awaiting deposit', 'awaiting exchange', 'completed']; +const STAGE_TITLES = [ + , + , + , + +]; +const ERROR_TITLE = ( + +); +@observer export default class Shapeshift extends Component { static contextTypes = { store: PropTypes.object.isRequired @@ -44,46 +62,38 @@ export default class Shapeshift extends Component { onClose: PropTypes.func } - state = { - stage: 0, - coinSymbol: 'BTC', - coinPair: 'btc_eth', - coins: [], - depositAddress: '', - refundAddress: '', - price: null, - depositInfo: null, - exchangeInfo: null, - error: {}, - hasAccepted: false, - shifting: false - } + store = new Store(this.props.address); componentDidMount () { - this.retrieveCoins(); + this.store.retrieveCoins(); } componentWillUnmount () { - this.unsubscribe(); - } - - unsubscribe () { - // Unsubscribe from Shapeshit - const { depositAddress } = this.state; - shapeshift.unsubscribe(depositAddress); + this.store.unsubscribe(); } render () { - const { error, stage } = this.state; + const { error, stage } = this.store; return ( + steps={ + error + ? null + : STAGE_TITLES + } + title={ + error + ? ERROR_TITLE + : null + } + visible + waiting={ [ + STAGE_WAIT_DEPOSIT, + STAGE_WAIT_EXCHANGE + ] }> { this.renderPage() } ); @@ -91,7 +101,7 @@ export default class Shapeshift extends Component { renderDialogActions () { const { address } = this.props; - const { coins, error, stage, hasAccepted, shifting } = this.state; + const { coins, error, hasAcceptedTerms, stage } = this.store; const logo = ( @@ -100,12 +110,16 @@ export default class Shapeshift extends Component { ); const cancelBtn = (
+ ); + } + + renderDescription () { + const { description } = this.props; + + if (!description) { + return null; + } + + const desc = typeof description === 'string' + ? ( + + { description } + + ) + : description; + + return ( +
+ { desc }
); } diff --git a/js/src/ui/Container/Title/title.spec.js b/js/src/ui/Container/Title/title.spec.js index 2d5335c14..957d59a84 100644 --- a/js/src/ui/Container/Title/title.spec.js +++ b/js/src/ui/Container/Title/title.spec.js @@ -15,38 +15,32 @@ // along with Parity. If not, see . import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import Title from './title'; -function renderShallow (props) { +function render (props) { return shallow( ); } -function renderMount (props) { - return mount( - <Title { ...props } /> - ); -} - describe('ui/Container/Title', () => { describe('rendering', () => { it('renders defaults', () => { - expect(renderShallow()).to.be.ok; + expect(render()).to.be.ok; }); it('renders with the specified className', () => { - expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + expect(render({ className: 'testClass' })).to.have.className('testClass'); }); it('renders the specified title', () => { - expect(renderMount({ title: 'titleText' })).to.contain.text('titleText'); + expect(render({ title: 'titleText' })).to.contain.text('titleText'); }); it('renders the specified byline', () => { - expect(renderMount({ byline: 'bylineText' })).to.contain.text('bylineText'); + expect(render({ byline: 'bylineText' })).to.contain.text('bylineText'); }); }); }); diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 692ff4285..7b09b5b07 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -56,6 +56,7 @@ class AddressSelect extends Component { contacts: PropTypes.object, contracts: PropTypes.object, tokens: PropTypes.object, + reverse: PropTypes.object, // Optional props allowCopy: PropTypes.bool, @@ -584,10 +585,12 @@ class AddressSelect extends Component { function mapStateToProps (state) { const { accountsInfo } = state.personal; const { balances } = state.balances; + const { reverse } = state.registry; return { accountsInfo, - balances + balances, + reverse }; } diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 26f9fe80e..e04f8b55d 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -16,7 +16,7 @@ import React from 'react'; import { observable, action } from 'mobx'; -import { flatMap } from 'lodash'; +import { flatMap, uniqBy } from 'lodash'; import { FormattedMessage } from 'react-intl'; import Contracts from '~/contracts'; @@ -30,7 +30,48 @@ export default class AddressSelectStore { @observable registryValues = []; initValues = []; - regLookups = []; + regLookups = [ + (query) => { + query = query.toLowerCase().trim(); + if (query.length === 0 || query === '0x') { + return null; + } + const startsWithQuery = (s) => new RegExp('^' + query, 'i').test(s); + + let address; + let name = this.reverse[query]; + + if (!name) { + const addr = Object + .keys(this.reverse) + .find((addr) => { + const name = this.reverse[addr]; + return startsWithQuery(addr) || (name && startsWithQuery(name)); + }); + + if (addr) { + address = addr; + name = this.reverse[addr]; + } else { + return null; + } + } + + return { + address, + name, + description: ( + <FormattedMessage + id='addressSelect.fromRegistry' + defaultMessage='{name} (from registry)' + values={ { + name + } } + /> + ) + }; + } + ]; constructor (api) { this.api = api; @@ -44,7 +85,7 @@ export default class AddressSelectStore { return emailVerification .instance .reverse - .call({}, [ sha3(email) ]) + .call({}, [ sha3.text(email) ]) .then((address) => { return { address, @@ -68,7 +109,7 @@ export default class AddressSelectStore { this.regLookups.push((name) => { return registryInstance .getAddress - .call({}, [ sha3(name), 'A' ]) + .call({}, [ sha3.text(name), 'A' ]) .then((address) => { return { address, @@ -114,7 +155,8 @@ export default class AddressSelectStore { } @action setValues (props) { - const { accounts = {}, contracts = {}, contacts = {} } = props; + const { accounts = {}, contracts = {}, contacts = {}, reverse = {} } = props; + this.reverse = reverse; const accountsN = Object.keys(accounts).length; const contractsN = Object.keys(contracts).length; @@ -194,6 +236,8 @@ export default class AddressSelectStore { .filter((result) => result && !ZERO.test(result.address)); }) .then((results) => { + results = uniqBy(results, (result) => result.address); + this.registryValues = results .map((result) => { const lowercaseAddress = result.address.toLowerCase(); diff --git a/js/src/ui/Form/InputChip/inputChip.js b/js/src/ui/Form/InputChip/inputChip.js index a12825d71..479ca4b09 100644 --- a/js/src/ui/Form/InputChip/inputChip.js +++ b/js/src/ui/Form/InputChip/inputChip.js @@ -170,6 +170,10 @@ export default class InputChip extends Component { .filter(v => v !== value)); this.handleTokensChange(newTokens); + this.focus(); + } + + focus = () => { this.refs.chipInput.focus(); } diff --git a/js/src/ui/Form/Select/select.js b/js/src/ui/Form/Select/select.js index f79cae58c..fb0940415 100644 --- a/js/src/ui/Form/Select/select.js +++ b/js/src/ui/Form/Select/select.js @@ -15,7 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; -import { SelectField } from 'material-ui'; +import { MenuItem, SelectField } from 'material-ui'; import { nodeOrStringProptype } from '~/util/proptypes'; @@ -42,11 +42,12 @@ export default class Select extends Component { onChange: PropTypes.func, onKeyDown: PropTypes.func, type: PropTypes.string, - value: PropTypes.any + value: PropTypes.any, + values: PropTypes.array } render () { - const { children, className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props; + const { className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props; return ( <SelectField @@ -65,9 +66,36 @@ export default class Select extends Component { onKeyDown={ onKeyDown } underlineDisabledStyle={ UNDERLINE_DISABLED } underlineStyle={ UNDERLINE_NORMAL } - value={ value }> - { children } + value={ value } + > + { this.renderChildren() } </SelectField> ); } + + renderChildren () { + const { children, values } = this.props; + + if (children) { + return children; + } + + if (!values) { + return null; + } + + return values.map((data, index) => { + const { name = index, value = index } = data; + + return ( + <MenuItem + key={ index } + label={ name } + value={ value } + > + { name } + </MenuItem> + ); + }); + } } diff --git a/js/src/ui/Form/TypedInput/typedInput.spec.js b/js/src/ui/Form/TypedInput/typedInput.spec.js index e27c7482a..7cd123d97 100644 --- a/js/src/ui/Form/TypedInput/typedInput.spec.js +++ b/js/src/ui/Form/TypedInput/typedInput.spec.js @@ -14,27 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; import sinon from 'sinon'; -import { ContextProvider, muiTheme } from '~/ui'; import { ABI_TYPES } from '~/util/abi'; import TypedInput from './'; let component; +let select; let onChange; function render (props) { onChange = sinon.stub(); - component = mount( - <ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }> - <TypedInput - { ...props } - onChange={ onChange } /> - </ContextProvider> + component = shallow( + <TypedInput + { ...props } + onChange={ onChange } /> ); + select = component.find('Select'); return component; } @@ -50,19 +49,19 @@ describe('ui/Form/TypedInput', () => { }); it('calls onChange when value changes', () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + select.shallow().simulate('change', { target: { value: 'true' } }); expect(onChange).to.have.been.called; }); it("calls onChange(true) when value changes to 'true'", () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + select.shallow().simulate('change', { target: { value: 'true' } }); expect(onChange).to.have.been.calledWith(true); }); it("calls onChange(false) when value changes to 'false'", () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'false' } }); + select.shallow().simulate('change', { target: { value: 'false' } }); expect(onChange).to.have.been.calledWith(false); }); diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index 1e0f93809..a35bbb610 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -25,6 +25,7 @@ import DashboardIcon from 'material-ui/svg-icons/action/dashboard'; import DeleteIcon from 'material-ui/svg-icons/action/delete'; import DoneIcon from 'material-ui/svg-icons/action/done-all'; import EditIcon from 'material-ui/svg-icons/content/create'; +import LinkIcon from 'material-ui/svg-icons/content/link'; import LockedIcon from 'material-ui/svg-icons/action/lock'; import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; @@ -47,6 +48,7 @@ export { DeleteIcon, DoneIcon, EditIcon, + LinkIcon, LockedIcon, NextIcon, PrevIcon, diff --git a/js/src/ui/IdentityIcon/identityIcon.js b/js/src/ui/IdentityIcon/identityIcon.js index d4d65241a..54a36d500 100644 --- a/js/src/ui/IdentityIcon/identityIcon.js +++ b/js/src/ui/IdentityIcon/identityIcon.js @@ -16,7 +16,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { createIdentityImg } from '~/api/util/identity'; import { isNullAddress } from '~/util/validation'; @@ -145,11 +144,7 @@ function mapStateToProps (state) { return { images }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(IdentityIcon); diff --git a/js/src/ui/IdentityIcon/identityIcon.spec.js b/js/src/ui/IdentityIcon/identityIcon.spec.js index 759907deb..df9665a2f 100644 --- a/js/src/ui/IdentityIcon/identityIcon.spec.js +++ b/js/src/ui/IdentityIcon/identityIcon.spec.js @@ -14,12 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; -import React, { PropTypes } from 'react'; +import { shallow } from 'enzyme'; +import React from 'react'; import sinon from 'sinon'; -import muiTheme from '../Theme'; - import IdentityIcon from './'; const ADDRESS0 = '0x0000000000000000000000000000000000000000'; @@ -27,6 +25,7 @@ const ADDRESS1 = '0x0123456789012345678901234567890123456789'; const ADDRESS2 = '0x9876543210987654321098765432109876543210'; let component; +let instance; function createApi () { return { @@ -53,20 +52,13 @@ function render (props = {}) { props.address = ADDRESS1; } - component = mount( + component = shallow( <IdentityIcon { ...props } />, - { - childContextTypes: { - api: PropTypes.object, - muiTheme: PropTypes.object - }, - context: { - api: createApi(), - muiTheme, - store: createRedux() - } - } - ); + { context: { store: createRedux() } } + ).find('IdentityIcon').shallow({ context: { api: createApi() } }); + + instance = component.instance(); + instance.componentDidMount(); return component; } diff --git a/js/src/ui/IdentityName/identityName.js b/js/src/ui/IdentityName/identityName.js index 980f42638..56118c59a 100644 --- a/js/src/ui/IdentityName/identityName.js +++ b/js/src/ui/IdentityName/identityName.js @@ -17,7 +17,6 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { isNullAddress } from '~/util/validation'; import ShortenedHash from '../ShortenedHash'; @@ -85,11 +84,7 @@ function mapStateToProps (state) { }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(IdentityName); diff --git a/js/src/ui/IdentityName/identityName.spec.js b/js/src/ui/IdentityName/identityName.spec.js index 12bd07363..eafcf91f8 100644 --- a/js/src/ui/IdentityName/identityName.spec.js +++ b/js/src/ui/IdentityName/identityName.spec.js @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; -import { IntlProvider } from 'react-intl'; import sinon from 'sinon'; @@ -45,13 +44,11 @@ const STORE = { }; function render (props) { - return mount( - <IntlProvider locale='en'> - <IdentityName - store={ STORE } - { ...props } /> - </IntlProvider> - ); + return shallow( + <IdentityName + store={ STORE } + { ...props } /> + ).find('IdentityName').shallow(); } describe('ui/IdentityName', () => { @@ -62,23 +59,33 @@ describe('ui/IdentityName', () => { describe('account not found', () => { it('renders null with empty', () => { - expect(render({ address: ADDR_C, empty: true }).html()).to.be.null; + expect( + render({ address: ADDR_C, empty: true }).html() + ).to.be.null; }); it('renders address without empty', () => { - expect(render({ address: ADDR_C }).text()).to.equal(ADDR_C); + expect( + render({ address: ADDR_C }).text() + ).to.equal(ADDR_C); }); it('renders short address with shorten', () => { - expect(render({ address: ADDR_C, shorten: true }).text()).to.equal('123456…56789c'); + expect( + render({ address: ADDR_C, shorten: true }).find('ShortenedHash').props().data + ).to.equal(ADDR_C); }); it('renders unknown with flag', () => { - expect(render({ address: ADDR_C, unknown: true }).text()).to.equal('UNNAMED'); + expect( + render({ address: ADDR_C, unknown: true } + ).find('FormattedMessage').props().id).to.equal('ui.identityName.unnamed'); }); it('renders 0x000...000 as null', () => { - expect(render({ address: ADDR_NULL }).text()).to.equal('NULL'); + expect( + render({ address: ADDR_NULL }).find('FormattedMessage').props().id + ).to.equal('ui.identityName.null'); }); }); }); diff --git a/js/src/ui/MethodDecoding/methodDecoding.css b/js/src/ui/MethodDecoding/methodDecoding.css index adb899e1c..c782d6ce7 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.css +++ b/js/src/ui/MethodDecoding/methodDecoding.css @@ -38,6 +38,10 @@ justify-content: center; } +.details { + line-height: 1.75em; +} + .details, .gasDetails { color: #aaa; diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index a929d2681..693ae60b5 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -167,7 +167,7 @@ class MethodDecoding extends Component { getAscii () { const { api } = this.context; const { transaction } = this.props; - const ascii = api.util.hex2Ascii(transaction.input || transaction.data); + const ascii = api.util.hexToAscii(transaction.input || transaction.data); return { value: ascii, valid: ASCII_INPUT.test(ascii) }; } @@ -196,7 +196,7 @@ class MethodDecoding extends Component { : text.slice(0, 50) + '...'; return ( - <div> + <div className={ styles.details }> <span>with the </span> <span onClick={ this.toggleInputType } diff --git a/js/src/util/wallet.js b/js/src/util/signer.js similarity index 82% rename from js/src/util/wallet.js rename to js/src/util/signer.js index b6240ff01..5d4442094 100644 --- a/js/src/util/wallet.js +++ b/js/src/util/signer.js @@ -15,7 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import scrypt from 'scryptsy'; -import Transaction from 'ethereumjs-tx'; +import * as Transaction from 'ethereumjs-tx'; import { pbkdf2Sync } from 'crypto'; import { createDecipheriv } from 'browserify-aes'; @@ -24,9 +24,26 @@ import { sha3 } from '~/api/util/sha3'; // Adapted from https://github.com/kvhnuke/etherwallet/blob/mercury/app/scripts/myetherwallet.js -export class Wallet { +export class Signer { static fromJson (json, password) { + return Signer + .getSeed(json, password) + .then((seed) => { + return new Signer(seed); + }); + } + + static getSeed (json, password) { + try { + const seed = Signer.getSyncSeed(json, password); + return Promise.resolve(seed); + } catch (error) { + return Promise.reject(error); + } + } + + static getSyncSeed (json, password) { if (json.version !== 3) { throw new Error('Only V3 wallets are supported'); } @@ -43,15 +60,17 @@ export class Wallet { if (kdfparams.prf !== 'hmac-sha256') { throw new Error('Unsupported parameters to PBKDF2'); } + derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256'); } else { throw new Error('Unsupported key derivation scheme'); } const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex'); - let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + if (mac !== inHex(json.crypto.mac)) { - throw new Error('Key derivation failed - possibly wrong passphrase'); + throw new Error('Key derivation failed - possibly wrong password'); } const decipher = createDecipheriv( @@ -59,6 +78,7 @@ export class Wallet { derivedKey.slice(0, 16), Buffer.from(json.crypto.cipherparams.iv, 'hex') ); + let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]); while (seed.length < 32) { @@ -66,7 +86,7 @@ export class Wallet { seed = Buffer.concat([nullBuff, seed]); } - return new Wallet(seed); + return seed; } constructor (seed) { diff --git a/js/src/util/subscribe-to-event.js b/js/src/util/subscribe-to-event.js deleted file mode 100644 index 36d1f6d55..000000000 --- a/js/src/util/subscribe-to-event.js +++ /dev/null @@ -1,77 +0,0 @@ -// 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 EventEmitter from 'eventemitter3'; - -const defaults = { - from: 0, // TODO - to: 'latest', - timeout: null, - filter: () => true -}; - -const subscribeToEvent = (contract, name, opt = {}) => { - opt = Object.assign({}, defaults, opt); - - let subscription = null; - let timeout = null; - - const unsubscribe = () => { - if (subscription) { - contract.unsubscribe(subscription); - subscription = null; - } - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - }; - - const emitter = new EventEmitter(); - emitter.unsubscribe = unsubscribe; - - if (typeof opt.timeout === 'number') { - timeout = setTimeout(() => { - unsubscribe(); - emitter.emit('timeout'); - }, opt.timeout); - } - - const callback = (err, logs) => { - if (err) { - return emitter.emit('error', err); - } - for (let log of logs) { - if (opt.filter(log)) { - emitter.emit('log', log); - } - } - }; - - contract.subscribe(name, { - fromBlock: opt.from, toBlock: opt.to - }, callback) - .then((_subscription) => { - subscription = _subscription; - }) - .catch((err) => { - emitter.emit('error', err); - }); - - return emitter; -}; - -export default subscribeToEvent; diff --git a/js/src/util/subscribe-to-events.js b/js/src/util/subscribe-to-events.js new file mode 100644 index 000000000..48c990277 --- /dev/null +++ b/js/src/util/subscribe-to-events.js @@ -0,0 +1,97 @@ +// 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 EventEmitter from 'eventemitter3'; + +const defaults = { + from: 0, + to: 'latest', + interval: 5000, + filter: () => true +}; + +const subscribeToEvents = (contract, events, opt = {}) => { + const { api } = contract; + opt = Object.assign({}, defaults, opt); + + let filter = null; + let interval = null; + + const unsubscribe = () => { + if (filter) { + filter + .then((filterId) => { + return api.eth.uninstallFilter(filterId); + }) + .catch((err) => { + emitter.emit('error', err); + }); + filter = null; + } + if (interval) { + clearInterval(interval); + interval = null; + } + }; + + const emitter = new EventEmitter(); + emitter.unsubscribe = unsubscribe; + + const fetcher = (method, filterId) => () => { + api + .eth[method](filterId) + .then((logs) => { + logs = contract.parseEventLogs(logs); + + for (let log of logs) { + if (opt.filter(log)) { + emitter.emit('log', log); + emitter.emit(log.event, log); + } + } + }) + .catch((err) => { + emitter.emit('error', err); + }); + }; + + const signatures = events + .filter((event) => contract.instance[event]) + .map((event) => contract.instance[event].signature); + + filter = api.eth + .newFilter({ + fromBlock: opt.from, toBlock: opt.to, + address: contract.address, + topics: [signatures] + }) + .then((filterId) => { + fetcher('getFilterLogs', filterId)(); // fetch immediately + + const fetchChanges = fetcher('getFilterChanges', filterId); + interval = setInterval(fetchChanges, opt.interval); + + return filterId; + }) + .catch((err) => { + emitter.emit('error', err); + throw err; // reject Promise + }); + + return emitter; +}; + +export default subscribeToEvents; diff --git a/js/src/util/validation.js b/js/src/util/validation.js index a789300b4..cb65b662a 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -16,10 +16,12 @@ import BigNumber from 'bignumber.js'; -import util from '~/api/util'; +import apiutil from '~/api/util'; import { NULL_ADDRESS } from './constants'; +// TODO: Convert to FormattedMessages as soon as comfortable with the impact, i.e. errors +// not being concatted into strings in components, all supporting a non-string format export const ERRORS = { invalidAddress: 'address is an invalid network address', invalidAmount: 'the supplied amount should be a valid positive number', @@ -42,9 +44,14 @@ export function validateAbi (abi) { try { abiParsed = JSON.parse(abi); - if (!util.isArray(abiParsed)) { + if (!apiutil.isArray(abiParsed)) { abiError = ERRORS.invalidAbi; - return { abi, abiError, abiParsed }; + + return { + abi, + abiError, + abiParsed + }; } // Validate each elements of the Array @@ -54,8 +61,15 @@ export function validateAbi (abi) { if (invalidIndex !== -1) { const invalid = abiParsed[invalidIndex]; + + // TODO: Needs seperate error when using FormattedMessage (no concats) abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`; - return { abi, abiError, abiParsed }; + + return { + abi, + abiError, + abiParsed + }; } abi = JSON.stringify(abiParsed); @@ -76,7 +90,7 @@ function isValidAbiFunction (object) { } return ((object.type === 'function' && object.name) || object.type === 'constructor') && - (object.inputs && util.isArray(object.inputs)); + (object.inputs && apiutil.isArray(object.inputs)); } function isAbiFallback (object) { @@ -94,7 +108,7 @@ function isValidAbiEvent (object) { return (object.type === 'event') && (object.name) && - (object.inputs && util.isArray(object.inputs)); + (object.inputs && apiutil.isArray(object.inputs)); } export function validateAddress (address) { @@ -102,10 +116,10 @@ export function validateAddress (address) { if (!address) { addressError = ERRORS.invalidAddress; - } else if (!util.isAddressValid(address)) { + } else if (!apiutil.isAddressValid(address)) { addressError = ERRORS.invalidAddress; } else { - address = util.toChecksumAddress(address); + address = apiutil.toChecksumAddress(address); } return { @@ -114,12 +128,12 @@ export function validateAddress (address) { }; } -export function validateCode (code, api) { +export function validateCode (code) { let codeError = null; - if (!code.length) { + if (!code || !code.length) { codeError = ERRORS.invalidCode; - } else if (!api.util.isHex(code)) { + } else if (!apiutil.isHex(code)) { codeError = ERRORS.invalidCode; } @@ -130,7 +144,9 @@ export function validateCode (code, api) { } export function validateName (name) { - const nameError = !name || name.trim().length < 2 ? ERRORS.invalidName : null; + const nameError = !name || name.trim().length < 2 + ? ERRORS.invalidName + : null; return { name, @@ -162,6 +178,7 @@ export function validateUint (value) { try { const bn = new BigNumber(value); + if (bn.lt(0)) { valueError = ERRORS.negativeNumber; } else if (!bn.isInteger()) { diff --git a/js/src/util/validation.spec.js b/js/src/util/validation.spec.js new file mode 100644 index 000000000..e34f3eb3f --- /dev/null +++ b/js/src/util/validation.spec.js @@ -0,0 +1,303 @@ +// 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 BigNumber from 'bignumber.js'; + +import { NULL_ADDRESS } from './constants'; +import { ERRORS, isNullAddress, validateAbi, validateAddress, validateCode, validateName, validatePositiveNumber, validateUint } from './validation'; + +describe('util/validation', () => { + describe('validateAbi', () => { + it('passes on valid ABI', () => { + const abi = '[{"type":"function","name":"test","inputs":[],"outputs":[]}]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: null, + abiParsed: [{ + type: 'function', + name: 'test', + inputs: [], + outputs: [] + }] + }); + }); + + it('passes on valid ABI & trims ABI', () => { + const abi = '[ { "type" : "function" , "name" : "test" , "inputs" : [] , "outputs" : [] } ]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi: '[{"type":"function","name":"test","inputs":[],"outputs":[]}]', + abiError: null, + abiParsed: [{ + type: 'function', + name: 'test', + inputs: [], + outputs: [] + }] + }); + }); + + it('sets error on invalid JSON', () => { + const abi = 'this is not json'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: ERRORS.invalidAbi, + abiParsed: null + }); + }); + + it('sets error on non-array JSON', () => { + const abi = '{}'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: ERRORS.invalidAbi, + abiParsed: {} + }); + }); + + it('fails with invalid event', () => { + const abi = '[{ "type":"event" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: event)`, + abiParsed: [{ type: 'event' }] + }); + }); + + it('fails with invalid function', () => { + const abi = '[{ "type":"function" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: function)`, + abiParsed: [{ type: 'function' }] + }); + }); + + it('fails with unknown type', () => { + const abi = '[{ "type":"somethingElse" }]'; + + expect(validateAbi(abi)).to.deep.equal({ + abi, + abiError: `${ERRORS.invalidAbi} (#0: somethingElse)`, + abiParsed: [{ type: 'somethingElse' }] + }); + }); + }); + + describe('validateAddress', () => { + it('validates address', () => { + const address = '0x1234567890123456789012345678901234567890'; + + expect(validateAddress(address)).to.deep.equal({ + address, + addressError: null + }); + }); + + it('validates address and converts to checksum', () => { + const address = '0x5A5eFF38DA95b0D58b6C616f2699168B480953C9'; + + expect(validateAddress(address.toLowerCase())).to.deep.equal({ + address, + addressError: null + }); + }); + + it('sets error on null addresses', () => { + expect(validateAddress(null)).to.deep.equal({ + address: null, + addressError: ERRORS.invalidAddress + }); + }); + + it('sets error on invalid addresses', () => { + const address = '0x12344567'; + + expect(validateAddress(address)).to.deep.equal({ + address, + addressError: ERRORS.invalidAddress + }); + }); + }); + + describe('validateCode', () => { + it('validates hex code', () => { + expect(validateCode('0x123abc')).to.deep.equal({ + code: '0x123abc', + codeError: null + }); + }); + + it('validates hex code (non-prefix)', () => { + expect(validateCode('123abc')).to.deep.equal({ + code: '123abc', + codeError: null + }); + }); + + it('sets error on invalid code', () => { + expect(validateCode(null)).to.deep.equal({ + code: null, + codeError: ERRORS.invalidCode + }); + }); + + it('sets error on empty code', () => { + expect(validateCode('')).to.deep.equal({ + code: '', + codeError: ERRORS.invalidCode + }); + }); + + it('sets error on non-hex code', () => { + expect(validateCode('123hfg')).to.deep.equal({ + code: '123hfg', + codeError: ERRORS.invalidCode + }); + }); + }); + + describe('validateName', () => { + it('validates names', () => { + expect(validateName('Joe Bloggs')).to.deep.equal({ + name: 'Joe Bloggs', + nameError: null + }); + }); + + it('sets error on null names', () => { + expect(validateName(null)).to.deep.equal({ + name: null, + nameError: ERRORS.invalidName + }); + }); + + it('sets error on short names', () => { + expect(validateName(' 1 ')).to.deep.equal({ + name: ' 1 ', + nameError: ERRORS.invalidName + }); + }); + }); + + describe('validatePositiveNumber', () => { + it('validates numbers', () => { + expect(validatePositiveNumber(123)).to.deep.equal({ + number: 123, + numberError: null + }); + }); + + it('validates strings', () => { + expect(validatePositiveNumber('123')).to.deep.equal({ + number: '123', + numberError: null + }); + }); + + it('validates bignumbers', () => { + expect(validatePositiveNumber(new BigNumber(123))).to.deep.equal({ + number: new BigNumber(123), + numberError: null + }); + }); + + it('sets error on invalid numbers', () => { + expect(validatePositiveNumber(null)).to.deep.equal({ + number: null, + numberError: ERRORS.invalidAmount + }); + }); + + it('sets error on negative numbers', () => { + expect(validatePositiveNumber(-1)).to.deep.equal({ + number: -1, + numberError: ERRORS.invalidAmount + }); + }); + }); + + describe('validateUint', () => { + it('validates numbers', () => { + expect(validateUint(123)).to.deep.equal({ + value: 123, + valueError: null + }); + }); + + it('validates strings', () => { + expect(validateUint('123')).to.deep.equal({ + value: '123', + valueError: null + }); + }); + + it('validates bignumbers', () => { + expect(validateUint(new BigNumber(123))).to.deep.equal({ + value: new BigNumber(123), + valueError: null + }); + }); + + it('sets error on invalid numbers', () => { + expect(validateUint(null)).to.deep.equal({ + value: null, + valueError: ERRORS.invalidNumber + }); + }); + + it('sets error on negative numbers', () => { + expect(validateUint(-1)).to.deep.equal({ + value: -1, + valueError: ERRORS.negativeNumber + }); + }); + + it('sets error on decimal numbers', () => { + expect(validateUint(3.1415927)).to.deep.equal({ + value: 3.1415927, + valueError: ERRORS.decimalNumber + }); + }); + }); + + describe('isNullAddress', () => { + it('verifies a prefixed null address', () => { + expect(isNullAddress(`0x${NULL_ADDRESS}`)).to.be.true; + }); + + it('verifies a non-prefixed null address', () => { + expect(isNullAddress(NULL_ADDRESS)).to.be.true; + }); + + it('sets false on a null value', () => { + expect(isNullAddress(null)).to.be.false; + }); + + it('sets false on a non-full length 00..00 value', () => { + expect(isNullAddress(NULL_ADDRESS.slice(2))).to.be.false; + }); + + it('sets false on a valid addess, non 00..00 value', () => { + expect(isNullAddress('0x1234567890123456789012345678901234567890')).to.be.false; + }); + }); +}); diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 9cebdda6e..ce7a2ae99 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -57,7 +57,7 @@ class List extends Component { } renderAccounts () { - const { accounts, balances, empty, link, handleAddSearchToken } = this.props; + const { accounts, balances, empty } = this.props; if (empty) { return ( @@ -80,20 +80,29 @@ class List extends Component { return ( <div className={ styles.item } - key={ address }> - <Summary - link={ link } - account={ account } - balance={ balance } - owners={ owners } - handleAddSearchToken={ handleAddSearchToken } - showCertifications - /> + key={ address } + > + { this.renderSummary(account, balance, owners) } </div> ); }); } + renderSummary (account, balance, owners) { + const { handleAddSearchToken, link } = this.props; + + return ( + <Summary + account={ account } + balance={ balance } + handleAddSearchToken={ handleAddSearchToken } + link={ link } + owners={ owners } + showCertifications + /> + ); + } + getAddresses () { const filteredAddresses = this.getFilteredAddresses(); return this.sortAddresses(filteredAddresses); @@ -122,7 +131,15 @@ class List extends Component { }); } - compareAccounts (accountA, accountB, key) { + compareAccounts (accountA, accountB, key, _reverse = null) { + if (key && key.split(':')[1] === '-1') { + return this.compareAccounts(accountA, accountB, key.split(':')[0], true); + } + + if (key === 'timestamp' && _reverse === null) { + return this.compareAccounts(accountA, accountB, key, true); + } + if (key === 'name') { return accountA.name.localeCompare(accountB.name); } @@ -177,7 +194,9 @@ class List extends Component { return tagsA.localeCompare(tagsB); } - const reverse = key === 'timestamp' ? -1 : 1; + const reverse = _reverse + ? -1 + : 1; const metaA = accountA.meta[key]; const metaB = accountB.meta[key]; @@ -220,8 +239,8 @@ class List extends Component { const tags = account.meta.tags || []; const name = account.name || ''; - const values = [] - .concat(tags, name) + const values = tags + .concat(name) .map(v => v.toLowerCase()); return searchValues diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 8658077a5..20924dc4a 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -19,6 +19,7 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; +import { FormattedMessage } from 'react-intl'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; import Certifications from '~/ui/Certifications'; @@ -107,14 +108,22 @@ export default class Summary extends Component { /> ); + const description = this.getDescription(account.meta); + return ( <Container> <Tags tags={ tags } handleAddSearchToken={ handleAddSearchToken } /> - <IdentityIcon - address={ address } /> - <ContainerTitle - title={ this.renderLink() } - byline={ addressComponent } /> + <div className={ styles.heading }> + <IdentityIcon + address={ address } + /> + <ContainerTitle + byline={ addressComponent } + className={ styles.main } + description={ description } + title={ this.renderLink() } + /> + </div> { this.renderOwners() } { this.renderBalance() } @@ -123,6 +132,26 @@ export default class Summary extends Component { ); } + getDescription (meta = {}) { + const { blockNumber } = meta; + + if (!blockNumber) { + return null; + } + + const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat(); + + return ( + <FormattedMessage + id='accounts.summary.minedBlock' + defaultMessage='Mined at block #{blockNumber}' + values={ { + blockNumber: formattedBlockNumber + } } + /> + ); + } + renderOwners () { const { owners } = this.props; const ownersValid = (owners || []).filter((owner) => owner.address && new BigNumber(owner.address).gt(0)); diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css index 2a7cdcec9..25dfdcab4 100644 --- a/js/src/views/Accounts/accounts.css +++ b/js/src/views/Accounts/accounts.css @@ -56,3 +56,12 @@ } } } + +.heading { + display: flex; + flex-direction: row; + + .main { + flex: 1; + } +} diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index dba05dfd9..1367624e8 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -74,6 +74,14 @@ export default class Events extends Component { return ( <Container title='events'> <table className={ styles.events }> + <thead> + <tr> + <th /> + <th className={ styles.origin }> + origin + </th> + </tr> + </thead> <tbody>{ list }</tbody> </table> </Container> diff --git a/js/src/views/Contract/contract.css b/js/src/views/Contract/contract.css index 4752fd04a..924818ebd 100644 --- a/js/src/views/Contract/contract.css +++ b/js/src/views/Contract/contract.css @@ -29,14 +29,36 @@ .event { td { vertical-align: top; - padding: 1em 0.5em; + padding: 0 0.5em 1.5em; div { white-space: nowrap; } + + &.timestamp { + padding-right: 1.5em; + text-align: right; + line-height: 1.5em; + opacity: 0.5; + white-space: nowrap; + } } } +.blockNumber { + color: rgba(255, 255, 255, 0.25); + margin-top: 1.5em; +} + +.origin { + text-align: left; + padding-left: 32px; + text-indent: 1em; + color: rgba(255, 255, 255, 0.5); + text-transform: uppercase; + font-size: 0.9em; +} + .txhash { text-overflow: ellipsis; width: 20%; @@ -54,14 +76,6 @@ opacity: 0.5; } -.timestamp { - padding-top: 1.5em; - text-align: right; - line-height: 1.5em; - opacity: 0.5; - white-space: nowrap; -} - .eventDetails { } diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index d06c22b92..fc299f7cb 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -17,6 +17,9 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { FormattedMessage } from 'react-intl'; +import BigNumber from 'bignumber.js'; + import ActionDelete from 'material-ui/svg-icons/action/delete'; import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow'; import ContentCreate from 'material-ui/svg-icons/content/create'; @@ -136,7 +139,9 @@ class Contract extends Component { account={ account } balance={ balance } isContract - /> + > + { this.renderBlockNumber(account.meta) } + </Header> <Queries accountsInfo={ accountsInfo } @@ -156,6 +161,28 @@ class Contract extends Component { ); } + renderBlockNumber (meta = {}) { + const { blockNumber } = meta; + + if (!blockNumber) { + return null; + } + + const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat(); + + return ( + <div className={ styles.blockNumber }> + <FormattedMessage + id='contract.minedBlock' + defaultMessage='Mined at block #{blockNumber}' + values={ { + blockNumber: formattedBlockNumber + } } + /> + </div> + ); + } + renderDetails (contract) { const { showDetailsDialog } = this.state; diff --git a/js/src/views/Contracts/Summary/summary.js b/js/src/views/Contracts/Summary/summary.js deleted file mode 100644 index 36e88f039..000000000 --- a/js/src/views/Contracts/Summary/summary.js +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2015, 2016 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see <http://www.gnu.org/licenses/>. - -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; - -import { Container, ContainerTitle, IdentityIcon, IdentityName } from '~/ui'; - -export default class Summary extends Component { - static contextTypes = { - api: React.PropTypes.object.isRequired - } - - static propTypes = { - contract: PropTypes.object.isRequired, - children: PropTypes.node - } - - render () { - const contract = this.props.contract; - - if (!contract) { - return null; - } - - const viewLink = `/app/${contract.address}`; - - return ( - <Container> - <IdentityIcon - address={ contract.address } /> - <ContainerTitle - title={ <Link to={ viewLink }>{ <IdentityName address={ contract.address } unknown /> }</Link> } - byline={ contract.address } /> - { this.props.children } - </Container> - ); - } -} diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index cc292275a..532c2ffb4 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -45,7 +45,7 @@ class Contracts extends Component { state = { addContract: false, deployContract: false, - sortOrder: 'timestamp', + sortOrder: 'blockNumber', searchValues: [], searchTokens: [] } @@ -92,7 +92,8 @@ class Contracts extends Component { empty={ !hasContracts } order={ sortOrder } orderFallback='name' - handleAddSearchToken={ this.onAddSearchToken } /> + handleAddSearchToken={ this.onAddSearchToken } + /> </Page> </div> ); @@ -109,7 +110,8 @@ class Contracts extends Component { id='sortContracts' order={ this.state.sortOrder } metas={ [ - { key: 'timestamp', label: 'date' } + { key: 'timestamp', label: 'date' }, + { key: 'blockNumber:-1', label: 'mined block' } ] } showDefault={ false } onChange={ onChange } /> diff --git a/js/src/views/Contracts/Summary/index.js b/js/src/views/Dapps/UrlButton/index.js similarity index 95% rename from js/src/views/Contracts/Summary/index.js rename to js/src/views/Dapps/UrlButton/index.js index 980ecff9a..f773334ac 100644 --- a/js/src/views/Contracts/Summary/index.js +++ b/js/src/views/Dapps/UrlButton/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -export default from './summary'; +export default from './urlButton'; diff --git a/js/src/views/Dapps/UrlButton/urlButton.css b/js/src/views/Dapps/UrlButton/urlButton.css new file mode 100644 index 000000000..6b22ae1da --- /dev/null +++ b/js/src/views/Dapps/UrlButton/urlButton.css @@ -0,0 +1,20 @@ +/* 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/>. +*/ + +.button { + vertical-align: middle; +} diff --git a/js/src/views/Dapps/UrlButton/urlButton.js b/js/src/views/Dapps/UrlButton/urlButton.js new file mode 100644 index 000000000..0a01be03b --- /dev/null +++ b/js/src/views/Dapps/UrlButton/urlButton.js @@ -0,0 +1,96 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see <http://www.gnu.org/licenses/>. + +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { withRouter } from 'react-router'; + +import Button from '~/ui/Button'; +import { LinkIcon } from '~/ui/Icons'; +import Input from '~/ui/Form/Input'; + +import styles from './urlButton.css'; + +const INPUT_STYLE = { display: 'inline-block', width: '20em' }; + +class UrlButton extends Component { + static propTypes = { + router: PropTypes.object.isRequired // injected by withRouter + }; + + state = { + inputShown: false + }; + + render () { + const { inputShown } = this.state; + + return ( + <div> + { inputShown ? this.renderInput() : null } + <Button + className={ styles.button } + icon={ <LinkIcon /> } + label={ + <FormattedMessage + id='dapps.button.url.label' + defaultMessage='URL' /> + } + onClick={ this.toggleInput } + /> + </div> + ); + } + + renderInput () { + return ( + <Input + hint={ + <FormattedMessage + id='dapps.button.url.input' + defaultMessage='https://mkr.market' /> + } + onBlur={ this.hideInput } + onFocus={ this.showInput } + onSubmit={ this.inputOnSubmit } + style={ INPUT_STYLE } + /> + ); + } + + toggleInput = () => { + const { inputShown } = this.state; + this.setState({ + inputShown: !inputShown + }); + } + + hideInput = () => { + this.setState({ inputShown: false }); + } + + showInput = () => { + this.setState({ inputShown: true }); + } + + inputOnSubmit = (url) => { + const { router } = this.props; + + router.push(`/web/${encodeURIComponent(url)}`); + } +} + +export default withRouter(UrlButton); diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index fa7c44878..8ebbb602d 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -27,6 +27,7 @@ import PermissionStore from '~/modals/DappPermissions/store'; import { Actionbar, Button, Page } from '~/ui'; import { LockedIcon, VisibleIcon } from '~/ui/Icons'; +import UrlButton from './UrlButton'; import DappsStore from './dappsStore'; import Summary from './Summary'; @@ -88,6 +89,7 @@ class Dapps extends Component { defaultMessage='Decentralized Applications' /> } buttons={ [ + <UrlButton key='url' />, <Button icon={ <VisibleIcon /> } key='edit' diff --git a/js/src/views/Settings/Background/background.js b/js/src/views/Settings/Background/background.js index 48f3a49ec..ebf3c7a96 100644 --- a/js/src/views/Settings/Background/background.js +++ b/js/src/views/Settings/Background/background.js @@ -141,7 +141,7 @@ class Background extends Component { generateSeed () { const { api, muiTheme } = this.context; - return api.util.sha3(`${muiTheme.backgroundSeed}${Math.random()}${counter++}`); + return api.util.sha3.text(`${muiTheme.backgroundSeed}${Math.random()}${counter++}`); } } diff --git a/js/src/views/Settings/Parity/parity.js b/js/src/views/Settings/Parity/parity.js index 978ef296f..c52d713cd 100644 --- a/js/src/views/Settings/Parity/parity.js +++ b/js/src/views/Settings/Parity/parity.js @@ -17,7 +17,9 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { MenuItem } from 'material-ui'; +import LogLevel from 'loglevel'; +import { LOG_KEYS } from '~/config'; import { Select, Container, LanguageSelector } from '~/ui'; import layout from '../layout.css'; @@ -25,14 +27,54 @@ import layout from '../layout.css'; export default class Parity extends Component { static contextTypes = { api: PropTypes.object.isRequired - } + }; state = { - mode: 'active' - } + loglevels: {}, + mode: 'active', + selectValues: [] + }; componentWillMount () { this.loadMode(); + this.loadLogLevels(); + this.setSelectValues(); + } + + loadLogLevels () { + if (process.env.NODE_ENV === 'production') { + return null; + } + + const nextState = { ...this.state.logLevels }; + + Object.keys(LOG_KEYS).map((logKey) => { + const log = LOG_KEYS[logKey]; + + const logger = LogLevel.getLogger(log.path); + const level = logger.getLevel(); + + nextState[logKey] = { level, log }; + }); + + this.setState({ logLevels: nextState }); + } + + setSelectValues () { + if (process.env.NODE_ENV === 'production') { + return null; + } + + const selectValues = Object.keys(LogLevel.levels).map((levelName) => { + const value = LogLevel.levels[levelName]; + + return { + name: levelName, + value + }; + }); + + this.setState({ selectValues }); } render () { @@ -45,7 +87,8 @@ export default class Parity extends Component { <div> <FormattedMessage id='settings.parity.overview_0' - defaultMessage='Control the Parity node settings and mode of operation via this interface.' /> + defaultMessage='Control the Parity node settings and mode of operation via this interface.' + /> </div> </div> <div className={ layout.details }> @@ -53,10 +96,64 @@ export default class Parity extends Component { { this.renderModes() } </div> </div> + + { this.renderLogsConfig() } </Container> ); } + renderLogsConfig () { + if (process.env.NODE_ENV === 'production') { + return null; + } + + return ( + <div className={ layout.layout }> + <div className={ layout.overview }> + <div> + <FormattedMessage + id='settings.parity.loglevels' + defaultMessage='Choose the different logs level.' + /> + </div> + </div> + <div className={ layout.details }> + { this.renderLogsLevels() } + </div> + </div> + ); + } + + renderLogsLevels () { + if (process.env.NODE_ENV === 'production') { + return null; + } + + const { logLevels, selectValues } = this.state; + + return Object.keys(logLevels).map((logKey) => { + const { level, log } = logLevels[logKey]; + const { path, desc } = log; + + const onChange = (_, index) => { + const nextLevel = Object.values(selectValues)[index].value; + LogLevel.getLogger(path).setLevel(nextLevel); + this.loadLogLevels(); + }; + + return ( + <div key={ logKey }> + <p>{ desc }</p> + <Select + onChange={ onChange } + value={ level } + values={ selectValues } + /> + </div> + ); + }); + } + renderModes () { const { mode } = this.state; diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js index 99bd1c5f3..5a93cef0d 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js @@ -77,13 +77,28 @@ class TransactionPendingFormConfirm extends Component { } } + getPasswordHint () { + const { account } = this.props; + const accountHint = account && account.meta && account.meta.passwordHint; + + if (accountHint) { + return accountHint; + } + + const { wallet } = this.state; + const walletHint = wallet && wallet.meta && wallet.meta.passwordHint; + + return walletHint || null; + } + render () { const { account, address, isSending } = this.props; const { password, wallet, walletError } = this.state; const isExternal = !account.uuid; - const passwordHint = account.meta && account.meta.passwordHint - ? (<div><span>(hint) </span>{ account.meta.passwordHint }</div>) + const passwordHintText = this.getPasswordHint(); + const passwordHint = passwordHintText + ? (<div><span>(hint) </span>{ passwordHintText }</div>) : null; const isWalletOk = !isExternal || (walletError === null && wallet !== null); @@ -170,12 +185,26 @@ class TransactionPendingFormConfirm extends Component { } onKeySelect = (event) => { + // Check that file have been selected + if (event.target.files.length === 0) { + return this.setState({ + wallet: null, + walletError: null + }); + } + const fileReader = new FileReader(); fileReader.onload = (e) => { try { const wallet = JSON.parse(e.target.result); + try { + if (wallet && typeof wallet.meta === 'string') { + wallet.meta = JSON.parse(wallet.meta); + } + } catch (e) {} + this.setState({ wallet, walletError: null @@ -218,13 +247,13 @@ class TransactionPendingFormConfirm extends Component { } } -function mapStateToProps (initState, initProps) { - const { accounts } = initState.personal; +function mapStateToProps (_, initProps) { const { address } = initProps; - const account = accounts[address] || {}; + return (state) => { + const { accounts } = state.personal; + const account = accounts[address] || {}; - return () => { return { account }; }; } diff --git a/js/src/views/Web/web.js b/js/src/views/Web/web.js index ac7d1c59e..a7b63ce30 100644 --- a/js/src/views/Web/web.js +++ b/js/src/views/Web/web.js @@ -16,6 +16,8 @@ import React, { Component, PropTypes } from 'react'; import store from 'store'; +import { parse as parseUrl, format as formatUrl } from 'url'; +import { parse as parseQuery } from 'querystring'; import AddressBar from './AddressBar'; @@ -23,39 +25,53 @@ import styles from './web.css'; const LS_LAST_ADDRESS = '_parity::webLastAddress'; +const hasProtocol = /^https?:\/\//; + export default class Web extends Component { static contextTypes = { api: PropTypes.object.isRequired } + static propTypes = { + params: PropTypes.object.isRequired + } + state = { - displayedUrl: this.lastAddress(), + displayedUrl: null, isLoading: true, token: null, - url: this.lastAddress() + url: null }; componentDidMount () { - this.context.api.signer.generateWebProxyAccessToken().then(token => { - this.setState({ token }); - }); + const { api } = this.context; + const { params } = this.props; + + api + .signer + .generateWebProxyAccessToken() + .then((token) => { + this.setState({ token }); + }); + + this.setUrl(params.url); } - address () { - const { dappsUrl } = this.context.api; - const { url, token } = this.state; - const path = url.replace(/:/g, '').replace(/\/\//g, '/'); - - return `${dappsUrl}/web/${token}/${path}/`; + componentWillReceiveProps (props) { + this.setUrl(props.params.url); } - lastAddress () { - return store.get(LS_LAST_ADDRESS) || 'https://mkr.market'; - } + setUrl = (url) => { + url = url || store.get(LS_LAST_ADDRESS) || 'https://mkr.market'; + if (!hasProtocol.test(url)) { + url = `https://${url}`; + } + + this.setState({ url, displayedUrl: url }); + }; render () { const { displayedUrl, isLoading, token } = this.state; - const address = this.address(); if (!token) { return ( @@ -67,20 +83,30 @@ export default class Web extends Component { ); } + const { dappsUrl } = this.context.api; + const { url } = this.state; + if (!url || !token) { + return null; + } + + const parsed = parseUrl(url); + const { protocol, host, path } = parsed; + const address = `${dappsUrl}/web/${token}/${protocol.slice(0, -1)}/${host}${path}`; + return ( <div className={ styles.wrapper }> <AddressBar className={ styles.url } isLoading={ isLoading } - onChange={ this.handleUpdateUrl } - onRefresh={ this.handleOnRefresh } + onChange={ this.onUrlChange } + onRefresh={ this.onRefresh } url={ displayedUrl } /> <iframe className={ styles.frame } frameBorder={ 0 } name={ name } - onLoad={ this.handleIframeLoad } + onLoad={ this.iframeOnLoad } sandbox='allow-forms allow-same-origin allow-scripts' scrolling='auto' src={ address } /> @@ -88,7 +114,11 @@ export default class Web extends Component { ); } - handleUpdateUrl = (url) => { + onUrlChange = (url) => { + if (!hasProtocol.test(url)) { + url = `https://${url}`; + } + store.set(LS_LAST_ADDRESS, url); this.setState({ @@ -98,18 +128,23 @@ export default class Web extends Component { }); }; - handleOnRefresh = (ev) => { + onRefresh = () => { const { displayedUrl } = this.state; - const hasQuery = displayedUrl.indexOf('?') > 0; - const separator = hasQuery ? '&' : '?'; + + // Insert timestamp + // This is a hack to prevent caching. + const parsed = parseUrl(displayedUrl); + parsed.query = parseQuery(parsed.query); + parsed.query.t = Date.now().toString(); + delete parsed.search; this.setState({ isLoading: true, - url: `${displayedUrl}${separator}t=${Date.now()}` + url: formatUrl(parsed) }); }; - handleIframeLoad = (ev) => { + iframeOnLoad = () => { this.setState({ isLoading: false }); diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js index 8adb80b5a..8a3ddf3d1 100644 --- a/js/src/views/WriteContract/writeContract.js +++ b/js/src/views/WriteContract/writeContract.js @@ -18,7 +18,6 @@ import React, { PropTypes, Component } from 'react'; import { observer } from 'mobx-react'; import { MenuItem, Toggle } from 'material-ui'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import CircularProgress from 'material-ui/CircularProgress'; import moment from 'moment'; import { throttle } from 'lodash'; @@ -32,8 +31,6 @@ import SendIcon from 'material-ui/svg-icons/content/send'; import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '~/ui'; import { DeployContract, SaveContract, LoadContract } from '~/modals'; -import { setupWorker } from '~/redux/providers/compilerActions'; - import WriteContractStore from './writeContractStore'; import styles from './writeContract.css'; @@ -42,7 +39,6 @@ class WriteContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - setupWorker: PropTypes.func.isRequired, worker: PropTypes.object, workerError: PropTypes.any }; @@ -55,8 +51,7 @@ class WriteContract extends Component { }; componentWillMount () { - const { setupWorker, worker } = this.props; - setupWorker(); + const { worker } = this.props; if (worker !== undefined) { this.store.setWorker(worker); @@ -458,23 +453,65 @@ class WriteContract extends Component { const { bytecode } = contract; const abi = contract.interface; + const metadata = contract.metadata + ? ( + <Input + allowCopy + label='Metadata' + readOnly + value={ contract.metadata } + /> + ) + : null; + return ( <div> <Input + allowCopy + label='ABI Interface' readOnly value={ abi } - label='ABI Interface' /> <Input + allowCopy + label='Bytecode' readOnly value={ `0x${bytecode}` } - label='Bytecode' /> + + { metadata } + { this.renderSwarmHash(contract) } </div> ); } + renderSwarmHash (contract) { + if (!contract || !contract.metadata) { + return null; + } + + const { bytecode } = contract; + + // @see https://solidity.readthedocs.io/en/develop/miscellaneous.html#encoding-of-the-metadata-hash-in-the-bytecode + const hashRegex = /a165627a7a72305820([a-f0-9]{64})0029$/; + + if (!hashRegex.test(bytecode)) { + return null; + } + + const hash = hashRegex.exec(bytecode)[1]; + + return ( + <Input + allowCopy + label='Swarm Metadata Hash' + readOnly + value={ `${hash}` } + /> + ); + } + renderErrors () { const { annotations } = this.store; @@ -533,17 +570,10 @@ class WriteContract extends Component { function mapStateToProps (state) { const { accounts } = state.personal; - const { worker, error } = state.compiler; + const { worker, error } = state.worker; return { accounts, worker, workerError: error }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({ - setupWorker - }, dispatch); -} - export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(WriteContract); diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index d2cedb551..330a9b665 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -17,7 +17,7 @@ //! Authority params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -29,7 +29,10 @@ pub struct AuthorityRoundParams { #[serde(rename="stepDuration")] pub step_duration: Uint, /// Valid authorities - pub authorities: Vec<Address>, + pub validators: ValidatorSet, + /// Block reward. + #[serde(rename="blockReward")] + pub block_reward: Option<Uint>, /// Starting step. Determined automatically if not specified. /// To be used for testing only. #[serde(rename="startStep")] @@ -49,12 +52,15 @@ mod tests { use spec::authority_round::AuthorityRound; #[test] - fn basic_authority_deserialization() { + fn authority_round_deserialization() { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", "stepDuration": "0x02", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "validators": { + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, + "blockReward": "0x50", "startStep" : 24 } }"#; diff --git a/json/src/spec/basic_authority.rs b/json/src/spec/basic_authority.rs index 0e388f1eb..b6ff3f47f 100644 --- a/json/src/spec/basic_authority.rs +++ b/json/src/spec/basic_authority.rs @@ -17,7 +17,7 @@ //! Authority params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -29,7 +29,7 @@ pub struct BasicAuthorityParams { #[serde(rename="durationLimit")] pub duration_limit: Uint, /// Valid authorities - pub authorities: Vec<Address>, + pub validators: ValidatorSet, } /// Authority engine deserialization. @@ -50,7 +50,9 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "durationLimit": "0x0d", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "validators" : { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } } }"#; diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index b0c34b2ea..d437b06ba 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -16,10 +16,7 @@ //! Engine deserialization. -use spec::Ethash; -use spec::BasicAuthority; -use spec::AuthorityRound; -use spec::Tendermint; +use super::{Ethash, BasicAuthority, AuthorityRound, Tendermint}; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index d923be069..2f4b06f31 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -25,6 +25,7 @@ pub mod seal; pub mod engine; pub mod state; pub mod ethash; +pub mod validator_set; pub mod basic_authority; pub mod authority_round; pub mod tendermint; @@ -38,6 +39,7 @@ pub use self::seal::{Seal, Ethereum, AuthorityRoundSeal, TendermintSeal}; pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; +pub use self::validator_set::ValidatorSet; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; pub use self::tendermint::{Tendermint, TendermintParams}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 6858602da..ede6f52e5 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -17,7 +17,7 @@ //! Tendermint params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Tendermint params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -25,8 +25,8 @@ pub struct TendermintParams { /// Gas limit divisor. #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, - /// Valid authorities - pub authorities: Vec<Address>, + /// Valid validators. + pub validators: ValidatorSet, /// Propose step timeout in milliseconds. #[serde(rename="timeoutPropose")] pub timeout_propose: Option<Uint>, @@ -39,6 +39,9 @@ pub struct TendermintParams { /// Commit step timeout in milliseconds. #[serde(rename="timeoutCommit")] pub timeout_commit: Option<Uint>, + /// Block reward. + #[serde(rename="blockReward")] + pub block_reward: Option<Uint>, } /// Tendermint engine deserialization. @@ -54,11 +57,14 @@ mod tests { use spec::tendermint::Tendermint; #[test] - fn basic_authority_deserialization() { + fn tendermint_deserialization() { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "validators": { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, + "blockReward": "0x50" } }"#; diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs new file mode 100644 index 000000000..47acd7c36 --- /dev/null +++ b/json/src/spec/validator_set.rs @@ -0,0 +1,47 @@ +// 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 deserialization. + +use hash::Address; + +/// Different ways of specifying validators. +#[derive(Debug, PartialEq, Deserialize)] +pub enum ValidatorSet { + /// A simple list of authorities. + #[serde(rename="list")] + List(Vec<Address>), + /// Address of a contract that indicates the list of authorities. + #[serde(rename="contract")] + Contract(Address), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::validator_set::ValidatorSet; + + #[test] + fn validator_set_deserialization() { + let s = r#"[{ + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, { + "contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }]"#; + + let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap(); + } +} diff --git a/logger/Cargo.toml b/logger/Cargo.toml index a22c748fb..091f9fefa 100644 --- a/logger/Cargo.toml +++ b/logger/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore client." name = "ethcore-logger" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] diff --git a/mac/Parity.pkgproj b/mac/Parity.pkgproj index 3cd4199a5..892d0212f 100755 --- a/mac/Parity.pkgproj +++ b/mac/Parity.pkgproj @@ -578,7 +578,7 @@ <key>OVERWRITE_PERMISSIONS</key> <false/> <key>VERSION</key> - <string>1.5.0</string> + <string>1.6.0</string> </dict> <key>UUID</key> <string>2DCD5B81-7BAF-4DA1-9251-6274B089FD36</string> diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 685b4a936..f40765944 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -9,7 +9,7 @@ !define COMPANYNAME "Ethcore" !define DESCRIPTION "Fast, light, robust Ethereum implementation" !define VERSIONMAJOR 1 -!define VERSIONMINOR 5 +!define VERSIONMINOR 6 !define VERSIONBUILD 0 !define ARGS "--warp" !define FIRST_START_ARGS "ui --warp --mode=passive" diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 088066e63..67cba6a48 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -2,7 +2,7 @@ mode = "last" mode_timeout = 300 mode_alarm = 3600 -auto_update = "critical" +auto_update = "none" release_track = "current" no_download = false no_consensus = false @@ -116,5 +116,3 @@ jit = false logging = "own_tx=trace" log_file = "/var/log/parity.log" color = true - - diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 47a0af0bb..2c008623a 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -16,7 +16,7 @@ #[macro_use] mod usage; -use dir::default_data_path; +use dir; usage! { { @@ -37,6 +37,7 @@ usage! { cmd_snapshot: bool, cmd_restore: bool, cmd_ui: bool, + cmd_dapp: bool, cmd_tools: bool, cmd_hash: bool, cmd_kill: bool, @@ -90,8 +91,8 @@ usage! { flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), - flag_base_path: String = default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone(), - flag_db_path: String = "$BASE/chains", or |c: &Config| otry!(c.parity).db_path.clone(), + flag_base_path: String = dir::default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone(), + flag_db_path: String = dir::CHAINS_PATH, or |c: &Config| otry!(c.parity).db_path.clone(), flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(), @@ -525,6 +526,7 @@ mod tests { cmd_snapshot: false, cmd_restore: false, cmd_ui: false, + cmd_dapp: false, cmd_tools: false, cmd_hash: false, cmd_db: false, @@ -540,7 +542,7 @@ mod tests { flag_mode: "last".into(), flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, - flag_auto_update: "critical".into(), + flag_auto_update: "none".into(), flag_release_track: "current".into(), flag_no_download: false, flag_no_consensus: false, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 7c3e5cc7d..f75dda524 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -5,6 +5,7 @@ Parity. Ethereum Client. Usage: parity [options] parity ui [options] + parity dapp <path> [options] parity daemon <pid-file> [options] parity account (new | list ) [options] parity account import <path>... [options] @@ -336,7 +337,7 @@ Legacy Options: testnet --keys-path $HOME/parity/testnet-keys. Overrides the --keys-path option. --import-geth-keys Attempt to import keys from Geth client. - --datadir PATH Equivalent to --db-path PATH. + --datadir PATH Equivalent to --base-path PATH. --networkid INDEX Equivalent to --network-id INDEX. --peers NUM Equivalent to --min-peers NUM. --nodekey KEY Equivalent to --node-key KEY. diff --git a/parity/configuration.rs b/parity/configuration.rs index 85ff61d5c..671c78206 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -17,7 +17,7 @@ use std::time::Duration; use std::io::Read; use std::net::SocketAddr; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::cmp::max; use cli::{Args, ArgsError}; use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address}; @@ -30,11 +30,11 @@ use ethcore::verification::queue::VerifierSettings; use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; use cache::CacheConfig; -use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, +use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, replace_home_for_db, geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; use ethcore_logger::Config as LogConfig; -use dir::{Directories, default_hypervisor_path}; +use dir::{Directories, default_hypervisor_path, default_local_path}; use dapps::Configuration as DappsConfiguration; use signer::{Configuration as SignerConfiguration}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; @@ -335,6 +335,7 @@ impl Configuration { net_settings: self.network_settings(), dapps_conf: dapps_conf, signer_conf: signer_conf, + dapp: self.dapp_to_open()?, ui: self.args.cmd_ui, name: self.args.flag_identity, custom_bootnodes: self.args.flag_bootnodes.is_some(), @@ -507,10 +508,28 @@ impl Configuration { hosts: self.dapps_hosts(), user: self.args.flag_dapps_user.clone(), pass: self.args.flag_dapps_pass.clone(), - dapps_path: self.directories().dapps, + dapps_path: PathBuf::from(self.directories().dapps), + extra_dapps: if self.args.cmd_dapp { + self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect() + } else { + vec![] + }, } } + fn dapp_to_open(&self) -> Result<Option<String>, String> { + if !self.args.cmd_dapp { + return Ok(None); + } + let path = self.args.arg_path.get(0).map(String::as_str).unwrap_or("."); + let path = Path::new(path).canonicalize() + .map_err(|e| format!("Invalid path: {}. Error: {:?}", path, e))?; + let name = path.file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Root path is not supported.".to_owned())?; + Ok(Some(name.into())) + } + fn gas_pricer_config(&self) -> Result<GasPricerConfig, String> { if let Some(d) = self.args.flag_gasprice.as_ref() { return Ok(GasPricerConfig::Fixed(to_u256(d)?)); @@ -707,9 +726,14 @@ impl Configuration { fn directories(&self) -> Directories { use util::path; + let local_path = default_local_path(); let data_path = replace_home("", self.args.flag_datadir.as_ref().unwrap_or(&self.args.flag_base_path)); - let db_path = replace_home(&data_path, &self.args.flag_db_path); + let db_path = if self.args.flag_datadir.is_some() { + replace_home(&data_path, &self.args.flag_db_path) + } else { + replace_home_for_db(&data_path, &local_path, &self.args.flag_db_path) + }; let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); @@ -1025,6 +1049,7 @@ mod tests { dapps_conf: Default::default(), signer_conf: Default::default(), ui: false, + dapp: None, name: "".into(), custom_bootnodes: false, fat_db: Default::default(), @@ -1219,6 +1244,22 @@ mod tests { }); } + #[test] + fn should_parse_dapp_opening() { + // given + let temp = RandomTempPath::new(); + let name = temp.file_name().unwrap().to_str().unwrap(); + create_dir(temp.as_str().to_owned()).unwrap(); + + // when + let conf0 = parse(&["parity", "dapp", temp.to_str().unwrap()]); + + // then + assert_eq!(conf0.dapp_to_open(), Ok(Some(name.into()))); + let extra_dapps = conf0.dapps_config().extra_dapps; + assert_eq!(extra_dapps, vec![temp.to_owned()]); + } + #[test] fn should_not_bail_on_empty_line_in_reserved_peers() { let temp = RandomTempPath::new(); diff --git a/parity/dapps.rs b/parity/dapps.rs index 8ec526a05..591c17593 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -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 io::PanicHandler; use rpc_apis; @@ -33,7 +34,8 @@ pub struct Configuration { pub hosts: Option<Vec<String>>, pub user: Option<String>, pub pass: Option<String>, - pub dapps_path: String, + pub dapps_path: PathBuf, + pub extra_dapps: Vec<PathBuf>, } impl Default for Configuration { @@ -46,7 +48,8 @@ impl Default for Configuration { hosts: Some(Vec::new()), user: None, pass: None, - dapps_path: replace_home(&data_dir, "$BASE/dapps"), + dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), + extra_dapps: vec![], } } } @@ -80,7 +83,14 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We (username.to_owned(), password) }); - Ok(Some(setup_dapps_server(deps, configuration.dapps_path, &addr, configuration.hosts, auth)?)) + Ok(Some(setup_dapps_server( + deps, + configuration.dapps_path, + configuration.extra_dapps, + &addr, + configuration.hosts, + auth + )?)) } pub use self::server::WebappServer; @@ -90,11 +100,13 @@ pub use self::server::setup_dapps_server; mod server { use super::Dependencies; use std::net::SocketAddr; + use std::path::PathBuf; pub struct WebappServer; pub fn setup_dapps_server( _deps: Dependencies, - _dapps_path: String, + _dapps_path: PathBuf, + _extra_dapps: Vec<PathBuf>, _url: &SocketAddr, _allowed_hosts: Option<Vec<String>>, _auth: Option<(String, String)>, @@ -106,6 +118,7 @@ mod server { #[cfg(feature = "dapps")] mod server { use super::Dependencies; + use std::path::PathBuf; use std::sync::Arc; use std::net::SocketAddr; use std::io; @@ -122,7 +135,8 @@ mod server { pub fn setup_dapps_server( deps: Dependencies, - dapps_path: String, + dapps_path: PathBuf, + extra_dapps: Vec<PathBuf>, url: &SocketAddr, allowed_hosts: Option<Vec<String>>, auth: Option<(String, String)>, @@ -130,7 +144,7 @@ mod server { use ethcore_dapps as dapps; let server = dapps::ServerBuilder::new( - dapps_path, + &dapps_path, Arc::new(Registrar { client: deps.client.clone() }), deps.remote.clone(), ); @@ -141,6 +155,7 @@ mod server { .fetch(deps.fetch.clone()) .sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))) .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) + .extra_dapps(&extra_dapps) .signer_address(deps.signer.address()); let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); diff --git a/parity/dir.rs b/parity/dir.rs index bd83ef3c0..ffef0d94e 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -18,7 +18,7 @@ use std::fs; use std::path::{PathBuf, Path}; use util::{H64, H256}; use util::journaldb::Algorithm; -use helpers::replace_home; +use helpers::{replace_home, replace_home_for_db}; use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(target_os = "macos")] const AUTHOR: &'static str = "Parity"; @@ -31,6 +31,9 @@ use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT: &'static str = "io.parity.ethereum"; #[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-updates"; +#[cfg(target_os = "windows")] pub const CHAINS_PATH: &'static str = "$LOCAL/chains"; +#[cfg(not(target_os = "windows"))] pub const CHAINS_PATH: &'static str = "$BASE/chains"; + // this const is irrelevent cause we do have migrations now, // but we still use it for backwards compatibility const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; @@ -47,9 +50,10 @@ pub struct Directories { impl Default for Directories { fn default() -> Self { let data_dir = default_data_path(); + let local_dir = default_local_path(); Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home(&data_dir, "$BASE/chains"), + db: replace_home_for_db(&data_dir, &local_dir, CHAINS_PATH), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), @@ -209,6 +213,11 @@ pub fn default_data_path() -> String { get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) } +pub fn default_local_path() -> String { + let app_info = AppInfo { name: PRODUCT, author: AUTHOR }; + get_app_root(AppDataType::UserCache, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) +} + pub fn default_hypervisor_path() -> String { let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR }; get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity-hypervisor".to_owned()) @@ -217,14 +226,18 @@ pub fn default_hypervisor_path() -> String { #[cfg(test)] mod tests { use super::Directories; - use helpers::replace_home; + use helpers::{replace_home, replace_home_for_db}; #[test] fn test_default_directories() { let data_dir = super::default_data_path(); + let local_dir = super::default_local_path(); let expected = Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home(&data_dir, "$BASE/chains"), + db: replace_home_for_db(&data_dir, &local_dir, + if cfg!(target_os = "windows") { "$LOCAL/chains" } + else { "$BASE/chains" } + ), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), diff --git a/parity/helpers.rs b/parity/helpers.rs index b91b62f1d..680306684 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -135,8 +135,13 @@ pub fn to_price(s: &str) -> Result<f32, String> { pub fn replace_home(base: &str, arg: &str) -> String { // the $HOME directory on mac os should be `~/Library` or `~/Library/Application Support` let r = arg.replace("$HOME", env::home_dir().unwrap().to_str().unwrap()); - let r = r.replace("$BASE", base ); - r.replace("/", &::std::path::MAIN_SEPARATOR.to_string() ) + let r = r.replace("$BASE", base); + r.replace("/", &::std::path::MAIN_SEPARATOR.to_string()) +} + +pub fn replace_home_for_db(base: &str, local: &str, arg: &str) -> String { + let r = replace_home(base, arg); + r.replace("$LOCAL", local) } /// Flush output buffer. diff --git a/parity/run.rs b/parity/run.rs index a878c2aae..e4c5fca6f 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -92,6 +92,7 @@ pub struct RunCmd { pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, pub signer_conf: signer::Configuration, + pub dapp: Option<String>, pub ui: bool, pub name: String, pub custom_bootnodes: bool, @@ -118,6 +119,17 @@ pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configur Ok(()) } +pub fn open_dapp(dapps_conf: &dapps::Configuration, dapp: &str) -> Result<(), String> { + if !dapps_conf.enabled { + return Err("Cannot use DAPP command with Dapps turned off.".into()) + } + + let url = format!("http://{}:{}/{}/", dapps_conf.interface, dapps_conf.port, dapp); + url::open(&url); + Ok(()) +} + + pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<bool, String> { if cmd.ui && cmd.dapps_conf.enabled { // Check if Parity is already running @@ -441,6 +453,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R open_ui(&cmd.dapps_conf, &cmd.signer_conf)?; } + if let Some(dapp) = cmd.dapp { + open_dapp(&cmd.dapps_conf, &dapp)?; + } + // Handle exit let restart = wait_for_exit(panic_handler, Some(updater), can_restart); diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 9031f6adf..aa0f6a34e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore jsonrpc" name = "ethcore-rpc" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/rpc/rpctest/Cargo.toml b/rpc/rpctest/Cargo.toml index 461746001..53f1dc2e8 100644 --- a/rpc/rpctest/Cargo.toml +++ b/rpc/rpctest/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Rpc test client." name = "rpctest" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index a11edc84e..fd5f3d4f3 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -671,13 +671,8 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where let request = CallRequest::into(request); let signed = self.sign_call(request)?; - let result = match num.0 { - BlockNumber::Pending => take_weak!(self.miner).call(&*take_weak!(self.client), &signed, Default::default()), - num => take_weak!(self.client).call(&signed, num.into(), Default::default()), - }; - - result - .map(|res| (res.gas_used + res.refunded).into()) + take_weak!(self.client).estimate_gas(&signed, num.0.into()) + .map(Into::into) .map_err(errors::from_call_error) } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 2adc50fc7..5f4424313 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -691,7 +691,7 @@ fn rpc_eth_estimate_gas() { "latest"], "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","result":"0xff35","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x5208","id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } @@ -725,7 +725,7 @@ fn rpc_eth_estimate_gas_default_block() { }], "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","result":"0xff35","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x5208","id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/scripts/contractABI.js b/scripts/contractABI.js index e6c8e923a..9a8225ccd 100644 --- a/scripts/contractABI.js +++ b/scripts/contractABI.js @@ -24,7 +24,7 @@ String.prototype.toSnake = function(){ function makeContractFile(name, json, prefs) { return `// Autogenerated from JSON contract definition using Rust contract convertor. - +#![allow(unused_imports)] use std::string::String; use std::result::Result; use std::fmt; @@ -39,10 +39,10 @@ function convertContract(name, json, prefs) { return `${prefs._pub ? "pub " : ""}struct ${name} { contract: ethabi::Contract, address: util::Address, - do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + 'static>, + do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static>, } impl ${name} { - pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + 'static { + pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static { ${name} { contract: ethabi::Contract::new(ethabi::Interface::load(b"${JSON.stringify(json.filter(a => a.type == 'function')).replaceAll('"', '\\"')}").expect("JSON is autogenerated; qed")), address: address, @@ -112,8 +112,10 @@ function mapReturnType(name, type, _prefs) { return "String"; if (type == "bytes") return "Vec<u8>"; + if (type == "address[]") + return "Vec<util::Address>"; - console.log(`Unsupported argument type: ${type} (${name})`); + console.log(`Unsupported return type: ${type} (${name})`); } function convertToken(name, type, _prefs) { @@ -163,8 +165,10 @@ function tokenType(name, type, _prefs) { return `${name}.to_string()`; if (type == "bytes") return `${name}.to_bytes()`; + if (type == "address[]") + return `${name}.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>())`; - // ERROR - unsupported + console.log(`Unsupported return type: ${type} (${name})`); } function tokenCoerce(name, type, _prefs) { @@ -190,6 +194,8 @@ function tokenCoerce(name, type, _prefs) { return `${name}`; if (type == "bytes") return `${name}`; + if (type == "address[]") + return `${name}.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>()`; console.log(`Unsupported return type: ${type} (${name})`); } @@ -217,6 +223,7 @@ function convertFunction(json, _prefs) { }`; } -jsonabi = [{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_hard","type":"bool"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"release","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"},{"name":"o_critical","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"build","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"client","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"fork","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"hard","type":"bool"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"clientOwner","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"grandOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"},{"name":"_critical","type":"bool"}],"name":"addRelease","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"checksum","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"gas","type":"uint256"}],"name":"TransactionProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":false,"name":"success","type":"bool"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"},{"indexed":true,"name":"name","type":"bytes32"},{"indexed":false,"name":"spec","type":"bytes32"},{"indexed":false,"name":"hard","type":"bool"}],"name":"ForkProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkAcceptedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkRejectedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRatified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"forkBlock","type":"uint32"},{"indexed":false,"name":"release","type":"bytes32"},{"indexed":false,"name":"track","type":"uint8"},{"indexed":false,"name":"semver","type":"uint24"},{"indexed":true,"name":"critical","type":"bool"}],"name":"ReleaseAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":true,"name":"platform","type":"bytes32"},{"indexed":false,"name":"checksum","type":"bytes32"}],"name":"ChecksumAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"ClientAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"}],"name":"ClientRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"now","type":"address"}],"name":"ClientOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"now","type":"bool"}],"name":"ClientRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old","type":"address"},{"indexed":false,"name":"now","type":"address"}],"name":"OwnerChanged","type":"event"}]; +let jsonabi = [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]; -makeContractFile("Operations", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}}); +let out = makeContractFile("Contract", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}, "_sync": true}); +console.log(`${out}`); diff --git a/signer/Cargo.toml b/signer/Cargo.toml index abf5d7755..5a24c52bb 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Trusted Signer" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-signer" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/stratum/Cargo.toml b/stratum/Cargo.toml index daf7aa15c..3b467b8f3 100644 --- a/stratum/Cargo.toml +++ b/stratum/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore stratum lib" name = "ethcore-stratum" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/sync/Cargo.toml b/sync/Cargo.toml index e22e50435..5fa635760 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore blockchain sync" name = "ethsync" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 1a4dc4197..d36cc26c9 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -100,8 +100,8 @@ use sync_io::SyncIo; use time; use super::SyncConfig; use block_sync::{BlockDownloader, BlockRequest, BlockDownloaderImportError as DownloaderImportError, DownloadAction}; +use rand::Rng; use snapshot::{Snapshot, ChunkType}; -use rand::{thread_rng, Rng}; use api::{PeerInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; use transactions_stats::{TransactionsStats, Stats as TransactionStats}; @@ -125,6 +125,8 @@ const MAX_NEW_HASHES: usize = 64; const MAX_TX_TO_IMPORT: usize = 512; const MAX_NEW_BLOCK_AGE: BlockNumber = 20; const MAX_TRANSACTION_SIZE: usize = 300*1024; +// Maximal number of transactions in sent in single packet. +const MAX_TRANSACTIONS_TO_PROPAGATE: usize = 64; // Min number of blocks to be behind for a snapshot sync const SNAPSHOT_RESTORE_THRESHOLD: BlockNumber = 100000; const SNAPSHOT_MIN_PEERS: usize = 3; @@ -329,6 +331,17 @@ impl PeerInfo { } } +#[cfg(not(test))] +mod random { + use rand; + pub fn new() -> rand::ThreadRng { rand::thread_rng() } +} +#[cfg(test)] +mod random { + use rand::{self, SeedableRng}; + pub fn new() -> rand::XorShiftRng { rand::XorShiftRng::from_seed([0, 1, 2, 3]) } +} + /// Blockchain sync handler. /// See module documentation for more details. pub struct ChainSync { @@ -570,7 +583,7 @@ impl ChainSync { if let (Some(ancient_block_hash), Some(ancient_block_number)) = (chain.ancient_block_hash, chain.ancient_block_number) { trace!(target: "sync", "Downloading old blocks from {:?} (#{}) till {:?} (#{:?})", ancient_block_hash, ancient_block_number, chain.first_block_hash, chain.first_block_number); - let mut downloader = BlockDownloader::new(true, &ancient_block_hash, ancient_block_number, pruning.state_history_size); + let mut downloader = BlockDownloader::new(true, &ancient_block_hash, ancient_block_number, None); if let Some(hash) = chain.first_block_hash { trace!(target: "sync", "Downloader target set to {:?}", hash); downloader.set_target(&hash); @@ -1120,7 +1133,7 @@ impl ChainSync { fn continue_sync(&mut self, io: &mut SyncIo) { let mut peers: Vec<(PeerId, U256, u8)> = self.peers.iter().filter_map(|(k, p)| if p.can_sync() { Some((*k, p.difficulty.unwrap_or_else(U256::zero), p.protocol_version)) } else { None }).collect(); - thread_rng().shuffle(&mut peers); //TODO: sort by rating + random::new().shuffle(&mut peers); //TODO: sort by rating // prefer peers with higher protocol version peers.sort_by(|&(_, _, ref v1), &(_, _, ref v2)| v1.cmp(v2)); trace!(target: "sync", "Syncing with peers: {} active, {} confirmed, {} total", self.active_peers.len(), peers.len(), self.peers.len()); @@ -1436,7 +1449,7 @@ impl ChainSync { } let mut item_count = r.item_count(); - trace!(target: "sync", "{} -> Transactions ({} entries)", peer_id, item_count); + trace!(target: "sync", "{:02} -> Transactions ({} entries)", peer_id, item_count); item_count = min(item_count, MAX_TX_TO_IMPORT); let mut transactions = Vec::with_capacity(item_count); for i in 0 .. item_count { @@ -1881,7 +1894,7 @@ impl ChainSync { let mut count = (peers.len() as f64).powf(0.5).round() as usize; count = min(count, MAX_PEERS_PROPAGATION); count = max(count, MIN_PEERS_PROPAGATION); - ::rand::thread_rng().shuffle(&mut peers); + random::new().shuffle(&mut peers); peers.truncate(count); peers } @@ -1961,10 +1974,11 @@ impl ChainSync { let small = self.peers.len() < MIN_PEERS_PROPAGATION; let block_number = io.chain().chain_info().best_block_number; + let mut random = random::new(); let lucky_peers = { let stats = &mut self.transactions_stats; self.peers.iter_mut() - .filter(|_| small || ::rand::random::<u32>() < fraction) + .filter(|_| small || random.next_u32() < fraction) .take(MAX_PEERS_PROPAGATION) .filter_map(|(peer_id, mut peer_info)| { // Send all transactions @@ -1975,11 +1989,14 @@ impl ChainSync { stats.propagated(*hash, id, block_number); } peer_info.last_sent_transactions = all_transactions_hashes.clone(); - return Some((*peer_id, all_transactions_rlp.clone())); + return Some((*peer_id, all_transactions_hashes.len(), all_transactions_rlp.clone())); } // Get hashes of all transactions to send to this peer - let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::<HashSet<_>>(); + let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions) + .take(MAX_TRANSACTIONS_TO_PROPAGATE) + .cloned() + .collect::<HashSet<_>>(); if to_send.is_empty() { return None; } @@ -1995,22 +2012,28 @@ impl ChainSync { } } - peer_info.last_sent_transactions = all_transactions_hashes.clone(); - Some((*peer_id, packet.out())) + peer_info.last_sent_transactions = all_transactions_hashes + .intersection(&peer_info.last_sent_transactions) + .chain(&to_send) + .cloned() + .collect(); + Some((*peer_id, to_send.len(), packet.out())) }) .collect::<Vec<_>>() }; // Send RLPs - let sent = lucky_peers.len(); - if sent > 0 { - for (peer_id, rlp) in lucky_peers { + let peers = lucky_peers.len(); + if peers > 0 { + let mut max_sent = 0; + for (peer_id, sent, rlp) in lucky_peers { self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp); + trace!(target: "sync", "{:02} <- Transactions ({} entries)", peer_id, sent); + max_sent = max(max_sent, sent); } - - trace!(target: "sync", "Sent up to {} transactions to {} peers.", transactions.len(), sent); + debug!(target: "sync", "Sent up to {} transactions to {} peers.", max_sent, peers); } - sent + peers } fn propagate_latest_blocks(&mut self, io: &mut SyncIo, sealed: &[H256]) { @@ -2030,7 +2053,6 @@ impl ChainSync { trace!(target: "sync", "Sent sealed block to all peers"); }; } - self.propagate_new_transactions(io); self.last_sent_block_number = chain_info.best_block_number; } @@ -2056,9 +2078,11 @@ impl ChainSync { } /// called when block is imported to chain - propagates the blocks and updates transactions sent to peers - pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256], proposed: &[Bytes]) { + pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], enacted: &[H256], _retracted: &[H256], sealed: &[H256], proposed: &[Bytes]) { let queue_info = io.chain().queue_info(); - if !self.status().is_syncing(queue_info) || !sealed.is_empty() { + let is_syncing = self.status().is_syncing(queue_info); + + if !is_syncing || !sealed.is_empty() { trace!(target: "sync", "Propagating blocks, state={:?}", self.state); self.propagate_latest_blocks(io, sealed); self.propagate_proposed_blocks(io, proposed); @@ -2067,6 +2091,15 @@ impl ChainSync { trace!(target: "sync", "Bad blocks in the queue, restarting"); self.restart(io); } + + if !is_syncing && !enacted.is_empty() && !self.peers.is_empty() { + // Select random peer to re-broadcast transactions to. + let peer = random::new().gen_range(0, self.peers.len()); + trace!(target: "sync", "Re-broadcasting transactions to a random peer."); + self.peers.values_mut().nth(peer).map(|mut peer_info| + peer_info.last_sent_transactions.clear() + ); + } } /// Called when peer sends us new consensus packet @@ -2504,7 +2537,7 @@ mod tests { } #[test] - fn propagates_new_transactions_after_new_block() { + fn does_not_propagate_new_transactions_after_new_block() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); @@ -2514,16 +2547,36 @@ mod tests { let mut io = TestIo::new(&mut client, &ss, &queue, None); let peer_count = sync.propagate_new_transactions(&mut io); io.chain.insert_transaction_to_queue(); - // New block import should trigger propagation. + // New block import should not trigger propagation. + // (we only propagate on timeout) sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // 2 message should be send - assert_eq!(2, io.packets.len()); + assert_eq!(1, io.packets.len()); // 1 peer should receive the message assert_eq!(1, peer_count); // TRANSACTIONS_PACKET assert_eq!(0x02, io.packets[0].packet_id); - assert_eq!(0x02, io.packets[1].packet_id); + } + + #[test] + fn does_not_fail_for_no_peers() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + client.insert_transaction_to_queue(); + // Sync with no peers + let mut sync = ChainSync::new(SyncConfig::default(), &client); + let queue = RwLock::new(VecDeque::new()); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &queue, None); + let peer_count = sync.propagate_new_transactions(&mut io); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); + // Try to propagate same transactions for the second time + let peer_count2 = sync.propagate_new_transactions(&mut io); + + assert_eq!(0, io.packets.len()); + assert_eq!(0, peer_count); + assert_eq!(0, peer_count2); } #[test] diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 855386870..ea8bd970d 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -16,7 +16,7 @@ use util::*; use io::{IoHandler, IoContext, IoChannel}; -use ethcore::client::{BlockChainClient, Client, MiningBlockChainClient}; +use ethcore::client::{BlockChainClient, Client}; use ethcore::service::ClientIoMessage; use ethcore::spec::Spec; use ethcore::miner::MinerService; @@ -33,9 +33,6 @@ struct TestIoHandler { impl IoHandler<ClientIoMessage> for TestIoHandler { fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) { match *net_message { - 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) { panic!("Invalid message received: {}", e); }, @@ -75,8 +72,8 @@ fn authority_round() { // Push transaction to both clients. Only one of them gets lucky to produce a block. net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); - net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); - net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); // exchange statuses @@ -140,8 +137,8 @@ fn tendermint() { trace!(target: "poa", "Peer 0 is {}.", s0.address()); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); trace!(target: "poa", "Peer 1 is {}.", s1.address()); - net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); - net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); // Exhange statuses diff --git a/updater/Cargo.toml b/updater/Cargo.toml index 31ca30c43..c0e2c15df 100644 --- a/updater/Cargo.toml +++ b/updater/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity Updater Service." name = "parity-updater" -version = "1.5.0" +version = "1.6.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/util/Cargo.toml b/util/Cargo.toml index f019a9554..4cc9e78a7 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore utility library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-util" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/util/benches/bigint.rs b/util/benches/bigint.rs index 5f35f52ae..4fa9c180e 100644 --- a/util/benches/bigint.rs +++ b/util/benches/bigint.rs @@ -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/>. -//! benchmarking for rlp +//! benchmarking for bigint //! should be started with: //! ```bash //! multirust run nightly cargo bench @@ -24,10 +24,10 @@ #![feature(asm)] extern crate test; -extern crate ethcore_bigint as bigint; +extern crate ethcore_util; use test::{Bencher, black_box}; -use bigint::uint::{U256, U512, Uint, U128}; +use ethcore_util::{U256, U512, Uint, U128}; #[bench] fn u256_add(b: &mut Bencher) { diff --git a/util/benches/rlp.rs b/util/benches/rlp.rs index d446f22cf..9a7889ef2 100644 --- a/util/benches/rlp.rs +++ b/util/benches/rlp.rs @@ -24,12 +24,12 @@ extern crate test; extern crate rlp; -extern crate ethcore_bigint as bigint; +extern crate ethcore_util as util; use test::Bencher; use std::str::FromStr; use rlp::*; -use bigint::uint::U256; +use util::U256; #[bench] fn bench_stream_u64_value(b: &mut Bencher) { diff --git a/util/io/Cargo.toml b/util/io/Cargo.toml index ce20830f1..d433f66f2 100644 --- a/util/io/Cargo.toml +++ b/util/io/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore IO library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-io" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies <admin@parity.io>"] [dependencies] diff --git a/util/io/src/service.rs b/util/io/src/service.rs index 6f5c87004..ed63600f2 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -391,7 +391,7 @@ impl<Message> IoChannel<Message> where Message: Send + Clone + Sync + 'static { } Ok(()) } - /// Create a new channel to connected to event loop. + /// Create a new channel disconnected from an event loop. pub fn disconnected() -> IoChannel<Message> { IoChannel { channel: None, diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index c60705d0d..4b256bf1d 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore network library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-network" -version = "1.5.0" +version = "1.6.0" authors = ["Parity Technologies <admin@parity.io>"] [dependencies] diff --git a/util/network/src/connection.rs b/util/network/src/connection.rs index 22fb845f4..40138a32c 100644 --- a/util/network/src/connection.rs +++ b/util/network/src/connection.rs @@ -158,8 +158,8 @@ impl<Socket: GenericSocket> GenericConnection<Socket> { } if self.send_queue.is_empty() { self.interest.remove(Ready::writable()); - io.update_registration(self.token)?; } + io.update_registration(self.token)?; Ok(r) }) } diff --git a/util/src/nibblevec.rs b/util/src/nibblevec.rs index 75925f52b..57dc28e1d 100644 --- a/util/src/nibblevec.rs +++ b/util/src/nibblevec.rs @@ -18,27 +18,26 @@ //! An owning, nibble-oriented byte vector. use ::NibbleSlice; +use elastic_array::ElasticArray36; -#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Debug)] /// Owning, nibble-oriented byte vector. Counterpart to `NibbleSlice`. +#[derive(Clone, PartialEq, Eq, Debug)] pub struct NibbleVec { - inner: Vec<u8>, + inner: ElasticArray36<u8>, len: usize, } +impl Default for NibbleVec { + fn default() -> Self { + NibbleVec::new() + } +} + impl NibbleVec { /// Make a new `NibbleVec` pub fn new() -> Self { NibbleVec { - inner: Vec::new(), - len: 0 - } - } - - /// Make a `NibbleVec` with capacity for `n` nibbles. - pub fn with_capacity(n: usize) -> Self { - NibbleVec { - inner: Vec::with_capacity((n / 2) + (n % 2)), + inner: ElasticArray36::new(), len: 0 } } @@ -49,9 +48,6 @@ impl NibbleVec { /// Retrurns true if `NibbleVec` has zero length pub fn is_empty(&self) -> bool { self.len == 0 } - /// Capacity of the `NibbleVec`. - pub fn capacity(&self) -> usize { self.inner.capacity() * 2 } - /// Try to get the nibble at the given offset. pub fn at(&self, idx: usize) -> u8 { if idx % 2 == 0 { @@ -109,7 +105,7 @@ impl NibbleVec { impl<'a> From<NibbleSlice<'a>> for NibbleVec { fn from(s: NibbleSlice<'a>) -> Self { - let mut v = NibbleVec::with_capacity(s.len()); + let mut v = NibbleVec::new(); for i in 0..s.len() { v.push(s.at(i)); } diff --git a/util/src/trie/fatdb.rs b/util/src/trie/fatdb.rs index f8d47bb38..942bca6e8 100644 --- a/util/src/trie/fatdb.rs +++ b/util/src/trie/fatdb.rs @@ -16,8 +16,8 @@ use hash::H256; use sha3::Hashable; -use hashdb::{HashDB, DBValue}; -use super::{TrieDB, Trie, TrieDBIterator, TrieItem, Recorder, TrieIterator}; +use hashdb::HashDB; +use super::{TrieDB, Trie, TrieDBIterator, TrieItem, TrieIterator, Query}; /// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database. /// Additionaly it stores inserted hash-key mappings for later retrieval. @@ -58,10 +58,10 @@ impl<'db> Trie for FatDB<'db> { self.raw.contains(&key.sha3()) } - fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<DBValue>> - where 'a: 'b, R: Recorder + fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) -> super::Result<Option<Q::Item>> + where 'a: 'key { - self.raw.get_recorded(&key.sha3(), rec) + self.raw.get_with(&key.sha3(), query) } } @@ -104,6 +104,7 @@ impl<'db> Iterator for FatDBIterator<'db> { #[test] fn fatdb_to_trie() { use memorydb::MemoryDB; + use hashdb::DBValue; use trie::{FatDBMut, TrieMut}; let mut memdb = MemoryDB::new(); diff --git a/util/src/trie/fatdbmut.rs b/util/src/trie/fatdbmut.rs index ceed14bab..1af476358 100644 --- a/util/src/trie/fatdbmut.rs +++ b/util/src/trie/fatdbmut.rs @@ -76,18 +76,28 @@ impl<'db> TrieMut for FatDBMut<'db> { self.raw.get(&key.sha3()) } - fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<()> { + fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<Option<DBValue>> { let hash = key.sha3(); - self.raw.insert(&hash, value)?; + let out = self.raw.insert(&hash, value)?; let db = self.raw.db_mut(); - db.emplace(Self::to_aux_key(&hash), DBValue::from_slice(key)); - Ok(()) + + // don't insert if it doesn't exist. + if out.is_none() { + db.emplace(Self::to_aux_key(&hash), DBValue::from_slice(key)); + } + Ok(out) } - fn remove(&mut self, key: &[u8]) -> super::Result<()> { + fn remove(&mut self, key: &[u8]) -> super::Result<Option<DBValue>> { let hash = key.sha3(); - self.raw.db_mut().remove(&Self::to_aux_key(&hash)); - self.raw.remove(&hash) + let out = self.raw.remove(&hash)?; + + // don't remove if it already exists. + if out.is_some() { + self.raw.db_mut().remove(&Self::to_aux_key(&hash)); + } + + Ok(out) } } diff --git a/util/src/trie/lookup.rs b/util/src/trie/lookup.rs new file mode 100644 index 000000000..8772ac15e --- /dev/null +++ b/util/src/trie/lookup.rs @@ -0,0 +1,94 @@ +// 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/>. + +//! Trie lookup via HashDB. + +use hashdb::HashDB; +use nibbleslice::NibbleSlice; +use rlp::{Rlp, View}; +use ::{H256}; + +use super::{TrieError, Query}; +use super::node::Node; + +/// Trie lookup helper object. +pub struct Lookup<'a, Q: Query> { + /// database to query from. + pub db: &'a HashDB, + /// Query object to record nodes and transform data. + pub query: Q, + /// Hash to start at + pub hash: H256, +} + +impl<'a, Q: Query> Lookup<'a, Q> { + /// Look up the given key. If the value is found, it will be passed to the given + /// function to decode or copy. + pub fn look_up(mut self, mut key: NibbleSlice) -> super::Result<Option<Q::Item>> { + let mut hash = self.hash; + + // this loop iterates through non-inline nodes. + for depth in 0.. { + let node_data = match self.db.get(&hash) { + Some(value) => value, + None => return Err(Box::new(match depth { + 0 => TrieError::InvalidStateRoot(hash), + _ => TrieError::IncompleteDatabase(hash), + })), + }; + + self.query.record(&hash, &node_data, depth); + + // this loop iterates through all inline children (usually max 1) + // without incrementing the depth. + let mut node_data = &node_data[..]; + loop { + match Node::decoded(node_data) { + Node::Leaf(slice, value) => { + return Ok(match slice == key { + true => Some(self.query.decode(value)), + false => None, + }) + } + Node::Extension(slice, item) => { + if key.starts_with(&slice) { + node_data = item; + key = key.mid(slice.len()); + } else { + return Ok(None) + } + } + Node::Branch(children, value) => match key.is_empty() { + true => return Ok(value.map(move |val| self.query.decode(val))), + false => { + node_data = children[key.at(0) as usize]; + key = key.mid(1); + } + }, + _ => return Ok(None), + } + + // check if new node data is inline or hash. + let r = Rlp::new(node_data); + if r.is_data() && r.size() == 32 { + hash = r.as_val(); + break + } + } + } + Ok(None) + } +} diff --git a/util/src/trie/mod.rs b/util/src/trie/mod.rs index 151760c8c..3b16f70b0 100644 --- a/util/src/trie/mod.rs +++ b/util/src/trie/mod.rs @@ -38,6 +38,7 @@ pub mod recorder; mod fatdb; mod fatdbmut; +mod lookup; pub use self::standardmap::{Alphabet, StandardMap, ValueMode}; pub use self::triedbmut::TrieDBMut; @@ -76,6 +77,46 @@ pub type Result<T> = ::std::result::Result<T, Box<TrieError>>; /// Trie-Item type. pub type TrieItem<'a> = Result<(Vec<u8>, DBValue)>; +/// Description of what kind of query will be made to the trie. +/// +/// This is implemented for any &mut recorder (where the query will return +/// a DBValue), any function taking raw bytes (where no recording will be made), +/// or any tuple of (&mut Recorder, FnOnce(&[u8])) +pub trait Query { + /// Output item. + type Item; + + /// Decode a byte-slice into the desired item. + fn decode(self, &[u8]) -> Self::Item; + + /// Record that a node has been passed through. + fn record(&mut self, &H256, &[u8], u32) { } +} + +impl<'a> Query for &'a mut Recorder { + type Item = DBValue; + + fn decode(self, value: &[u8]) -> DBValue { DBValue::from_slice(value) } + fn record(&mut self, hash: &H256, data: &[u8], depth: u32) { + (&mut **self).record(hash, data, depth); + } +} + +impl<F, T> Query for F where F: for<'a> FnOnce(&'a [u8]) -> T { + type Item = T; + + fn decode(self, value: &[u8]) -> T { (self)(value) } +} + +impl<'a, F, T> Query for (&'a mut Recorder, F) where F: FnOnce(&[u8]) -> T { + type Item = T; + + fn decode(self, value: &[u8]) -> T { (self.1)(value) } + fn record(&mut self, hash: &H256, data: &[u8], depth: u32) { + self.0.record(hash, data, depth) + } +} + /// A key-value datastore implemented as a database-backed modified Merkle tree. pub trait Trie { /// Return the root of the trie. @@ -91,13 +132,13 @@ pub trait Trie { /// What is the value of the given key in this trie? fn get<'a, 'key>(&'a self, key: &'key [u8]) -> Result<Option<DBValue>> where 'a: 'key { - self.get_recorded(key, &mut recorder::NoOp) + self.get_with(key, DBValue::from_slice) } - /// Query the value of the given key in this trie while recording visited nodes - /// to the given recorder. If the query encounters an error, the nodes passed to the recorder are unspecified. - fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result<Option<DBValue>> - where 'a: 'b, R: Recorder; + /// Search for the key with the given query parameter. See the docs of the `Query` + /// trait for more details. + fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) + -> Result<Option<Q::Item>> where 'a: 'key; /// Returns a depth-first iterator over the elements of trie. fn iter<'a>(&'a self) -> Result<Box<TrieIterator<Item = TrieItem> + 'a>>; @@ -119,13 +160,13 @@ pub trait TrieMut { /// What is the value of the given key in this trie? fn get<'a, 'key>(&'a self, key: &'key [u8]) -> Result<Option<DBValue>> where 'a: 'key; - /// Insert a `key`/`value` pair into the trie. An `empty` value is equivalent to removing - /// `key` from the trie. - fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<()>; + /// Insert a `key`/`value` pair into the trie. An empty value is equivalent to removing + /// `key` from the trie. Returns the old value associated with this key, if it existed. + fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<Option<DBValue>>; /// Remove a `key` from the trie. Equivalent to making it equal to the empty - /// value. - fn remove(&mut self, key: &[u8]) -> Result<()>; + /// value. Returns the old value associated with this key, if it existed. + fn remove(&mut self, key: &[u8]) -> Result<Option<DBValue>>; } /// A trie iterator that also supports random access. @@ -192,9 +233,10 @@ impl<'db> Trie for TrieKinds<'db> { wrapper!(self, contains, key) } - fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], r: &'b mut R) -> Result<Option<DBValue>> - where 'a: 'b, R: Recorder { - wrapper!(self, get_recorded, key, r) + fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) -> Result<Option<Q::Item>> + where 'a: 'key + { + wrapper!(self, get_with, key, query) } fn iter<'a>(&'a self) -> Result<Box<TrieIterator<Item = TrieItem> + 'a>> { diff --git a/util/src/trie/node.rs b/util/src/trie/node.rs index 44f1f3bfa..24f2815ec 100644 --- a/util/src/trie/node.rs +++ b/util/src/trie/node.rs @@ -16,6 +16,7 @@ use elastic_array::ElasticArray36; use nibbleslice::*; +use nibblevec::NibbleVec; use bytes::*; use rlp::*; use hashdb::DBValue; @@ -24,40 +25,21 @@ use hashdb::DBValue; pub type NodeKey = ElasticArray36<u8>; /// Type of node in the trie and essential information thereof. -#[derive(Eq, PartialEq, Debug)] -pub enum Node { +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Node<'a> { /// Null trie node; could be an empty root or an empty branch entry. Empty, /// Leaf node; has key slice and value. Value may not be empty. - Leaf(NodeKey, DBValue), + Leaf(NibbleSlice<'a>, &'a [u8]), /// Extension node; has key slice and node data. Data may not be null. - Extension(NodeKey, DBValue), + Extension(NibbleSlice<'a>, &'a [u8]), /// Branch node; has array of 16 child nodes (each possibly null) and an optional immediate node data. - Branch([NodeKey; 16], Option<DBValue>) + Branch([&'a [u8]; 16], Option<&'a [u8]>) } -impl Clone for Node { - fn clone(&self) -> Node { - match *self { - Node::Empty => Node::Empty, - Node::Leaf(ref k, ref v) => Node::Leaf(k.clone(), v.clone()), - Node::Extension(ref k, ref v) => Node::Extension(k.clone(), v.clone()), - Node::Branch(ref k, ref v) => { - let mut branch = [NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), - NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), - NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new()]; - for i in 0 .. 16 { - branch[i] = k[i].clone(); - } - Node::Branch(branch, v.clone()) - } - } - } -} - -impl Node { +impl<'a> Node<'a> { /// Decode the `node_rlp` and return the Node. - pub fn decoded(node_rlp: &[u8]) -> Node { + pub fn decoded(node_rlp: &'a [u8]) -> Self { let r = Rlp::new(node_rlp); match r.prototype() { // either leaf or extension - decode first item with NibbleSlice::??? @@ -66,18 +48,16 @@ impl Node { // if extension, second item is a node (either SHA3 to be looked up and // fed back into this function or inline RLP which can be fed back into this function). Prototype::List(2) => match NibbleSlice::from_encoded(r.at(0).data()) { - (slice, true) => Node::Leaf(slice.encoded(true), DBValue::from_slice(r.at(1).data())), - (slice, false) => Node::Extension(slice.encoded(false), DBValue::from_slice(r.at(1).as_raw())), + (slice, true) => Node::Leaf(slice, r.at(1).data()), + (slice, false) => Node::Extension(slice, r.at(1).as_raw()), }, // branch - first 16 are nodes, 17th is a value (or empty). Prototype::List(17) => { - let mut nodes: [NodeKey; 16] = [NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), - NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), - NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new()]; + let mut nodes = [&[] as &[u8]; 16]; for i in 0..16 { - nodes[i] = NodeKey::from_slice(r.at(i).as_raw()); + nodes[i] = r.at(i).as_raw(); } - Node::Branch(nodes, if r.at(16).is_empty() { None } else { Some(DBValue::from_slice(r.at(16).data())) }) + Node::Branch(nodes, if r.at(16).is_empty() { None } else { Some(r.at(16).data()) }) }, // an empty branch index. Prototype::Data(0) => Node::Empty, @@ -94,23 +74,23 @@ impl Node { match *self { Node::Leaf(ref slice, ref value) => { let mut stream = RlpStream::new_list(2); - stream.append(&&**slice); - stream.append(&&**value); + stream.append(&&*slice.encoded(true)); + stream.append(value); stream.out() }, Node::Extension(ref slice, ref raw_rlp) => { let mut stream = RlpStream::new_list(2); - stream.append(&&**slice); - stream.append_raw(&&*raw_rlp, 1); + stream.append(&&*slice.encoded(false)); + stream.append_raw(raw_rlp, 1); stream.out() }, Node::Branch(ref nodes, ref value) => { let mut stream = RlpStream::new_list(17); for i in 0..16 { - stream.append_raw(&*nodes[i], 1); + stream.append_raw(nodes[i], 1); } match *value { - Some(ref n) => { stream.append(&&**n); }, + Some(ref n) => { stream.append(n); }, None => { stream.append_empty_data(); }, } stream.out() @@ -123,3 +103,64 @@ impl Node { } } } + +/// An owning node type. Useful for trie iterators. +#[derive(Debug, PartialEq, Eq)] +pub enum OwnedNode { + /// Empty trie node. + Empty, + /// Leaf node: partial key and value. + Leaf(NibbleVec, DBValue), + /// Extension node: partial key and child node. + Extension(NibbleVec, DBValue), + /// Branch node: 16 children and an optional value. + Branch([NodeKey; 16], Option<DBValue>), +} + +impl Clone for OwnedNode { + fn clone(&self) -> Self { + match *self { + OwnedNode::Empty => OwnedNode::Empty, + OwnedNode::Leaf(ref k, ref v) => OwnedNode::Leaf(k.clone(), v.clone()), + OwnedNode::Extension(ref k, ref c) => OwnedNode::Extension(k.clone(), c.clone()), + OwnedNode::Branch(ref c, ref v) => { + let mut children = [ + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + ]; + + for (owned, borrowed) in children.iter_mut().zip(c.iter()) { + *owned = borrowed.clone() + } + + OwnedNode::Branch(children, v.as_ref().cloned()) + } + } + } +} + +impl<'a> From<Node<'a>> for OwnedNode { + fn from(node: Node<'a>) -> Self { + match node { + Node::Empty => OwnedNode::Empty, + Node::Leaf(k, v) => OwnedNode::Leaf(k.into(), DBValue::from_slice(v)), + Node::Extension(k, child) => OwnedNode::Extension(k.into(), DBValue::from_slice(child)), + Node::Branch(c, val) => { + let mut children = [ + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + NodeKey::new(), NodeKey::new(), NodeKey::new(), NodeKey::new(), + ]; + + for (owned, borrowed) in children.iter_mut().zip(c.iter()) { + *owned = NodeKey::from_slice(borrowed) + } + + OwnedNode::Branch(children, val.map(DBValue::from_slice)) + } + } + } +} diff --git a/util/src/trie/recorder.rs b/util/src/trie/recorder.rs index 7f98c20e5..868f4d27d 100644 --- a/util/src/trie/recorder.rs +++ b/util/src/trie/recorder.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. +//! Trie query recorder. + use sha3::Hashable; use {Bytes, H256}; @@ -30,63 +32,36 @@ pub struct Record { pub hash: H256, } -/// Trie node recorder. -/// -/// These are used to record which nodes are visited during a trie query. -/// Inline nodes are not to be recorded, as they are contained within their parent. -pub trait Recorder { - /// Record that the given node has been visited. - /// - /// The depth parameter is the depth of the visited node, with the root node having depth 0. - fn record(&mut self, hash: &H256, data: &[u8], depth: u32); - - /// Drain all accepted records from the recorder in ascending order by depth. - fn drain(&mut self) -> Vec<Record> where Self: Sized; -} - -/// A no-op trie recorder. This ignores everything which is thrown at it. -pub struct NoOp; - -impl Recorder for NoOp { - #[inline] - fn record(&mut self, _hash: &H256, _data: &[u8], _depth: u32) {} - - #[inline] - fn drain(&mut self) -> Vec<Record> { Vec::new() } -} - -/// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface -/// properly. +/// Records trie nodes as they pass it. #[derive(Debug)] -pub struct BasicRecorder { +pub struct Recorder { nodes: Vec<Record>, min_depth: u32, } -impl Default for BasicRecorder { +impl Default for Recorder { fn default() -> Self { - BasicRecorder::new() + Recorder::new() } } -impl BasicRecorder { - /// Create a new `BasicRecorder` which records all given nodes. +impl Recorder { + /// Create a new `Recorder` which records all given nodes. #[inline] pub fn new() -> Self { - BasicRecorder::with_depth(0) + Recorder::with_depth(0) } - /// Create a `BasicRecorder` which only records nodes beyond a given depth. + /// Create a `Recorder` which only records nodes beyond a given depth. pub fn with_depth(depth: u32) -> Self { - BasicRecorder { + Recorder { nodes: Vec::new(), min_depth: depth, } } -} -impl Recorder for BasicRecorder { - fn record(&mut self, hash: &H256, data: &[u8], depth: u32) { + /// Record a visited node, given its hash, data, and depth. + pub fn record(&mut self, hash: &H256, data: &[u8], depth: u32) { debug_assert_eq!(data.sha3(), *hash); if depth >= self.min_depth { @@ -98,7 +73,8 @@ impl Recorder for BasicRecorder { } } - fn drain(&mut self) -> Vec<Record> { + /// Drain all visited records. + pub fn drain(&mut self) -> Vec<Record> { ::std::mem::replace(&mut self.nodes, Vec::new()) } } @@ -109,20 +85,9 @@ mod tests { use sha3::Hashable; use ::H256; - #[test] - fn no_op_does_nothing() { - let mut no_op = NoOp; - let (node1, node2) = (&[1], &[2]); - let (hash1, hash2) = (node1.sha3(), node2.sha3()); - no_op.record(&hash1, node1, 1); - no_op.record(&hash2, node2, 2); - - assert_eq!(no_op.drain(), Vec::new()); - } - #[test] fn basic_recorder() { - let mut basic = BasicRecorder::new(); + let mut basic = Recorder::new(); let node1 = vec![1, 2, 3, 4]; let node2 = vec![4, 5, 6, 7, 8, 9, 10]; @@ -148,7 +113,7 @@ mod tests { #[test] fn basic_recorder_min_depth() { - let mut basic = BasicRecorder::with_depth(400); + let mut basic = Recorder::with_depth(400); let node1 = vec![1, 2, 3, 4]; let node2 = vec![4, 5, 6, 7, 8, 9, 10]; @@ -192,9 +157,9 @@ mod tests { } let trie = TrieDB::new(&db, &root).unwrap(); - let mut recorder = BasicRecorder::new(); + let mut recorder = Recorder::new(); - trie.get_recorded(b"pirate", &mut recorder).unwrap().unwrap(); + trie.get_with(b"pirate", &mut recorder).unwrap().unwrap(); let nodes: Vec<_> = recorder.drain().into_iter().map(|r| r.data).collect(); assert_eq!(nodes, vec![ @@ -213,7 +178,7 @@ mod tests { ] ]); - trie.get_recorded(b"letter", &mut recorder).unwrap().unwrap(); + trie.get_with(b"letter", &mut recorder).unwrap().unwrap(); let nodes: Vec<_> = recorder.drain().into_iter().map(|r| r.data).collect(); assert_eq!(nodes, vec![ diff --git a/util/src/trie/sectriedb.rs b/util/src/trie/sectriedb.rs index e3e6bf90d..9b4f68e73 100644 --- a/util/src/trie/sectriedb.rs +++ b/util/src/trie/sectriedb.rs @@ -16,9 +16,9 @@ use hash::H256; use sha3::Hashable; -use hashdb::{HashDB, DBValue}; +use hashdb::HashDB; use super::triedb::TrieDB; -use super::{Trie, TrieItem, Recorder, TrieIterator}; +use super::{Trie, TrieItem, TrieIterator, Query}; /// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database. /// @@ -59,16 +59,17 @@ impl<'db> Trie for SecTrieDB<'db> { self.raw.contains(&key.sha3()) } - fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<DBValue>> - where 'a: 'b, R: Recorder + fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) -> super::Result<Option<Q::Item>> + where 'a: 'key { - self.raw.get_recorded(&key.sha3(), rec) + self.raw.get_with(&key.sha3(), query) } } #[test] fn trie_to_sectrie() { use memorydb::MemoryDB; + use hashdb::DBValue; use super::triedbmut::TrieDBMut; use super::super::TrieMut; diff --git a/util/src/trie/sectriedbmut.rs b/util/src/trie/sectriedbmut.rs index ca14b2df6..ab695921f 100644 --- a/util/src/trie/sectriedbmut.rs +++ b/util/src/trie/sectriedbmut.rs @@ -68,11 +68,11 @@ impl<'db> TrieMut for SecTrieDBMut<'db> { self.raw.get(&key.sha3()) } - fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<()> { + fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<Option<DBValue>> { self.raw.insert(&key.sha3(), value) } - fn remove(&mut self, key: &[u8]) -> super::Result<()> { + fn remove(&mut self, key: &[u8]) -> super::Result<Option<DBValue>> { self.raw.remove(&key.sha3()) } } diff --git a/util/src/trie/triedb.rs b/util/src/trie/triedb.rs index 3508908e7..e23eb1788 100644 --- a/util/src/trie/triedb.rs +++ b/util/src/trie/triedb.rs @@ -18,16 +18,14 @@ use common::*; use hashdb::*; use nibbleslice::*; use rlp::*; -use super::node::Node; -use super::recorder::{Recorder, NoOp}; -use super::{Trie, TrieItem, TrieError, TrieIterator}; +use super::node::{Node, OwnedNode}; +use super::lookup::Lookup; +use super::{Trie, TrieItem, TrieError, TrieIterator, Query}; /// A `Trie` implementation using a generic `HashDB` backing database. /// -/// Use it as a `Trie` trait object. You can use `db()` to get the backing database object, `keys` -/// to get the keys belonging to the trie in the backing database, and `db_items_remaining()` to get -/// which items in the backing database do not belong to this trie. If this is the only trie in the -/// backing database, then `db_items_remaining()` should be empty. +/// Use it as a `Trie` trait object. You can use `db()` to get the backing database object. +/// Use `get` and `contains` to query values associated with keys in the trie. /// /// # Example /// ``` @@ -45,7 +43,6 @@ use super::{Trie, TrieItem, TrieError, TrieIterator}; /// let t = TrieDB::new(&memdb, &root).unwrap(); /// assert!(t.contains(b"foo").unwrap()); /// assert_eq!(t.get(b"foo").unwrap().unwrap(), DBValue::from_slice(b"bar")); -/// assert!(t.db_items_remaining().unwrap().is_empty()); /// } /// ``` pub struct TrieDB<'db> { @@ -76,74 +73,12 @@ impl<'db> TrieDB<'db> { self.db } - /// Determine all the keys in the backing database that belong to the trie. - pub fn keys(&self) -> super::Result<Vec<H256>> { - let mut ret: Vec<H256> = Vec::new(); - ret.push(self.root.clone()); - self.accumulate_keys(self.root_node(&mut NoOp)?, &mut ret)?; - Ok(ret) - } - - /// Convert a vector of hashes to a hashmap of hash to occurrences. - pub fn to_map(hashes: Vec<H256>) -> HashMap<H256, u32> { - let mut r: HashMap<H256, u32> = HashMap::new(); - for h in hashes { - *r.entry(h).or_insert(0) += 1; - } - r - } - - /// Determine occurrences of items in the backing database which are not related to this - /// trie. - pub fn db_items_remaining(&self) -> super::Result<HashMap<H256, i32>> { - let mut ret = self.db.keys(); - for (k, v) in Self::to_map(self.keys()?) { - let keycount = *ret.get(&k).unwrap_or(&0); - match keycount <= v as i32 { - true => ret.remove(&k), - _ => ret.insert(k, keycount - v as i32), - }; - } - Ok(ret) - } - - /// Recursion helper for `keys`. - fn accumulate_keys(&self, node: Node, acc: &mut Vec<H256>) -> super::Result<()> { - let mut handle_payload = |payload| { - let p = Rlp::new(payload); - if p.is_data() && p.size() == 32 { - acc.push(p.as_val()); - } - - self.accumulate_keys(self.get_node(payload, &mut NoOp, 0)?, acc) - }; - - match node { - Node::Extension(_, ref payload) => handle_payload(payload)?, - Node::Branch(ref payloads, _) => for payload in payloads { handle_payload(payload)? }, - _ => {}, - } - - Ok(()) - } - - /// Get the root node's RLP. - fn root_node<R: Recorder>(&self, r: &mut R) -> super::Result<Node> { - self.root_data(r).map(|d| Node::decoded(&d)) - } - /// Get the data of the root node. - fn root_data<R: Recorder>(&self, r: &mut R) -> super::Result<DBValue> { + fn root_data(&self) -> super::Result<DBValue> { self.db.get(self.root).ok_or_else(|| Box::new(TrieError::InvalidStateRoot(*self.root))) - .map(|node| { r.record(self.root, &*node, 0); node }) } - /// Get the root node as a `Node`. - fn get_node<'a, R: 'a + Recorder>(&'db self, node: &'db [u8], r: &'a mut R, depth: u32) -> super::Result<Node> { - self.get_raw_or_lookup(node, r, depth).map(|n| Node::decoded(&n)) - } - - /// Indentation helper for `formal_all`. + /// Indentation helper for `format_all`. fn fmt_indent(&self, f: &mut fmt::Formatter, size: usize) -> fmt::Result { for _ in 0..size { write!(f, " ")?; @@ -157,8 +92,8 @@ impl<'db> TrieDB<'db> { Node::Leaf(slice, value) => writeln!(f, "'{:?}: {:?}.", slice, value.pretty())?, Node::Extension(ref slice, ref item) => { write!(f, "'{:?} ", slice)?; - if let Ok(node) = self.get_node(&*item, &mut NoOp, 0) { - self.fmt_all(node, f, deepness)?; + if let Ok(node) = self.get_raw_or_lookup(&*item) { + self.fmt_all(Node::decoded(&node), f, deepness)?; } }, Node::Branch(ref nodes, ref value) => { @@ -168,7 +103,8 @@ impl<'db> TrieDB<'db> { writeln!(f, "=: {:?}", v.pretty())? } for i in 0..16 { - match self.get_node(&*nodes[i], &mut NoOp, 0) { + let node = self.get_raw_or_lookup(&*nodes[i]); + match node.as_ref().map(|n| Node::decoded(&*n)) { Ok(Node::Empty) => {}, Ok(n) => { self.fmt_indent(f, deepness + 1)?; @@ -189,64 +125,49 @@ impl<'db> TrieDB<'db> { Ok(()) } - /// Return optional data for a key given as a `NibbleSlice`. Returns `None` if no data exists. - fn do_lookup<'key, R: 'key>(&'db self, key: &NibbleSlice<'key>, r: &'key mut R) -> super::Result<Option<DBValue>> - where 'db: 'key, R: Recorder - { - let root_rlp = self.root_data(r)?; - self.get_from_node(&root_rlp, key, r, 1) - } - - /// Recursible function to retrieve the value given a `node` and a partial `key`. `None` if no - /// value exists for the key. - /// - /// Note: Not a public API; use Trie trait functions. - fn get_from_node<'key, R: 'key>( - &'db self, - node: &'db [u8], - key: &NibbleSlice<'key>, - r: &'key mut R, - d: u32 - ) -> super::Result<Option<DBValue>> where 'db: 'key, R: Recorder { - match Node::decoded(node) { - Node::Leaf(ref slice, ref value) if NibbleSlice::from_encoded(slice).0 == *key => Ok(Some(value.clone())), - Node::Extension(ref slice, ref item) => { - let slice = &NibbleSlice::from_encoded(slice).0; - if key.starts_with(slice) { - let data = self.get_raw_or_lookup(&*item, r, d)?; - self.get_from_node(&data, &key.mid(slice.len()), r, d + 1) - } else { - Ok(None) - } - }, - Node::Branch(ref nodes, ref value) => match key.is_empty() { - true => Ok(value.clone()), - false => { - let node = self.get_raw_or_lookup(&*nodes[key.at(0) as usize], r, d)?; - self.get_from_node(&node, &key.mid(1), r, d + 1) - } - }, - _ => Ok(None) - } - } - /// Given some node-describing data `node`, return the actual node RLP. /// This could be a simple identity operation in the case that the node is sufficiently small, but /// may require a database lookup. - fn get_raw_or_lookup<R: Recorder>(&'db self, node: &'db [u8], rec: &mut R, d: u32) -> super::Result<DBValue> { + fn get_raw_or_lookup(&'db self, node: &'db [u8]) -> super::Result<DBValue> { // check if its sha3 + len let r = Rlp::new(node); match r.is_data() && r.size() == 32 { true => { let key = r.as_val::<H256>(); self.db.get(&key).ok_or_else(|| Box::new(TrieError::IncompleteDatabase(key))) - .map(|raw| { rec.record(&key, &raw, d); raw }) } false => Ok(DBValue::from_slice(node)) } } } +impl<'db> Trie for TrieDB<'db> { + fn iter<'a>(&'a self) -> super::Result<Box<TrieIterator<Item = TrieItem> + 'a>> { + TrieDBIterator::new(self).map(|iter| Box::new(iter) as Box<_>) + } + + fn root(&self) -> &H256 { self.root } + + fn get_with<'a, 'key, Q: Query>(&'a self, key: &'key [u8], query: Q) -> super::Result<Option<Q::Item>> + where 'a: 'key + { + Lookup { + db: self.db, + query: query, + hash: self.root.clone(), + }.look_up(NibbleSlice::new(key)) + } +} + +impl<'db> fmt::Debug for TrieDB<'db> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "c={:?} [", self.hash_count)?; + let root_rlp = self.db.get(self.root).expect("Trie root not found!"); + self.fmt_all(Node::decoded(&root_rlp), f, 0)?; + writeln!(f, "]") + } +} + #[derive(Clone, Eq, PartialEq)] enum Status { Entering, @@ -257,7 +178,7 @@ enum Status { #[derive(Clone, Eq, PartialEq)] struct Crumb { - node: Node, + node: OwnedNode, status: Status, } @@ -265,10 +186,10 @@ impl Crumb { /// Move on to next status in the node's sequence. fn increment(&mut self) { self.status = match (&self.status, &self.node) { - (_, &Node::Empty) => Status::Exiting, + (_, &OwnedNode::Empty) => Status::Exiting, (&Status::Entering, _) => Status::At, - (&Status::At, &Node::Branch(_, _)) => Status::AtChild(0), - (&Status::AtChild(x), &Node::Branch(_, _)) if x < 15 => Status::AtChild(x + 1), + (&Status::At, &OwnedNode::Branch(_, _)) => Status::AtChild(0), + (&Status::AtChild(x), &OwnedNode::Branch(_, _)) if x < 15 => Status::AtChild(x + 1), _ => Status::Exiting, } } @@ -291,41 +212,40 @@ impl<'a> TrieDBIterator<'a> { key_nibbles: Vec::new(), }; - db.root_data(&mut NoOp).and_then(|root| r.descend(&root))?; + db.root_data().and_then(|root| r.descend(&root))?; Ok(r) } - fn seek_descend<'key> ( &mut self, node: &[u8], key: &NibbleSlice<'key>, d: u32) -> super::Result<()> { - match Node::decoded(node) { + fn seek_descend<'key>(&mut self, node_data: DBValue, key: &NibbleSlice<'key>) -> super::Result<()> { + let node = Node::decoded(&node_data); + match node { Node::Leaf(ref slice, _) => { - let slice = &NibbleSlice::from_encoded(slice).0; if slice == key { self.trail.push(Crumb { status: Status::At, - node: Node::decoded(node), + node: node.clone().into(), }); } else { self.trail.push(Crumb { status: Status::Exiting, - node: Node::decoded(node), + node: node.clone().into(), }); } + self.key_nibbles.extend(slice.iter()); Ok(()) }, Node::Extension(ref slice, ref item) => { - let slice = &NibbleSlice::from_encoded(slice).0; if key.starts_with(slice) { - let mut r = NoOp; self.trail.push(Crumb { status: Status::At, - node: Node::decoded(node), + node: node.clone().into(), }); self.key_nibbles.extend(slice.iter()); - let data = self.db.get_raw_or_lookup(&*item, &mut r, d)?; - self.seek_descend(&data, &key.mid(slice.len()), d + 1) + let data = self.db.get_raw_or_lookup(&*item)?; + self.seek_descend(data, &key.mid(slice.len())) } else { - self.descend(node)?; + self.descend(&node_data)?; Ok(()) } }, @@ -333,20 +253,19 @@ impl<'a> TrieDBIterator<'a> { true => { self.trail.push(Crumb { status: Status::At, - node: Node::decoded(node), + node: node.clone().into(), }); Ok(()) }, false => { - let mut r = NoOp; let i = key.at(0); self.trail.push(Crumb { status: Status::AtChild(i as usize), - node: Node::decoded(node), + node: node.clone().into(), }); self.key_nibbles.push(i); - let child = self.db.get_raw_or_lookup(&*nodes[i as usize], &mut r, d)?; - self.seek_descend(&child, &key.mid(1), d + 1) + let child = self.db.get_raw_or_lookup(&*nodes[i as usize])?; + self.seek_descend(child, &key.mid(1)) } }, _ => Ok(()) @@ -357,10 +276,12 @@ impl<'a> TrieDBIterator<'a> { fn descend(&mut self, d: &[u8]) -> super::Result<()> { self.trail.push(Crumb { status: Status::Entering, - node: self.db.get_node(d, &mut NoOp, 0)?, + node: Node::decoded(&self.db.get_raw_or_lookup(d)?).into(), }); - match self.trail.last().expect("just pushed item; qed").node { - Node::Leaf(ref n, _) | Node::Extension(ref n, _) => { self.key_nibbles.extend(NibbleSlice::from_encoded(n).0.iter()); }, + match &self.trail.last().expect("just pushed item; qed").node { + &OwnedNode::Leaf(ref n, _) | &OwnedNode::Extension(ref n, _) => { + self.key_nibbles.extend((0..n.len()).map(|i| n.at(i))); + }, _ => {} } @@ -379,9 +300,8 @@ impl<'a> TrieIterator for TrieDBIterator<'a> { fn seek(&mut self, key: &[u8]) -> super::Result<()> { self.trail.clear(); self.key_nibbles.clear(); - let mut r = NoOp; - let root_rlp = self.db.root_data(&mut r)?; - self.seek_descend(&root_rlp, &NibbleSlice::new(key), 1) + let root_rlp = self.db.root_data()?; + self.seek_descend(root_rlp, &NibbleSlice::new(key)) } } @@ -397,27 +317,27 @@ impl<'a> Iterator for TrieDBIterator<'a> { match (b.status, b.node) { (Status::Exiting, n) => { match n { - Node::Leaf(n, _) | Node::Extension(n, _) => { + OwnedNode::Leaf(n, _) | OwnedNode::Extension(n, _) => { let l = self.key_nibbles.len(); - self.key_nibbles.truncate(l - NibbleSlice::from_encoded(&*n).0.len()); + self.key_nibbles.truncate(l - n.len()); }, - Node::Branch(_, _) => { self.key_nibbles.pop(); }, + OwnedNode::Branch(_, _) => { self.key_nibbles.pop(); }, _ => {} } self.trail.pop(); // continue }, - (Status::At, Node::Leaf(_, v)) | (Status::At, Node::Branch(_, Some(v))) => { + (Status::At, OwnedNode::Leaf(_, v)) | (Status::At, OwnedNode::Branch(_, Some(v))) => { return Some(Ok((self.key(), v))); }, - (Status::At, Node::Extension(_, d)) => { + (Status::At, OwnedNode::Extension(_, d)) => { if let Err(e) = self.descend(&*d) { return Some(Err(e)); } // continue }, - (Status::At, Node::Branch(_, _)) => {}, - (Status::AtChild(i), Node::Branch(ref children, _)) if children[i].len() > 0 => { + (Status::At, OwnedNode::Branch(_, _)) => {}, + (Status::AtChild(i), OwnedNode::Branch(ref children, _)) if children[i].len() > 0 => { match i { 0 => self.key_nibbles.push(0), i => *self.key_nibbles.last_mut() @@ -428,7 +348,7 @@ impl<'a> Iterator for TrieDBIterator<'a> { } // continue }, - (Status::AtChild(i), Node::Branch(_, _)) => { + (Status::AtChild(i), OwnedNode::Branch(_, _)) => { if i == 0 { self.key_nibbles.push(0); } @@ -440,29 +360,6 @@ impl<'a> Iterator for TrieDBIterator<'a> { } } -impl<'db> Trie for TrieDB<'db> { - fn iter<'a>(&'a self) -> super::Result<Box<TrieIterator<Item = TrieItem> + 'a>> { - TrieDBIterator::new(self).map(|iter| Box::new(iter) as Box<_>) - } - - fn root(&self) -> &H256 { self.root } - - fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> super::Result<Option<DBValue>> - where 'a: 'b, R: Recorder - { - self.do_lookup(&NibbleSlice::new(key), rec) - } -} - -impl<'db> fmt::Debug for TrieDB<'db> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "c={:?} [", self.hash_count)?; - let root_rlp = self.db.get(self.root).expect("Trie root not found!"); - self.fmt_all(Node::decoded(&root_rlp), f, 0)?; - writeln!(f, "]") - } -} - #[test] fn iterator() { use memorydb::*; @@ -529,3 +426,23 @@ fn iterator_seek() { iter.seek(b"C").unwrap(); assert_eq!(&d[4..], &iter.map(|x| x.unwrap().1).collect::<Vec<_>>()[..]); } + +#[test] +fn get_len() { + use memorydb::*; + use super::TrieMut; + use super::triedbmut::*; + + let mut memdb = MemoryDB::new(); + let mut root = H256::new(); + { + let mut t = TrieDBMut::new(&mut memdb, &mut root); + t.insert(b"A", b"ABC").unwrap(); + t.insert(b"B", b"ABCBA").unwrap(); + } + + let t = TrieDB::new(&memdb, &root).unwrap(); + assert_eq!(t.get_with(b"A", |x: &[u8]| x.len()), Ok(Some(3))); + assert_eq!(t.get_with(b"B", |x: &[u8]| x.len()), Ok(Some(5))); + assert_eq!(t.get_with(b"C", |x: &[u8]| x.len()), Ok(None)); +} diff --git a/util/src/trie/triedbmut.rs b/util/src/trie/triedbmut.rs index b062cd70b..0563adc1f 100644 --- a/util/src/trie/triedbmut.rs +++ b/util/src/trie/triedbmut.rs @@ -17,6 +17,7 @@ //! In-memory trie representation. use super::{TrieError, TrieMut}; +use super::lookup::Lookup; use super::node::Node as RlpNode; use super::node::NodeKey; @@ -100,22 +101,22 @@ impl Node { fn from_rlp(rlp: &[u8], db: &HashDB, storage: &mut NodeStorage) -> Self { match RlpNode::decoded(rlp) { RlpNode::Empty => Node::Empty, - RlpNode::Leaf(k, v) => Node::Leaf(k, v), + RlpNode::Leaf(k, v) => Node::Leaf(k.encoded(true), DBValue::from_slice(&v)), RlpNode::Extension(key, cb) => { - Node::Extension(key, Self::inline_or_hash(&*cb, db, storage)) + Node::Extension(key.encoded(false), Self::inline_or_hash(cb, db, storage)) } RlpNode::Branch(children_rlp, val) => { let mut children = empty_children(); for i in 0..16 { - let raw = &children_rlp[i]; - let child_rlp = Rlp::new(&*raw); + let raw = children_rlp[i]; + let child_rlp = Rlp::new(raw); if !child_rlp.is_empty() { - children[i] = Some(Self::inline_or_hash(&*raw, db, storage)); + children[i] = Some(Self::inline_or_hash(raw, db, storage)); } } - Node::Branch(children, val) + Node::Branch(children, val.map(DBValue::from_slice)) } } } @@ -370,7 +371,11 @@ impl<'a> TrieDBMut<'a> { where 'x: 'key { match *handle { - NodeHandle::Hash(ref hash) => self.do_db_lookup(hash, partial), + NodeHandle::Hash(ref hash) => Lookup { + db: &*self.db, + query: DBValue::from_slice, + hash: hash.clone(), + }.look_up(partial), NodeHandle::InMemory(ref handle) => match self.storage[handle] { Node::Empty => Ok(None), Node::Leaf(ref key, ref value) => { @@ -403,63 +408,17 @@ impl<'a> TrieDBMut<'a> { } } - /// Return optional data for a key given as a `NibbleSlice`. Returns `None` if no data exists. - fn do_db_lookup<'x, 'key>(&'x self, hash: &H256, key: NibbleSlice<'key>) -> super::Result<Option<DBValue>> - where 'x: 'key - { - self.db.get(hash).ok_or_else(|| Box::new(TrieError::IncompleteDatabase(*hash))) - .and_then(|node_rlp| self.get_from_db_node(&node_rlp, key)) - } - - /// Recursible function to retrieve the value given a `node` and a partial `key`. `None` if no - /// value exists for the key. - /// - /// Note: Not a public API; use Trie trait functions. - fn get_from_db_node<'x, 'key>(&'x self, node: &'x [u8], key: NibbleSlice<'key>) -> super::Result<Option<DBValue>> - where 'x: 'key - { - match RlpNode::decoded(node) { - RlpNode::Leaf(ref slice, ref value) if NibbleSlice::from_encoded(slice).0 == key => Ok(Some(value.clone())), - RlpNode::Extension(ref slice, ref item) => { - let slice = &NibbleSlice::from_encoded(slice).0; - if key.starts_with(slice) { - self.get_from_db_node(&self.get_raw_or_lookup(&*item)?, key.mid(slice.len())) - } else { - Ok(None) - } - }, - RlpNode::Branch(ref nodes, ref value) => match key.is_empty() { - true => Ok(value.clone()), - false => self.get_from_db_node(&self.get_raw_or_lookup(&*nodes[key.at(0) as usize])?, key.mid(1)) - }, - _ => Ok(None), - } - } - - /// Given some node-describing data `node`, return the actual node RLP. - /// This could be a simple identity operation in the case that the node is sufficiently small, but - /// may require a database lookup. - fn get_raw_or_lookup<'x>(&'x self, node: &'x [u8]) -> super::Result<DBValue> { - // check if its sha3 + len - let r = Rlp::new(node); - match r.is_data() && r.size() == 32 { - true => { - let key = r.as_val::<H256>(); - self.db.get(&key).ok_or_else(|| Box::new(TrieError::IncompleteDatabase(key))) - } - false => Ok(DBValue::from_slice(node)) - } - } - /// insert a key, value pair into the trie, creating new nodes if necessary. - fn insert_at(&mut self, handle: NodeHandle, partial: NibbleSlice, value: DBValue) -> super::Result<(StorageHandle, bool)> { + fn insert_at(&mut self, handle: NodeHandle, partial: NibbleSlice, value: DBValue, old_val: &mut Option<DBValue>) + -> super::Result<(StorageHandle, bool)> + { let h = match handle { NodeHandle::InMemory(h) => h, NodeHandle::Hash(h) => self.cache(h)?, }; let stored = self.storage.destroy(h); let (new_stored, changed) = self.inspect(stored, move |trie, stored| { - trie.insert_inspector(stored, partial, value).map(|a| a.into_action()) + trie.insert_inspector(stored, partial, value, old_val).map(|a| a.into_action()) })?.expect("Insertion never deletes."); Ok((self.storage.alloc(new_stored), changed)) @@ -467,7 +426,9 @@ impl<'a> TrieDBMut<'a> { /// the insertion inspector. #[cfg_attr(feature = "dev", allow(cyclomatic_complexity))] - fn insert_inspector(&mut self, node: Node, partial: NibbleSlice, value: DBValue) -> super::Result<InsertAction> { + fn insert_inspector(&mut self, node: Node, partial: NibbleSlice, value: DBValue, old_val: &mut Option<DBValue>) + -> super::Result<InsertAction> + { trace!(target: "trie", "augmented (partial: {:?}, value: {:?})", partial, value.pretty()); Ok(match node { @@ -481,6 +442,8 @@ impl<'a> TrieDBMut<'a> { if partial.is_empty() { let unchanged = stored_value.as_ref() == Some(&value); let branch = Node::Branch(children, Some(value)); + *old_val = stored_value; + match unchanged { true => InsertAction::Restore(branch), false => InsertAction::Replace(branch), @@ -490,7 +453,7 @@ impl<'a> TrieDBMut<'a> { let partial = partial.mid(1); if let Some(child) = children[idx].take() { // original had something there. recurse down into it. - let (new_child, changed) = self.insert_at(child, partial, value)?; + let (new_child, changed) = self.insert_at(child, partial, value, old_val)?; children[idx] = Some(new_child.into()); if !changed { // the new node we composed didn't change. that means our branch is untouched too. @@ -511,7 +474,10 @@ impl<'a> TrieDBMut<'a> { if cp == existing_key.len() && cp == partial.len() { trace!(target: "trie", "equivalent-leaf: REPLACE"); // equivalent leaf. - match stored_value == value { + let unchanged = stored_value == value; + *old_val = Some(stored_value); + + match unchanged { // unchanged. restore true => InsertAction::Restore(Node::Leaf(encoded.clone(), value)), false => InsertAction::Replace(Node::Leaf(encoded.clone(), value)), @@ -533,7 +499,7 @@ impl<'a> TrieDBMut<'a> { }; // always replace because whatever we get out here is not the branch we started with. - let branch_action = self.insert_inspector(branch, partial, value)?.unwrap_node(); + let branch_action = self.insert_inspector(branch, partial, value, old_val)?.unwrap_node(); InsertAction::Replace(branch_action) } else if cp == existing_key.len() { trace!(target: "trie", "complete-prefix (cp={:?}): AUGMENT-AT-END", cp); @@ -542,7 +508,7 @@ impl<'a> TrieDBMut<'a> { // make a stub branch and an extension. let branch = Node::Branch(empty_children(), Some(stored_value)); // augment the new branch. - let branch = self.insert_inspector(branch, partial.mid(cp), value)?.unwrap_node(); + let branch = self.insert_inspector(branch, partial.mid(cp), value, old_val)?.unwrap_node(); // always replace since we took a leaf and made an extension. let branch_handle = self.storage.alloc(Stored::New(branch)).into(); @@ -553,9 +519,10 @@ impl<'a> TrieDBMut<'a> { // partially-shared prefix for an extension. // start by making a leaf. let low = Node::Leaf(existing_key.mid(cp).encoded(true), stored_value); + // augment it. this will result in the Leaf -> cp == 0 routine, // which creates a branch. - let augmented_low = self.insert_inspector(low, partial.mid(cp), value)?.unwrap_node(); + let augmented_low = self.insert_inspector(low, partial.mid(cp), value, old_val)?.unwrap_node(); // make an extension using it. this is a replacement. InsertAction::Replace(Node::Extension( @@ -586,7 +553,7 @@ impl<'a> TrieDBMut<'a> { }; // continue inserting. - let branch_action = self.insert_inspector(Node::Branch(children, None), partial, value)?.unwrap_node(); + let branch_action = self.insert_inspector(Node::Branch(children, None), partial, value, old_val)?.unwrap_node(); InsertAction::Replace(branch_action) } else if cp == existing_key.len() { trace!(target: "trie", "complete-prefix (cp={:?}): AUGMENT-AT-END", cp); @@ -594,7 +561,7 @@ impl<'a> TrieDBMut<'a> { // fully-shared prefix. // insert into the child node. - let (new_child, changed) = self.insert_at(child_branch, partial.mid(cp), value)?; + let (new_child, changed) = self.insert_at(child_branch, partial.mid(cp), value, old_val)?; let new_ext = Node::Extension(existing_key.encoded(false), new_child.into()); // if the child branch wasn't changed, meaning this extension remains the same. @@ -608,7 +575,7 @@ impl<'a> TrieDBMut<'a> { // partially-shared. let low = Node::Extension(existing_key.mid(cp).encoded(false), child_branch); // augment the extension. this will take the cp == 0 path, creating a branch. - let augmented_low = self.insert_inspector(low, partial.mid(cp), value)?.unwrap_node(); + let augmented_low = self.insert_inspector(low, partial.mid(cp), value, old_val)?.unwrap_node(); // always replace, since this extension is not the one we started with. // this is known because the partial key is only the common prefix. @@ -622,7 +589,9 @@ impl<'a> TrieDBMut<'a> { } /// Remove a node from the trie based on key. - fn remove_at(&mut self, handle: NodeHandle, partial: NibbleSlice) -> super::Result<Option<(StorageHandle, bool)>> { + fn remove_at(&mut self, handle: NodeHandle, partial: NibbleSlice, old_val: &mut Option<DBValue>) + -> super::Result<Option<(StorageHandle, bool)>> + { let stored = match handle { NodeHandle::InMemory(h) => self.storage.destroy(h), NodeHandle::Hash(h) => { @@ -631,17 +600,18 @@ impl<'a> TrieDBMut<'a> { } }; - let opt = self.inspect(stored, move |trie, node| trie.remove_inspector(node, partial))?; + let opt = self.inspect(stored, move |trie, node| trie.remove_inspector(node, partial, old_val))?; Ok(opt.map(|(new, changed)| (self.storage.alloc(new), changed))) } /// the removal inspector - fn remove_inspector(&mut self, node: Node, partial: NibbleSlice) -> super::Result<Action> { + fn remove_inspector(&mut self, node: Node, partial: NibbleSlice, old_val: &mut Option<DBValue>) -> super::Result<Action> { Ok(match (node, partial.is_empty()) { (Node::Empty, _) => Action::Delete, (Node::Branch(c, None), true) => Action::Restore(Node::Branch(c, None)), - (Node::Branch(children, _), true) => { + (Node::Branch(children, Some(val)), true) => { + *old_val = Some(val); // always replace since we took the value out. Action::Replace(self.fix(Node::Branch(children, None))?) } @@ -649,7 +619,7 @@ impl<'a> TrieDBMut<'a> { let idx = partial.at(0) as usize; if let Some(child) = children[idx].take() { trace!(target: "trie", "removing value out of branch child, partial={:?}", partial); - match self.remove_at(child, partial.mid(1))? { + match self.remove_at(child, partial.mid(1), old_val)? { Some((new, changed)) => { children[idx] = Some(new.into()); let branch = Node::Branch(children, value); @@ -675,6 +645,7 @@ impl<'a> TrieDBMut<'a> { (Node::Leaf(encoded, value), _) => { if NibbleSlice::from_encoded(&encoded).0 == partial { // this is the node we were looking for. Let's delete it. + *old_val = Some(value); Action::Delete } else { // leaf the node alone. @@ -690,7 +661,7 @@ impl<'a> TrieDBMut<'a> { if cp == existing_len { // try to remove from the child branch. trace!(target: "trie", "removing from extension child, partial={:?}", partial); - match self.remove_at(child_branch, partial.mid(cp))? { + match self.remove_at(child_branch, partial.mid(cp), old_val)? { Some((new_child, changed)) => { let new_child = new_child.into(); @@ -907,28 +878,35 @@ impl<'a> TrieMut for TrieDBMut<'a> { } - fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<()> { - if value.is_empty() { - return self.remove(key); - } + fn insert(&mut self, key: &[u8], value: &[u8]) -> super::Result<Option<DBValue>> { + if value.is_empty() { return self.remove(key) } + + let mut old_val = None; trace!(target: "trie", "insert: key={:?}, value={:?}", key.pretty(), value.pretty()); let root_handle = self.root_handle(); - let (new_handle, changed) = self.insert_at(root_handle, NibbleSlice::new(key), DBValue::from_slice(value))?; + let (new_handle, changed) = self.insert_at( + root_handle, + NibbleSlice::new(key), + DBValue::from_slice(value), + &mut old_val, + )?; trace!(target: "trie", "insert: altered trie={}", changed); self.root_handle = NodeHandle::InMemory(new_handle); - Ok(()) + Ok(old_val) } - fn remove(&mut self, key: &[u8]) -> super::Result<()> { + fn remove(&mut self, key: &[u8]) -> super::Result<Option<DBValue>> { trace!(target: "trie", "remove: key={:?}", key.pretty()); let root_handle = self.root_handle(); let key = NibbleSlice::new(key); - match self.remove_at(root_handle, key)? { + let mut old_val = None; + + match self.remove_at(root_handle, key, &mut old_val)? { Some((handle, changed)) => { trace!(target: "trie", "remove: altered trie={}", changed); self.root_handle = NodeHandle::InMemory(handle); @@ -938,9 +916,9 @@ impl<'a> TrieMut for TrieDBMut<'a> { self.root_handle = NodeHandle::Hash(SHA3_NULL_RLP); *self.root = SHA3_NULL_RLP; } - }; + } - Ok(()) + Ok(old_val) } } @@ -1287,4 +1265,29 @@ mod tests { assert!(t.is_empty()); assert_eq!(*t.root(), SHA3_NULL_RLP); } + + #[test] + fn return_old_values() { + let mut seed = H256::new(); + let x = StandardMap { + alphabet: Alphabet::Custom(b"@QWERTYUIOPASDFGHJKLZXCVBNM[/]^_".to_vec()), + min_key: 5, + journal_key: 0, + value_mode: ValueMode::Index, + count: 4, + }.make_with(&mut seed); + + let mut db = MemoryDB::new(); + let mut root = H256::new(); + let mut t = TrieDBMut::new(&mut db, &mut root); + for &(ref key, ref value) in &x { + assert!(t.insert(key, value).unwrap().is_none()); + assert_eq!(t.insert(key, value).unwrap(), Some(DBValue::from_slice(value))); + } + + for (key, value) in x { + assert_eq!(t.remove(&key).unwrap(), Some(DBValue::from_slice(&value))); + assert!(t.remove(&key).unwrap().is_none()); + } + } }