Merge branch 'master' into pip-msg

This commit is contained in:
Robert Habermeier 2017-03-08 18:54:48 +01:00
commit 868f83e6ca
301 changed files with 9175 additions and 3037 deletions

View File

@ -1,8 +1,8 @@
stages: stages:
- test - test
- js-build - js-build
- push-release
- build - build
- push-release
variables: variables:
GIT_DEPTH: "3" GIT_DEPTH: "3"
SIMPLECOV: "true" SIMPLECOV: "true"
@ -499,8 +499,7 @@ docker-build:
before_script: before_script:
- docker info - docker info
script: script:
- cd docker/hub - if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
- if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
- sh scripts/docker-build.sh $DOCKER_TAG - sh scripts/docker-build.sh $DOCKER_TAG
tags: tags:

276
Cargo.lock generated
View File

@ -9,22 +9,22 @@ dependencies = [
"daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-dapps 1.6.0", "ethcore-dapps 1.7.0",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-hypervisor 1.2.0", "ethcore-ipc-hypervisor 1.2.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-ipc-tests 0.1.0", "ethcore-ipc-tests 0.1.0",
"ethcore-light 1.6.0", "ethcore-light 1.7.0",
"ethcore-logger 1.6.0", "ethcore-logger 1.7.0",
"ethcore-rpc 1.6.0", "ethcore-rpc 1.7.0",
"ethcore-secretstore 1.0.0", "ethcore-secretstore 1.0.0",
"ethcore-signer 1.6.0", "ethcore-signer 1.7.0",
"ethcore-stratum 1.6.0", "ethcore-stratum 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethsync 1.6.0", "ethsync 1.7.0",
"evmbin 0.1.0", "evmbin 0.1.0",
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
@ -33,12 +33,12 @@ dependencies = [
"log 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)",
"num_cpus 0.2.11 (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)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.6.0", "parity-hash-fetch 1.7.0",
"parity-ipfs-api 1.6.0", "parity-ipfs-api 1.7.0",
"parity-local-store 0.1.0", "parity-local-store 0.1.0",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
"parity-rpc-client 1.4.0", "parity-rpc-client 1.4.0",
"parity-updater 1.6.0", "parity-updater 1.7.0",
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -344,7 +344,7 @@ dependencies = [
[[package]] [[package]]
name = "eth-secp256k1" name = "eth-secp256k1"
version = "0.5.6" version = "0.5.6"
source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16" source = "git+https://github.com/ethcore/rust-secp256k1#98ad9b9ecae44a563efdd64273bcebc6b4ed81c6"
dependencies = [ dependencies = [
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
@ -367,7 +367,7 @@ dependencies = [
[[package]] [[package]]
name = "ethash" name = "ethash"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"log 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)",
"parking_lot 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)",
@ -377,7 +377,7 @@ dependencies = [
[[package]] [[package]]
name = "ethcore" name = "ethcore"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -386,20 +386,20 @@ dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethash 1.6.0", "ethash 1.7.0",
"ethcore-bloom-journal 0.1.0", "ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-stratum 1.6.0", "ethcore-stratum 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethjson 0.1.0", "ethjson 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"ethstore 0.1.0", "ethstore 0.1.0",
"evmjit 1.6.0", "evmjit 1.7.0",
"hardware-wallet 1.6.0", "hardware-wallet 1.7.0",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "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)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -436,14 +436,14 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-dapps" name = "ethcore-dapps"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-rpc 1.6.0", "ethcore-rpc 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"fetch 0.1.0", "fetch 0.1.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
@ -454,9 +454,9 @@ dependencies = [
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.6.0", "parity-hash-fetch 1.7.0",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
"parity-ui 1.6.0", "parity-ui 1.7.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -470,14 +470,14 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-devtools" name = "ethcore-devtools"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "ethcore-io" name = "ethcore-io"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -488,17 +488,17 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-ipc" name = "ethcore-ipc"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "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)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "ethcore-ipc-codegen" name = "ethcore-ipc-codegen"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -511,9 +511,9 @@ dependencies = [
name = "ethcore-ipc-hypervisor" name = "ethcore-ipc-hypervisor"
version = "1.2.0" version = "1.2.0"
dependencies = [ dependencies = [
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"log 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)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "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)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -522,9 +522,9 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-ipc-nano" name = "ethcore-ipc-nano"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"lazy_static 0.2.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)", "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)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
@ -534,11 +534,11 @@ dependencies = [
name = "ethcore-ipc-tests" name = "ethcore-ipc-tests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"log 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)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "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)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -546,14 +546,14 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-light" name = "ethcore-light"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-network 1.6.0", "ethcore-network 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 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)",
@ -566,10 +566,10 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-logger" name = "ethcore-logger"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -579,13 +579,13 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-network" name = "ethcore-network"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -604,21 +604,21 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-rpc" name = "ethcore-rpc"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"ethash 1.6.0", "ethash 1.7.0",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-light 1.6.0", "ethcore-light 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethjson 0.1.0", "ethjson 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"ethstore 0.1.0", "ethstore 0.1.0",
"ethsync 1.6.0", "ethsync 1.7.0",
"fetch 0.1.0", "fetch 0.1.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
@ -628,7 +628,7 @@ dependencies = [
"log 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)",
"order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
"parity-updater 1.6.0", "parity-updater 1.7.0",
"rlp 0.1.0", "rlp 0.1.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -644,11 +644,11 @@ dependencies = [
name = "ethcore-secretstore" name = "ethcore-secretstore"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -659,18 +659,18 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-signer" name = "ethcore-signer"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-rpc 1.6.0", "ethcore-rpc 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"log 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)",
"parity-dapps-glue 1.4.0 (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.6.0", "parity-ui 1.7.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)",
@ -678,14 +678,14 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-stratum" name = "ethcore-stratum"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"env_logger 0.3.3 (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.6.0", "ethcore-devtools 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
@ -699,7 +699,7 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-util" name = "ethcore-util"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -709,7 +709,7 @@ dependencies = [
"eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)",
"ethcore-bigint 0.1.2", "ethcore-bigint 0.1.2",
"ethcore-bloom-journal 0.1.0", "ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -748,7 +748,7 @@ dependencies = [
name = "ethjson" name = "ethjson"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"rustc-serialize 0.3.19 (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.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -775,8 +775,8 @@ name = "ethstore"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -797,19 +797,19 @@ dependencies = [
[[package]] [[package]]
name = "ethsync" name = "ethsync"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-devtools 1.6.0", "ethcore-devtools 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.6.0", "ethcore-ipc-nano 1.7.0",
"ethcore-light 1.6.0", "ethcore-light 1.7.0",
"ethcore-network 1.6.0", "ethcore-network 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -825,14 +825,14 @@ name = "evmbin"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "evmjit" name = "evmjit"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -913,7 +913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "hardware-wallet" name = "hardware-wallet"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore-bigint 0.1.2", "ethcore-bigint 0.1.2",
"ethkey 0.2.0", "ethkey 0.2.0",
@ -976,7 +976,7 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.10.0-a.0" version = "0.10.0-a.0"
source = "git+https://github.com/ethcore/hyper#2e6702984f4f9e99fe251537a755aff0badc0b3a" source = "git+https://github.com/ethcore/hyper#453c683b52208fefc32d29e4ac7c863439b2321f"
dependencies = [ dependencies = [
"cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1050,11 +1050,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "ipc-common-types" name = "ipc-common-types"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1618,10 +1618,10 @@ dependencies = [
[[package]] [[package]]
name = "parity-hash-fetch" name = "parity-hash-fetch"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"fetch 0.1.0", "fetch 0.1.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"log 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)",
@ -1634,11 +1634,11 @@ dependencies = [
[[package]] [[package]]
name = "parity-ipfs-api" name = "parity-ipfs-api"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
"jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1650,9 +1650,9 @@ dependencies = [
name = "parity-local-store" name = "parity-local-store"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-io 1.6.0", "ethcore-io 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethkey 0.2.0", "ethkey 0.2.0",
"log 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)",
"rlp 0.1.0", "rlp 0.1.0",
@ -1673,9 +1673,9 @@ dependencies = [
name = "parity-rpc-client" name = "parity-rpc-client"
version = "1.4.0" version = "1.4.0"
dependencies = [ dependencies = [
"ethcore-rpc 1.6.0", "ethcore-rpc 1.7.0",
"ethcore-signer 1.6.0", "ethcore-signer 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"log 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)",
@ -1689,7 +1689,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui" name = "parity-ui"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"parity-ui-dev 1.4.0", "parity-ui-dev 1.4.0",
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)",
@ -1706,24 +1706,24 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#aaee793907e4ff61082d83ff44733363dfff6eae" source = "git+https://github.com/ethcore/js-precompiled.git#9eef2b78d363560fe942062caaaa7f6b1d64dd17"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "parity-updater" name = "parity-updater"
version = "1.6.0" version = "1.7.0"
dependencies = [ dependencies = [
"ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.6.0", "ethcore 1.7.0",
"ethcore-ipc 1.6.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.6.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"ethsync 1.6.0", "ethsync 1.7.0",
"ipc-common-types 1.6.0", "ipc-common-types 1.7.0",
"log 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)",
"parity-hash-fetch 1.6.0", "parity-hash-fetch 1.7.0",
"parity-reactor 0.1.0", "parity-reactor 0.1.0",
"target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1992,8 +1992,8 @@ name = "rpc-cli"
version = "1.4.0" version = "1.4.0"
dependencies = [ dependencies = [
"ethcore-bigint 0.1.2", "ethcore-bigint 0.1.2",
"ethcore-rpc 1.6.0", "ethcore-rpc 1.7.0",
"ethcore-util 1.6.0", "ethcore-util 1.7.0",
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-rpc-client 1.4.0", "parity-rpc-client 1.4.0",
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -100,7 +100,11 @@ $ cargo build --release
``` ```
This will produce an executable in the `./target/release` subdirectory. This will produce an executable in the `./target/release` subdirectory.
Note: if cargo fails to parse manifest try:
```bash
$ ~/.cargo/bin/cargo build --release
```
---- ----
## Simple one-line installer for Mac and Ubuntu ## Simple one-line installer for Mac and Ubuntu

View File

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

View File

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

View File

@ -39,7 +39,11 @@ pub struct RestApi {
impl RestApi { impl RestApi {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> { pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
Box::new(RestApi { Box::new(RestApi {
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
"null" => AccessControlAllowOrigin::Null,
other => AccessControlAllowOrigin::Value(other.into()),
}).collect()),
endpoints: endpoints, endpoints: endpoints,
fetcher: fetcher, fetcher: fetcher,
}) })

View File

@ -111,6 +111,7 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
web_proxy_tokens: Arc<WebProxyTokens>, web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>, signer_address: Option<(String, u16)>,
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
remote: Remote, remote: Remote,
fetch: Option<T>, fetch: Option<T>,
} }
@ -126,6 +127,7 @@ impl ServerBuilder {
web_proxy_tokens: Arc::new(|_| false), web_proxy_tokens: Arc::new(|_| false),
signer_address: None, signer_address: None,
allowed_hosts: Some(vec![]), allowed_hosts: Some(vec![]),
extra_cors: None,
remote: remote, remote: remote,
fetch: None, fetch: None,
} }
@ -143,6 +145,7 @@ impl<T: Fetch> ServerBuilder<T> {
web_proxy_tokens: self.web_proxy_tokens, web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address, signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts, allowed_hosts: self.allowed_hosts,
extra_cors: self.extra_cors,
remote: self.remote, remote: self.remote,
fetch: Some(fetch), fetch: Some(fetch),
} }
@ -174,6 +177,13 @@ impl<T: Fetch> ServerBuilder<T> {
self self
} }
/// Extra cors headers.
/// `None` - no additional CORS URLs
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
self.extra_cors = cors;
self
}
/// Change extra dapps paths (apart from `dapps_path`) /// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self { pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
@ -187,6 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http( Server::start_http(
addr, addr,
self.allowed_hosts, self.allowed_hosts,
self.extra_cors,
NoAuth, NoAuth,
handler, handler,
self.dapps_path, self.dapps_path,
@ -207,6 +218,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http( Server::start_http(
addr, addr,
self.allowed_hosts, self.allowed_hosts,
self.extra_cors,
HttpBasicAuth::single_user(username, password), HttpBasicAuth::single_user(username, password),
handler, handler,
self.dapps_path, self.dapps_path,
@ -251,8 +263,8 @@ impl Server {
} }
/// Returns a list of CORS domains for API endpoint. /// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> { fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
match signer_address { let basic_cors = match signer_address {
Some(signer_address) => vec![ Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
@ -260,15 +272,20 @@ impl Server {
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)), format!("https://{}", address(&signer_address)),
], ],
None => vec![], None => vec![],
};
match extra_cors {
None => basic_cors,
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
} }
} }
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>( fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
addr: &SocketAddr, addr: &SocketAddr,
hosts: Option<Vec<String>>, hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
authorization: A, authorization: A,
handler: RpcHandler<Metadata, T>, handler: RpcHandler<Metadata, T>,
dapps_path: PathBuf, dapps_path: PathBuf,
@ -297,7 +314,7 @@ impl Server {
remote.clone(), remote.clone(),
fetch.clone(), fetch.clone(),
)); ));
let cors_domains = Self::cors_domains(signer_address.clone()); let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();
@ -413,8 +430,9 @@ mod util_tests {
// given // given
// when // when
let none = Server::cors_domains(None); let none = Server::cors_domains(None, None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
// then // then
assert_eq!(none, Vec::<String>::new()); assert_eq!(none, Vec::<String>::new());
@ -425,7 +443,7 @@ mod util_tests {
"https://parity.web3.site".into(), "https://parity.web3.site".into(),
"https://parity.web3.site:18180".into(), "https://parity.web3.site:18180".into(),
"https://127.0.0.1:18180".into() "https://127.0.0.1:18180".into()
]); ]);
assert_eq!(extra, vec!["all".to_owned()]);
} }
} }

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers};
#[test] #[test]
fn should_return_error() { fn should_return_error() {
@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
); );
} }
#[test]
fn should_return_extra_cors_headers() {
// given
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://somedomain.io\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
}

View File

@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
} }
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
}
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) { pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
FROM ubuntu:14.04 FROM ubuntu:14.04
MAINTAINER Parity Technologies <devops@parity.io> MAINTAINER Parity Technologies <devops@parity.io>
WORKDIR /build WORKDIR /build
#ENV for build TAG
ARG BUILD_TAG
ENV BUILD_TAG ${BUILD_TAG:-master}
RUN echo $BUILD_TAG
# install tools and dependencies # install tools and dependencies
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --force-yes --no-install-recommends \ apt-get install -y --force-yes --no-install-recommends \
@ -47,7 +51,7 @@ RUN apt-get update && \
cd /build&&git clone https://github.com/ethcore/parity && \ cd /build&&git clone https://github.com/ethcore/parity && \
cd parity && \ cd parity && \
git pull&& \ git pull&& \
git checkout $CI_BUILD_REF_NAME && \ git checkout $BUILD_TAG && \
cargo build --verbose --release --features final && \ cargo build --verbose --release --features final && \
#ls /build/parity/target/release/parity && \ #ls /build/parity/target/release/parity && \
strip /build/parity/target/release/parity && \ strip /build/parity/target/release/parity && \

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@
"timestamp": "0x00", "timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x", "extraData": "0x",
"gasLimit": "0x2fefd8" "gasLimit": "0x222222"
}, },
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@ -48,18 +48,12 @@
"stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"
}, },
"nodes": [ "nodes": [
"enode://08c7ee6a4f861ff0664a49532bcc86de1363acd608999d1b76609bb9bc278649906f069057630fd9493924a368b5d1dc9b8f8bf13ac26df72512f6d1fabd8c95@45.32.7.81:30303",
"enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303", "enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303",
"enode://687be94c3a7beaa3d2fde82fa5046cdeb3e8198354e05b29d6e0d4e276713e3707ac10f784a7904938b06b46c764875c241b0337dd853385a4d8bfcbf8190647@95.183.51.229:30303",
"enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303", "enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303",
"enode://ca5ae4eca09ba6787e29cf6d86f7634d07aae6b9e6317a59aff675851c0bf445068173208cf8ef7f5cd783d8e29b85b2fa3fa358124cf0546823149724f9bde1@138.68.1.16:30303", "enode://5fbfb426fbb46f8b8c1bd3dd140f5b511da558cd37d60844b525909ab82e13a25ee722293c829e52cb65c2305b1637fa9a2ea4d6634a224d5f400bfe244ac0de@162.243.55.45:30303",
"enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303", "enode://42d8f29d1db5f4b2947cd5c3d76c6d0d3697e6b9b3430c3d41e46b4bb77655433aeedc25d4b4ea9d8214b6a43008ba67199374a9b53633301bca0cd20c6928ab@104.155.176.151:30303",
"enode://fa20444ef991596ce99b81652ac4e61de1eddc4ff21d3cd42762abd7ed47e7cf044d3c9ccddaf6035d39725e4eb372806787829ccb9a08ec7cb71883cb8c3abd@50.149.116.182:30303", "enode://814920f1ec9510aa9ea1c8f79d8b6e6a462045f09caa2ae4055b0f34f7416fca6facd3dd45f1cf1673c0209e0503f02776b8ff94020e98b6679a0dc561b4eba0@104.154.136.117:30303",
"enode://4bd6a4df3612c718333eb5ea7f817923a8cdf1bed89cee70d1710b45a0b6b77b2819846440555e451a9b602ad2efa2d2facd4620650249d8468008946887820a@71.178.232.20:30304", "enode://72e445f4e89c0f476d404bc40478b0df83a5b500d2d2e850e08eb1af0cd464ab86db6160d0fde64bd77d5f0d33507ae19035671b3c74fec126d6e28787669740@104.198.71.200:30303"
"enode://921cf8e4c345fe8db913c53964f9cadc667644e7f20195a0b7d877bd689a5934e146ff2c2259f1bae6817b6585153a007ceb67d260b720fa3e6fc4350df25c7f@51.255.49.170:30303",
"enode://ffea3b01c000cdd89e1e9229fea3e80e95b646f9b2aa55071fc865e2f19543c9b06045cc2e69453e6b78100a119e66be1b5ad50b36f2ffd27293caa28efdd1b2@128.199.93.177:3030",
"enode://ee3da491ce6a155eb132708eb0e8d04b0637926ec0ae1b79e63fc97cb9fc3818f49250a0ae0d7f79ed62b66ec677f408c4e01741504dc7a051e274f1e803d454@91.121.65.179:40404",
"enode://48e063a6cf5f335b1ef2ed98126bf522cf254396f850c7d442fe2edbbc23398787e14cd4de7968a00175a82762de9cbe9e1407d8ccbcaeca5004d65f8398d759@159.203.255.59:30303"
], ],
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@ -1,5 +1,5 @@
{ {
"name": "Frontier/Homestead", "name": "Foundation",
"dataDir": "ethereum", "dataDir": "ethereum",
"engine": { "engine": {
"Ethash": { "Ethash": {
@ -9,7 +9,7 @@
"difficultyBoundDivisor": "0x0800", "difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea", "registrar" : "0xe3389675d0338462dC76C6f9A3e432550c36A142",
"homesteadTransition": "0x118c30", "homesteadTransition": "0x118c30",
"daoHardforkTransition": "0x1d4c00", "daoHardforkTransition": "0x1d4c00",
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",

View File

@ -0,0 +1,59 @@
{
"name": "Kovan",
"dataDir": "kovan",
"engine": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x400",
"registrar" : "0xfAb104398BBefbd47752E7702D9fE23047E1Bca3",
"stepDuration": "4",
"blockReward": "0x4563918244F40000",
"validators" : {
"list": [
"0x00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED",
"0x00427feae2419c15b89d1c21af10d1b6650a4d3d",
"0x4Ed9B08e6354C70fE6F8CB0411b0d3246b424d6c",
"0x0020ee4Be0e2027d76603cB751eE069519bA81A1",
"0x0010f94b296a852aaac52ea6c5ac72e03afd032d",
"0x007733a1FE69CF3f2CF989F81C7b4cAc1693387A",
"0x00E6d2b931F55a3f1701c7389d592a7778897879",
"0x00e4a10650e5a6D6001C38ff8E64F97016a1645c",
"0x00a0a24b9f0e5ec7aa4c7389b8302fd0123194de"
]
}
}
}
},
"params": {
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2A"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x5B8D80"
},
"accounts": {
"0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0x00521965e7bd230323c423d96c657db5b79d099f": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
},
"nodes": [
"enode://c005dd308256c60fab247813d8bf6d6e81f9cd354287837eb1c2fcf294adaa913a3208e88900ef5c55a8cba7042c301d80503edec2ad3f92a72e241ee6743854@192.241.230.87:30303",
"enode://48caeceb2724f2f71406990aa81efe87f8c53f26441d891473da2ae50cc138f238addc0e46b5aee240db55de8c711daac53d7b32a3f13e30edb86a3ca7c2700b@138.68.143.220:30303",
"enode://85705212fd28ebdd56669fb55e958feb9d81f74fe76c82f867564b6c2995e69f596df0f588eba16f1a43b69ce06485d68231a0c83fed8469b41eba0e390c126f@139.59.146.42:30303",
"enode://2aa81bd0a761cd4f02c934dcf3f81c5b65953e51ab5ba03ceb1f125eb06418a1cdffb1c9d01871aa7bd456f3fce35e745608189ad1164f72b2161634b0c3f6ea@188.166.240.190:30303",
"enode://c5900cdd6d20795d58372f42dfbab9d664c27bb97e9c27972741942736e919122f9bac28e74cbc58e4ff195475ea90d9880b71a37af5b5a8cb41d843f765cff8@174.138.79.48:30303"
]
}

View File

@ -48,7 +48,10 @@
}, },
"nodes": [ "nodes": [
"enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303", "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303",
"enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303" "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303",
"enode://fb28713820e718066a2f5df6250ae9d07cff22f672dbf26be6c75d088f821a9ad230138ba492c533a80407d054b1436ef18e951bb65e6901553516c8dffe8ff0@104.155.176.151:30304",
"enode://afdc6076b9bf3e7d3d01442d6841071e84c76c73a7016cb4f35c0437df219db38565766234448f1592a07ba5295a867f0ce87b359bf50311ed0b830a2361392d@104.154.136.117:30403",
"enode://21101a9597b79e933e17bc94ef3506fe99a137808907aa8fefa67eea4b789792ad11fb391f38b00087f8800a2d3dff011572b62a31232133dd1591ac2d1502c8@104.198.71.200:30403"
], ],
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@
"timestamp": "0x00", "timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x", "extraData": "0x",
"gasLimit": "0x2fefd8" "gasLimit": "0x222222"
}, },
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@ -27,7 +27,7 @@
"timestamp": "0x00", "timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x", "extraData": "0x",
"gasLimit": "0x2fefd8" "gasLimit": "0x222222"
}, },
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@ -253,7 +253,7 @@ impl Client {
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
trace!(target: "client", "Found registrar at {}", reg_addr); trace!(target: "client", "Found registrar at {}", reg_addr);
let weak = Arc::downgrade(&client); let weak = Arc::downgrade(&client);
let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d)));
*client.registrar.lock() = Some(registrar); *client.registrar.lock() = Some(registrar);
} }
Ok(client) Ok(client)
@ -1428,7 +1428,7 @@ impl BlockChainClient for Client {
} }
} }
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String> { fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
let from = Address::default(); let from = Address::default();
let transaction = Transaction { let transaction = Transaction {
nonce: self.latest_nonce(&from), nonce: self.latest_nonce(&from),
@ -1439,7 +1439,7 @@ impl BlockChainClient for Client {
data: data, data: data,
}.fake_sign(from); }.fake_sign(from);
self.call(&transaction, BlockId::Latest, Default::default()) self.call(&transaction, block_id, Default::default())
.map_err(|e| format!("{:?}", e)) .map_err(|e| format!("{:?}", e))
.map(|executed| { .map(|executed| {
executed.output executed.output
@ -1612,7 +1612,6 @@ impl ::client::ProvingBlockChainClient for Client {
_ => return Some(state.drop().1.extract_proof()), _ => return Some(state.drop().1.extract_proof()),
} }
} }
} }
impl Drop for Client { impl Drop for Client {

File diff suppressed because one or more lines are too long

View File

@ -732,7 +732,7 @@ impl BlockChainClient for TestBlockChainClient {
} }
} }
fn call_contract(&self, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) } fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError> { fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError> {
let transaction = Transaction { let transaction = Transaction {

View File

@ -256,7 +256,7 @@ pub trait BlockChainClient : Sync + Send {
fn pruning_info(&self) -> PruningInfo; fn pruning_info(&self) -> PruningInfo;
/// Like `call`, but with various defaults. Designed to be used for calling contracts. /// Like `call`, but with various defaults. Designed to be used for calling contracts.
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String>; fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String>;
/// Import a transaction: used for misbehaviour reporting. /// Import a transaction: used for misbehaviour reporting.
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError>; fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError>;

View File

@ -47,6 +47,8 @@ pub struct AuthorityRoundParams {
pub step_duration: Duration, pub step_duration: Duration,
/// Block reward. /// Block reward.
pub block_reward: U256, pub block_reward: U256,
/// Namereg contract address.
pub registrar: Address,
/// Starting step, /// Starting step,
pub start_step: Option<u64>, pub start_step: Option<u64>,
/// Valid validators. /// Valid validators.
@ -60,6 +62,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
step_duration: Duration::from_secs(p.step_duration.into()), step_duration: Duration::from_secs(p.step_duration.into()),
validators: p.validators, validators: p.validators,
block_reward: p.block_reward.map_or_else(U256::zero, Into::into), block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
registrar: p.registrar.map_or_else(Address::new, Into::into),
start_step: p.start_step.map(Into::into), start_step: p.start_step.map(Into::into),
} }
} }
@ -71,6 +74,7 @@ pub struct AuthorityRound {
params: CommonParams, params: CommonParams,
gas_limit_bound_divisor: U256, gas_limit_bound_divisor: U256,
block_reward: U256, block_reward: U256,
registrar: Address,
step_duration: Duration, step_duration: Duration,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<()>, transition_service: IoService<()>,
@ -79,6 +83,8 @@ pub struct AuthorityRound {
client: RwLock<Option<Weak<EngineClient>>>, client: RwLock<Option<Weak<EngineClient>>>,
signer: EngineSigner, signer: EngineSigner,
validators: Box<ValidatorSet + Send + Sync>, validators: Box<ValidatorSet + Send + Sync>,
/// Is this Engine just for testing (prevents step calibration).
calibrate_step: bool,
} }
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> { fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@ -109,6 +115,7 @@ impl AuthorityRound {
params: params, params: params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
block_reward: our_params.block_reward, block_reward: our_params.block_reward,
registrar: our_params.registrar,
step_duration: our_params.step_duration, step_duration: our_params.step_duration,
builtins: builtins, builtins: builtins,
transition_service: IoService::<()>::start()?, transition_service: IoService::<()>::start()?,
@ -117,6 +124,7 @@ impl AuthorityRound {
client: RwLock::new(None), client: RwLock::new(None),
signer: Default::default(), signer: Default::default(),
validators: new_validator_set(our_params.validators), validators: new_validator_set(our_params.validators),
calibrate_step: our_params.start_step.is_none(),
}); });
// Do not initialize timeouts for tests. // Do not initialize timeouts for tests.
if should_timeout { if should_timeout {
@ -126,6 +134,12 @@ impl AuthorityRound {
Ok(engine) Ok(engine)
} }
fn calibrate_step(&self) {
if self.calibrate_step {
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
}
}
fn remaining_step_duration(&self) -> Duration { fn remaining_step_duration(&self) -> Duration {
let now = unix_now(); let now = unix_now();
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
@ -136,12 +150,22 @@ impl AuthorityRound {
} }
} }
fn step_proposer(&self, step: usize) -> Address { fn step_proposer(&self, bh: &H256, step: usize) -> Address {
self.validators.get(step) self.validators.get(bh, step)
} }
fn is_step_proposer(&self, step: usize, address: &Address) -> bool { fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
self.step_proposer(step) == *address self.step_proposer(bh, step) == *address
}
fn is_future_step(&self, step: usize) -> bool {
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
// Make absolutely sure that the step is correct.
self.calibrate_step();
step > self.step.load(AtomicOrdering::SeqCst) + 1
} else {
false
}
} }
} }
@ -176,11 +200,16 @@ impl IoHandler<()> for TransitionHandler {
impl Engine for AuthorityRound { impl Engine for AuthorityRound {
fn name(&self) -> &str { "AuthorityRound" } fn name(&self) -> &str { "AuthorityRound" }
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
/// Two fields - consensus step and the corresponding proposer signature. /// Two fields - consensus step and the corresponding proposer signature.
fn seal_fields(&self) -> usize { 2 } fn seal_fields(&self) -> usize { 2 }
fn params(&self) -> &CommonParams { &self.params } fn params(&self) -> &CommonParams { &self.params }
fn additional_params(&self) -> HashMap<String, String> { hash_map!["registrar".to_owned() => self.registrar.hex()] }
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
fn step(&self) { fn step(&self) {
@ -221,7 +250,7 @@ impl Engine for AuthorityRound {
} }
fn seals_internally(&self) -> Option<bool> { fn seals_internally(&self) -> Option<bool> {
Some(self.validators.contains(&self.signer.address())) Some(self.signer.address() != Address::default())
} }
/// Attempt to seal the block internally. /// Attempt to seal the block internally.
@ -232,7 +261,7 @@ impl Engine for AuthorityRound {
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
let header = block.header(); let header = block.header();
let step = self.step.load(AtomicOrdering::SeqCst); let step = self.step.load(AtomicOrdering::SeqCst);
if self.is_step_proposer(step, header.author()) { if self.is_step_proposer(header.parent_hash(), step, header.author()) {
if let Ok(signature) = self.signer.sign(header.bare_hash()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) {
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst); self.proposed.store(true, AtomicOrdering::SeqCst);
@ -271,32 +300,32 @@ impl Engine for AuthorityRound {
} }
} }
/// Check if the signature belongs to the correct proposer. fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let header_step = header_step(header)?;
// 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 correct_proposer = self.step_proposer(header_step);
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
Ok(()) Ok(())
} else { }
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? /// Do the validator and gas limit validation.
} fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
} else { let step = header_step(header)?;
// Give one step slack if step is lagging, double vote is still not possible.
if self.is_future_step(step) {
trace!(target: "engine", "verify_block_unordered: block from the future"); trace!(target: "engine", "verify_block_unordered: block from the future");
self.validators.report_benign(header.author()); self.validators.report_benign(header.author());
Err(BlockError::InvalidSeal)? Err(BlockError::InvalidSeal)?
} else {
let proposer_signature = header_signature(header)?;
let correct_proposer = self.step_proposer(header.parent_hash(), step);
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
}
} }
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { // Do not calculate difficulty for genesis blocks.
if header.number() == 0 { if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
} }
let step = header_step(header)?;
// Check if parent is from a previous step. // Check if parent is from a previous step.
if step == header_step(parent)? { if step == header_step(parent)? {
trace!(target: "engine", "Multiple blocks proposed for step {}.", step); trace!(target: "engine", "Multiple blocks proposed for step {}.", step);
@ -384,7 +413,7 @@ mod tests {
let mut header: Header = Header::default(); let mut header: Header = Header::default();
header.set_seal(vec![encode(&H520::default()).to_vec()]); header.set_seal(vec![encode(&H520::default()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None); let verify_result = engine.verify_block_family(&header, &Default::default(), None);
assert!(verify_result.is_err()); assert!(verify_result.is_err());
} }
@ -422,10 +451,14 @@ mod tests {
#[test] #[test]
fn proposer_switching() { fn proposer_switching() {
let mut header: Header = Header::default();
let tap = AccountProvider::transient_provider(); let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(addr); header.set_author(addr);
let engine = Spec::new_test_round().engine; let engine = Spec::new_test_round().engine;
@ -434,17 +467,22 @@ mod tests {
// Two validators. // Two validators.
// Spec starts with step 2. // Spec starts with step 2.
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err()); assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
} }
#[test] #[test]
fn rejects_future_block() { fn rejects_future_block() {
let mut header: Header = Header::default();
let tap = AccountProvider::transient_provider(); let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(addr); header.set_author(addr);
let engine = Spec::new_test_round().engine; let engine = Spec::new_test_round().engine;
@ -453,8 +491,8 @@ mod tests {
// Two validators. // Two validators.
// Spec starts with step 2. // Spec starts with step 2.
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err()); assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
} }
} }

View File

@ -104,14 +104,14 @@ impl Engine for BasicAuthority {
} }
fn seals_internally(&self) -> Option<bool> { fn seals_internally(&self) -> Option<bool> {
Some(self.validators.contains(&self.signer.address())) Some(self.signer.address() != Address::default())
} }
/// Attempt to seal the block internally. /// Attempt to seal the block internally.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal { fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
let header = block.header(); let header = block.header();
let author = header.author(); let author = header.author();
if self.validators.contains(author) { if self.validators.contains(header.parent_hash(), author) {
// account should be pernamently unlocked, otherwise sealing will fail // account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = self.signer.sign(header.bare_hash()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) {
return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]); return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]);
@ -133,20 +133,20 @@ impl Engine for BasicAuthority {
Ok(()) Ok(())
} }
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
use rlp::{UntrustedRlp, View};
// check the signature is legit.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.validators.contains(&signer) {
return Err(BlockError::InvalidSeal)?;
}
Ok(()) Ok(())
} }
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// we should not calculate difficulty for genesis blocks use rlp::{UntrustedRlp, View};
// Check if the signature belongs to a validator, can depend on parent state.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.validators.contains(header.parent_hash(), &signer) {
return Err(BlockError::InvalidSeal)?;
}
// Do not calculate difficulty for genesis blocks.
if header.number() == 0 { if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
} }
@ -239,7 +239,7 @@ mod tests {
let mut header: Header = Header::default(); let mut header: Header = Header::default();
header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]); header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None); let verify_result = engine.verify_block_family(&header, &Default::default(), None);
assert!(verify_result.is_err()); assert!(verify_result.is_err());
} }

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap; use std::collections::BTreeMap;
use util::Address; use util::{Address, HashMap};
use builtin::Builtin; use builtin::Builtin;
use engines::{Engine, Seal}; use engines::{Engine, Seal};
use env_info::EnvInfo; use env_info::EnvInfo;
@ -26,14 +26,16 @@ use block::ExecutedBlock;
/// An engine which does not provide any consensus mechanism, just seals blocks internally. /// An engine which does not provide any consensus mechanism, just seals blocks internally.
pub struct InstantSeal { pub struct InstantSeal {
params: CommonParams, params: CommonParams,
registrar: Address,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
} }
impl InstantSeal { impl InstantSeal {
/// Returns new instance of InstantSeal with default VM Factory /// Returns new instance of InstantSeal with default VM Factory
pub fn new(params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Self { pub fn new(params: CommonParams, registrar: Address, builtins: BTreeMap<Address, Builtin>) -> Self {
InstantSeal { InstantSeal {
params: params, params: params,
registrar: registrar,
builtins: builtins, builtins: builtins,
} }
} }
@ -48,6 +50,10 @@ impl Engine for InstantSeal {
&self.params &self.params
} }
fn additional_params(&self) -> HashMap<String, String> {
hash_map!["registrar".to_owned() => self.registrar.hex()]
}
fn builtins(&self) -> &BTreeMap<Address, Builtin> { fn builtins(&self) -> &BTreeMap<Address, Builtin> {
&self.builtins &self.builtins
} }
@ -76,9 +82,9 @@ mod tests {
fn instant_can_seal() { fn instant_can_seal() {
let spec = Spec::new_instant(); let spec = Spec::new_instant();
let engine = &*spec.engine; let engine = &*spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_state_db(); let mut db_result = get_temp_state_db();
let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); 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 last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock(); let b = b.close_and_lock();

View File

@ -166,7 +166,9 @@ pub trait Engine : Sync + Send {
} }
/// The network ID that transactions should be signed with. /// The network ID that transactions should be signed with.
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> { None } fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> {
Some(self.params().chain_id)
}
/// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods /// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods
/// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer /// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer

View File

@ -78,6 +78,7 @@ pub struct Tendermint {
step_service: IoService<Step>, step_service: IoService<Step>,
client: RwLock<Option<Weak<EngineClient>>>, client: RwLock<Option<Weak<EngineClient>>>,
block_reward: U256, block_reward: U256,
registrar: Address,
/// Blockchain height. /// Blockchain height.
height: AtomicUsize, height: AtomicUsize,
/// Consensus view. /// Consensus view.
@ -94,6 +95,8 @@ pub struct Tendermint {
last_lock: AtomicUsize, last_lock: AtomicUsize,
/// Bare hash of the proposed block, used for seal submission. /// Bare hash of the proposed block, used for seal submission.
proposal: RwLock<Option<H256>>, proposal: RwLock<Option<H256>>,
/// Hash of the proposal parent block.
proposal_parent: RwLock<H256>,
/// Set used to determine the current validators. /// Set used to determine the current validators.
validators: Box<ValidatorSet + Send + Sync>, validators: Box<ValidatorSet + Send + Sync>,
} }
@ -109,14 +112,16 @@ impl Tendermint {
client: RwLock::new(None), client: RwLock::new(None),
step_service: IoService::<Step>::start()?, step_service: IoService::<Step>::start()?,
block_reward: our_params.block_reward, block_reward: our_params.block_reward,
registrar: our_params.registrar,
height: AtomicUsize::new(1), height: AtomicUsize::new(1),
view: AtomicUsize::new(0), view: AtomicUsize::new(0),
step: RwLock::new(Step::Propose), step: RwLock::new(Step::Propose),
votes: VoteCollector::default(), votes: Default::default(),
signer: Default::default(), signer: Default::default(),
lock_change: RwLock::new(None), lock_change: RwLock::new(None),
last_lock: AtomicUsize::new(0), last_lock: AtomicUsize::new(0),
proposal: RwLock::new(None), proposal: RwLock::new(None),
proposal_parent: Default::default(),
validators: new_validator_set(our_params.validators), validators: new_validator_set(our_params.validators),
}); });
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts)); let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
@ -230,7 +235,7 @@ impl Tendermint {
let height = self.height.load(AtomicOrdering::SeqCst); let height = self.height.load(AtomicOrdering::SeqCst);
if let Some(block_hash) = *self.proposal.read() { if let Some(block_hash) = *self.proposal.read() {
// Generate seal and remove old votes. // Generate seal and remove old votes.
if self.is_signer_proposer() { if self.is_signer_proposer(&*self.proposal_parent.read()) {
let proposal_step = VoteStep::new(height, view, Step::Propose); let proposal_step = VoteStep::new(height, view, Step::Propose);
let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit); let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit);
if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) { if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) {
@ -252,23 +257,23 @@ impl Tendermint {
} }
fn is_authority(&self, address: &Address) -> bool { fn is_authority(&self, address: &Address) -> bool {
self.validators.contains(address) self.validators.contains(&*self.proposal_parent.read(), address)
} }
fn is_above_threshold(&self, n: usize) -> bool { fn is_above_threshold(&self, n: usize) -> bool {
n > self.validators.count() * 2/3 n > self.validators.count(&*self.proposal_parent.read()) * 2/3
} }
/// Find the designated for the given view. /// Find the designated for the given view.
fn view_proposer(&self, height: Height, view: View) -> Address { fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address {
let proposer_nonce = height + view; let proposer_nonce = height + view;
trace!(target: "engine", "Proposer nonce: {}", proposer_nonce); trace!(target: "engine", "Proposer nonce: {}", proposer_nonce);
self.validators.get(proposer_nonce) self.validators.get(bh, proposer_nonce)
} }
/// Check if address is a proposer for given view. /// Check if address is a proposer for given view.
fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> { fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
let proposer = self.view_proposer(height, view); let proposer = self.view_proposer(bh, height, view);
if proposer == *address { if proposer == *address {
Ok(()) Ok(())
} else { } else {
@ -277,8 +282,8 @@ impl Tendermint {
} }
/// Check if current signer is the current proposer. /// Check if current signer is the current proposer.
fn is_signer_proposer(&self) -> bool { fn is_signer_proposer(&self, bh: &H256) -> bool {
let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
self.signer.is_address(&proposer) self.signer.is_address(&proposer)
} }
@ -369,14 +374,20 @@ impl Tendermint {
impl Engine for Tendermint { impl Engine for Tendermint {
fn name(&self) -> &str { "Tendermint" } fn name(&self) -> &str { "Tendermint" }
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
/// (consensus view, proposal signature, authority signatures) /// (consensus view, proposal signature, authority signatures)
fn seal_fields(&self) -> usize { 3 } fn seal_fields(&self) -> usize { 3 }
fn params(&self) -> &CommonParams { &self.params } fn params(&self) -> &CommonParams { &self.params }
fn additional_params(&self) -> HashMap<String, String> { hash_map!["registrar".to_owned() => self.registrar.hex()] }
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
fn maximum_uncle_count(&self) -> usize { 0 } fn maximum_uncle_count(&self) -> usize { 0 }
fn maximum_uncle_age(&self) -> usize { 0 } fn maximum_uncle_age(&self) -> usize { 0 }
/// Additional engine-specific information for the user/developer concerning `header`. /// Additional engine-specific information for the user/developer concerning `header`.
@ -411,7 +422,7 @@ impl Engine for Tendermint {
/// Should this node participate. /// Should this node participate.
fn seals_internally(&self) -> Option<bool> { fn seals_internally(&self) -> Option<bool> {
Some(self.is_authority(&self.signer.address())) Some(self.signer.address() != Address::default())
} }
/// Attempt to seal generate a proposal seal. /// Attempt to seal generate a proposal seal.
@ -419,7 +430,7 @@ impl Engine for Tendermint {
let header = block.header(); let header = block.header();
let author = header.author(); let author = header.author();
// Only proposer can generate seal if None was generated. // Only proposer can generate seal if None was generated.
if !self.is_signer_proposer() || self.proposal.read().is_some() { if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() {
return Seal::None; return Seal::None;
} }
@ -433,6 +444,7 @@ impl Engine for Tendermint {
self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author); self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author);
// Remember proposal for later seal submission. // Remember proposal for later seal submission.
*self.proposal.write() = bh; *self.proposal.write() = bh;
*self.proposal_parent.write() = header.parent_hash().clone();
Seal::Proposal(vec![ Seal::Proposal(vec![
::rlp::encode(&view).to_vec(), ::rlp::encode(&view).to_vec(),
::rlp::encode(&signature).to_vec(), ::rlp::encode(&signature).to_vec(),
@ -497,7 +509,12 @@ impl Engine for Tendermint {
} }
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
Ok(())
}
/// Verify validators and gas limit.
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let proposal = ConsensusMessage::new_proposal(header)?; let proposal = ConsensusMessage::new_proposal(header)?;
let proposer = proposal.verify()?; let proposer = proposal.verify()?;
if !self.is_authority(&proposer) { if !self.is_authority(&proposer) {
@ -514,7 +531,7 @@ impl Engine for Tendermint {
Some(a) => a, Some(a) => a,
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
}; };
if !self.validators.contains(&address) { if !self.validators.contains(header.parent_hash(), &address) {
Err(EngineError::NotAuthorized(address.to_owned()))? Err(EngineError::NotAuthorized(address.to_owned()))?
} }
@ -537,12 +554,9 @@ impl Engine for Tendermint {
found: signatures_len found: signatures_len
}))?; }))?;
} }
self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?; self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
} }
Ok(())
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
if header.number() == 0 { if header.number() == 0 {
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
} }
@ -587,6 +601,7 @@ impl Engine for Tendermint {
debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
if self.is_view(&proposal) { if self.is_view(&proposal) {
*self.proposal.write() = proposal.block_hash.clone(); *self.proposal.write() = proposal.block_hash.clone();
*self.proposal_parent.write() = header.parent_hash().clone();
} }
self.votes.vote(proposal, &proposer); self.votes.vote(proposal, &proposer);
true true
@ -599,7 +614,7 @@ impl Engine for Tendermint {
trace!(target: "engine", "Propose timeout."); trace!(target: "engine", "Propose timeout.");
if self.proposal.read().is_none() { if self.proposal.read().is_none() {
// Report the proposer if no proposal was received. // Report the proposer if no proposal was received.
let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
self.validators.report_benign(&current_proposer); self.validators.report_benign(&current_proposer);
} }
Step::Prevote Step::Prevote
@ -757,20 +772,25 @@ mod tests {
let (spec, tap) = setup(); let (spec, tap) = setup();
let engine = spec.engine; let engine = spec.engine;
let mut header = Header::default(); let mut parent_header: Header = Header::default();
let validator = insert_and_unlock(&tap, "0"); parent_header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Good proposer.
assert!(engine.verify_block_unordered(&header.clone(), None).is_ok());
let mut header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
let validator = insert_and_unlock(&tap, "1"); let validator = insert_and_unlock(&tap, "1");
header.set_author(validator); header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0); let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal); header.set_seal(seal);
// Good proposer.
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
let validator = insert_and_unlock(&tap, "0");
header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Bad proposer. // Bad proposer.
match engine.verify_block_unordered(&header, None) { match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotProposer(_))) => {}, Err(Error::Engine(EngineError::NotProposer(_))) => {},
_ => panic!(), _ => panic!(),
} }
@ -780,7 +800,7 @@ mod tests {
let seal = proposal_seal(&tap, &header, 0); let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal); header.set_seal(seal);
// Not authority. // Not authority.
match engine.verify_block_unordered(&header, None) { match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
_ => panic!(), _ => panic!(),
}; };
@ -792,19 +812,24 @@ mod tests {
let (spec, tap) = setup(); let (spec, tap) = setup();
let engine = spec.engine; let engine = spec.engine;
let mut parent_header: Header = Header::default();
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header = Header::default(); let mut header = Header::default();
header.set_number(2);
header.set_gas_limit(U256::from_str("222222").unwrap());
let proposer = insert_and_unlock(&tap, "1"); let proposer = insert_and_unlock(&tap, "1");
header.set_author(proposer); header.set_author(proposer);
let mut seal = proposal_seal(&tap, &header, 0); let mut seal = proposal_seal(&tap, &header, 0);
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash())); let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
header.set_seal(seal.clone()); header.set_seal(seal.clone());
// One good signature is not enough. // One good signature is not enough.
match engine.verify_block_unordered(&header, None) { match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {},
_ => panic!(), _ => panic!(),
} }
@ -815,7 +840,7 @@ mod tests {
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec();
header.set_seal(seal.clone()); header.set_seal(seal.clone());
assert!(engine.verify_block_unordered(&header, None).is_ok()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
let bad_voter = insert_and_unlock(&tap, "101"); let bad_voter = insert_and_unlock(&tap, "101");
let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap();
@ -824,7 +849,7 @@ mod tests {
header.set_seal(seal); header.set_seal(seal);
// One good and one bad signature. // One good and one bad signature.
match engine.verify_block_unordered(&header, None) { match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
_ => panic!(), _ => panic!(),
}; };

View File

@ -17,7 +17,7 @@
//! Tendermint specific parameters. //! Tendermint specific parameters.
use ethjson; use ethjson;
use util::{U256, Uint}; use util::{U256, Uint, Address, FixedHash};
use time::Duration; use time::Duration;
use super::super::transition::Timeouts; use super::super::transition::Timeouts;
use super::Step; use super::Step;
@ -33,6 +33,8 @@ pub struct TendermintParams {
pub timeouts: TendermintTimeouts, pub timeouts: TendermintTimeouts,
/// Block reward. /// Block reward.
pub block_reward: U256, pub block_reward: U256,
/// Namereg contract address.
pub registrar: Address,
} }
/// Base timeout of each step in ms. /// Base timeout of each step in ms.
@ -88,6 +90,7 @@ impl From<ethjson::spec::TendermintParams> for TendermintParams {
commit: p.timeout_commit.map_or(dt.commit, to_duration), commit: p.timeout_commit.map_or(dt.commit, to_duration),
}, },
block_reward: p.block_reward.map_or_else(U256::zero, Into::into), block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
registrar: p.registrar.map_or_else(Address::new, Into::into),
} }
} }
} }

View File

@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract;
/// The validator contract should have the following interface: /// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorContract { pub struct ValidatorContract {
validators: Arc<ValidatorSafeContract>, validators: ValidatorSafeContract,
provider: RwLock<Option<provider::Contract>>, provider: RwLock<Option<provider::Contract>>,
} }
impl ValidatorContract { impl ValidatorContract {
pub fn new(contract_address: Address) -> Self { pub fn new(contract_address: Address) -> Self {
ValidatorContract { ValidatorContract {
validators: Arc::new(ValidatorSafeContract::new(contract_address)), validators: ValidatorSafeContract::new(contract_address),
provider: RwLock::new(None), provider: RwLock::new(None),
} }
} }
} }
impl ValidatorSet for Arc<ValidatorContract> { impl ValidatorSet for ValidatorContract {
fn contains(&self, address: &Address) -> bool { fn contains(&self, bh: &H256, address: &Address) -> bool {
self.validators.contains(address) self.validators.contains(bh, address)
} }
fn get(&self, nonce: usize) -> Address { fn get(&self, bh: &H256, nonce: usize) -> Address {
self.validators.get(nonce) self.validators.get(bh, nonce)
} }
fn count(&self) -> usize { fn count(&self, bh: &H256) -> usize {
self.validators.count() self.validators.count(bh)
} }
fn report_malicious(&self, address: &Address) { fn report_malicious(&self, address: &Address) {
@ -144,6 +144,7 @@ mod tests {
use header::Header; use header::Header;
use account_provider::AccountProvider; use account_provider::AccountProvider;
use miner::MinerService; use miner::MinerService;
use types::ids::BlockId;
use client::BlockChainClient; use client::BlockChainClient;
use tests::helpers::generate_dummy_client_with_spec_and_accounts; use tests::helpers::generate_dummy_client_with_spec_and_accounts;
use super::super::ValidatorSet; use super::super::ValidatorSet;
@ -154,8 +155,9 @@ mod tests {
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None);
let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_contract(Arc::downgrade(&client)); vc.register_contract(Arc::downgrade(&client));
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); let last_hash = client.best_block_header().hash();
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
} }
#[test] #[test]
@ -171,18 +173,21 @@ mod tests {
client.miner().set_engine_signer(v1, "".into()).unwrap(); client.miner().set_engine_signer(v1, "".into()).unwrap();
let mut header = Header::default(); let mut header = Header::default();
let seal = encode(&vec!(5u8)).to_vec(); let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()];
header.set_seal(vec!(seal)); header.set_seal(seal);
header.set_author(v1); header.set_author(v1);
header.set_number(1); header.set_number(2);
header.set_parent_hash(client.chain_info().best_block_hash);
// `reportBenign` when the designated proposer releases block from the future (bad clock). // `reportBenign` when the designated proposer releases block from the future (bad clock).
assert!(client.engine().verify_block_unordered(&header, None).is_err()); assert!(client.engine().verify_block_family(&header, &header, None).is_err());
// Seal a block. // Seal a block.
client.engine().step(); client.engine().step();
assert_eq!(client.chain_info().best_block_number, 1); assert_eq!(client.chain_info().best_block_number, 1);
// Check if the unresponsive validator is `disliked`. // Check if the unresponsive validator is `disliked`.
assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e");
// Simulate a misbehaving validator by handling a double proposal. // Simulate a misbehaving validator by handling a double proposal.
let header = client.best_block_header().decode();
assert!(client.engine().verify_block_family(&header, &header, None).is_err()); assert!(client.engine().verify_block_family(&header, &header, None).is_err());
// Seal a block. // Seal a block.
client.engine().step(); client.engine().step();

View File

@ -21,7 +21,7 @@ mod safe_contract;
mod contract; mod contract;
use std::sync::Weak; use std::sync::Weak;
use util::{Address, Arc}; use util::{Address, H256};
use ethjson::spec::ValidatorSet as ValidatorSpec; use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client; use client::Client;
use self::simple_list::SimpleList; use self::simple_list::SimpleList;
@ -32,18 +32,18 @@ use self::safe_contract::ValidatorSafeContract;
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> { pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
match spec { match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))), ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())),
} }
} }
pub trait ValidatorSet { pub trait ValidatorSet {
/// Checks if a given address is a validator. /// Checks if a given address is a validator.
fn contains(&self, address: &Address) -> bool; fn contains(&self, bh: &H256, address: &Address) -> bool;
/// Draws an validator nonce modulo number of validators. /// Draws an validator nonce modulo number of validators.
fn get(&self, nonce: usize) -> Address; fn get(&self, bh: &H256, nonce: usize) -> Address;
/// Returns the current number of validators. /// Returns the current number of validators.
fn count(&self) -> usize; fn count(&self, bh: &H256) -> usize;
/// Notifies about malicious behaviour. /// Notifies about malicious behaviour.
fn report_malicious(&self, _validator: &Address) {} fn report_malicious(&self, _validator: &Address) {}
/// Notifies about benign misbehaviour. /// Notifies about benign misbehaviour.

View File

@ -17,17 +17,23 @@
/// Validator set maintained in a contract, updated using `getValidators` method. /// Validator set maintained in a contract, updated using `getValidators` method.
use std::sync::Weak; use std::sync::Weak;
use ethabi;
use util::*; use util::*;
use util::cache::MemoryLruCache;
use types::ids::BlockId;
use client::{Client, BlockChainClient}; use client::{Client, BlockChainClient};
use client::chain_notify::ChainNotify;
use super::ValidatorSet; use super::ValidatorSet;
use super::simple_list::SimpleList; use super::simple_list::SimpleList;
const MEMOIZE_CAPACITY: usize = 500;
const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]";
const GET_VALIDATORS: &'static str = "getValidators";
/// The validator contract should have the following interface: /// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorSafeContract { pub struct ValidatorSafeContract {
pub address: Address, pub address: Address,
validators: RwLock<SimpleList>, validators: RwLock<MemoryLruCache<H256, SimpleList>>,
provider: RwLock<Option<provider::Contract>>, provider: RwLock<Option<provider::Contract>>,
} }
@ -35,102 +41,127 @@ impl ValidatorSafeContract {
pub fn new(contract_address: Address) -> Self { pub fn new(contract_address: Address) -> Self {
ValidatorSafeContract { ValidatorSafeContract {
address: contract_address, address: contract_address,
validators: Default::default(), validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
provider: RwLock::new(None), provider: RwLock::new(None),
} }
} }
/// Queries the state and updates the set of validators. /// Queries the state and gets the set of validators.
pub fn update(&self) { fn get_list(&self, block_hash: H256) -> Option<SimpleList> {
if let Some(ref provider) = *self.provider.read() { if let Some(ref provider) = *self.provider.read() {
match provider.get_validators() { match provider.get_validators(BlockId::Hash(block_hash)) {
Ok(new) => { Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new); debug!(target: "engine", "Set of validators obtained: {:?}", new);
*self.validators.write() = SimpleList::new(new); Some(SimpleList::new(new))
},
Err(s) => {
debug!(target: "engine", "Set of validators could not be updated: {}", s);
None
}, },
Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s),
} }
} else { } else {
warn!(target: "engine", "Set of validators could not be updated: no provider contract.") warn!(target: "engine", "Set of validators could not be updated: no provider contract.");
None
} }
} }
} }
/// Checks validators on every block. impl ValidatorSet for ValidatorSafeContract {
impl ChainNotify for ValidatorSafeContract { fn contains(&self, block_hash: &H256, address: &Address) -> bool {
fn new_blocks( let mut guard = self.validators.write();
&self, let maybe_existing = guard
_: Vec<H256>, .get_mut(block_hash)
_: Vec<H256>, .map(|list| list.contains(block_hash, address));
enacted: Vec<H256>, maybe_existing
_: Vec<H256>, .unwrap_or_else(|| self
_: Vec<H256>, .get_list(block_hash.clone())
_: Vec<Bytes>, .map_or(false, |list| {
_duration: u64) { let contains = list.contains(block_hash, address);
if !enacted.is_empty() { guard.insert(block_hash.clone(), list);
self.update(); contains
} }))
}
}
impl ValidatorSet for Arc<ValidatorSafeContract> {
fn contains(&self, address: &Address) -> bool {
self.validators.read().contains(address)
} }
fn get(&self, nonce: usize) -> Address { fn get(&self, block_hash: &H256, nonce: usize) -> Address {
self.validators.read().get(nonce) let mut guard = self.validators.write();
let maybe_existing = guard
.get_mut(block_hash)
.map(|list| list.get(block_hash, nonce));
maybe_existing
.unwrap_or_else(|| self
.get_list(block_hash.clone())
.map_or_else(Default::default, |list| {
let address = list.get(block_hash, nonce);
guard.insert(block_hash.clone(), list);
address
}))
} }
fn count(&self) -> usize { fn count(&self, block_hash: &H256) -> usize {
self.validators.read().count() let mut guard = self.validators.write();
let maybe_existing = guard
.get_mut(block_hash)
.map(|list| list.count(block_hash));
maybe_existing
.unwrap_or_else(|| self
.get_list(block_hash.clone())
.map_or_else(usize::max_value, |list| {
let address = list.count(block_hash);
guard.insert(block_hash.clone(), list);
address
}))
} }
fn register_contract(&self, client: Weak<Client>) { fn register_contract(&self, client: Weak<Client>) {
if let Some(c) = client.upgrade() { trace!(target: "engine", "Setting up contract caller.");
c.add_notify(self.clone()); let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed"));
} let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed");
{ let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed");
*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)))); let contract_address = self.address.clone();
} let do_call = move |id| client
self.update(); .upgrade()
.ok_or("No client!".into())
.and_then(|c| c.call_contract(id, contract_address.clone(), data.clone()))
.map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed"));
*self.provider.write() = Some(provider::Contract::new(do_call));
} }
} }
mod provider { mod provider {
// Autogenerated from JSON contract definition using Rust contract convertor.
#![allow(unused_imports)]
use std::string::String; use std::string::String;
use std::result::Result; use std::result::Result;
use std::fmt;
use {util, ethabi}; use {util, ethabi};
use util::{FixedHash, Uint}; use types::ids::BlockId;
pub struct Contract { pub struct Contract {
contract: ethabi::Contract, do_call: Box<Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static>,
address: util::Address,
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
} }
impl Contract { impl Contract {
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static { pub fn new<F>(do_call: F) -> Self where F: Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static {
Contract { 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), do_call: Box::new(do_call),
} }
} }
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` /// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
#[allow(dead_code)] pub fn get_validators(&self, id: BlockId) -> Result<Vec<util::Address>, String> {
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> { Ok((self.do_call)(id)?
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?; .into_iter()
let data = call.encode_call( .rev()
vec![] .collect::<Vec<_>>()
).map_err(Self::as_string)?; .pop()
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; .expect("get_validators returns one argument; qed")
let mut result = output.into_iter().rev().collect::<Vec<_>>(); .to_array()
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() })) .and_then(|v| v
.into_iter()
.map(|a| a.to_address())
.collect::<Option<Vec<[u8; 20]>>>())
.expect("get_validators returns a list of addresses; qed")
.into_iter()
.map(util::Address::from)
.collect::<Vec<_>>()
)
} }
} }
} }
@ -138,13 +169,14 @@ mod provider {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use util::*; use util::*;
use types::ids::BlockId;
use spec::Spec; use spec::Spec;
use account_provider::AccountProvider; use account_provider::AccountProvider;
use transaction::{Transaction, Action}; use transaction::{Transaction, Action};
use client::{BlockChainClient, EngineClient}; use client::{BlockChainClient, EngineClient};
use ethkey::Secret; use ethkey::Secret;
use miner::MinerService; use miner::MinerService;
use tests::helpers::generate_dummy_client_with_spec_and_accounts; use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use super::super::ValidatorSet; use super::super::ValidatorSet;
use super::ValidatorSafeContract; use super::ValidatorSafeContract;
@ -153,12 +185,13 @@ mod tests {
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_contract(Arc::downgrade(&client)); vc.register_contract(Arc::downgrade(&client));
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); let last_hash = client.best_block_header().hash();
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
} }
#[test] #[test]
fn updates_validators() { fn knows_validators() {
let tap = Arc::new(AccountProvider::transient_provider()); let tap = Arc::new(AccountProvider::transient_provider());
let s0 = Secret::from_slice(&"1".sha3()).unwrap(); let s0 = Secret::from_slice(&"1".sha3()).unwrap();
let v0 = tap.insert_account(s0.clone(), "").unwrap(); let v0 = tap.insert_account(s0.clone(), "").unwrap();
@ -212,5 +245,14 @@ mod tests {
client.update_sealing(); client.update_sealing();
// Able to seal again. // Able to seal again.
assert_eq!(client.chain_info().best_block_number, 3); assert_eq!(client.chain_info().best_block_number, 3);
// Check syncing.
let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]);
sync_client.engine().register_client(Arc::downgrade(&sync_client));
for i in 1..4 {
sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap();
}
sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3);
} }
} }

View File

@ -16,7 +16,7 @@
/// Preconfigured validator list. /// Preconfigured validator list.
use util::Address; use util::{H256, Address, HeapSizeOf};
use super::ValidatorSet; use super::ValidatorSet;
#[derive(Debug, PartialEq, Eq, Default)] #[derive(Debug, PartialEq, Eq, Default)]
@ -34,16 +34,22 @@ impl SimpleList {
} }
} }
impl HeapSizeOf for SimpleList {
fn heap_size_of_children(&self) -> usize {
self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children()
}
}
impl ValidatorSet for SimpleList { impl ValidatorSet for SimpleList {
fn contains(&self, address: &Address) -> bool { fn contains(&self, _bh: &H256, address: &Address) -> bool {
self.validators.contains(address) self.validators.contains(address)
} }
fn get(&self, nonce: usize) -> Address { fn get(&self, _bh: &H256, 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() 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 { fn count(&self, _bh: &H256) -> usize {
self.validator_n self.validator_n
} }
} }
@ -60,9 +66,9 @@ mod tests {
let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
let list = SimpleList::new(vec![a1.clone(), a2.clone()]); let list = SimpleList::new(vec![a1.clone(), a2.clone()]);
assert!(list.contains(&a1)); assert!(list.contains(&Default::default(), &a1));
assert_eq!(list.get(0), a1); assert_eq!(list.get(&Default::default(), 0), a1);
assert_eq!(list.get(1), a2); assert_eq!(list.get(&Default::default(), 1), a2);
assert_eq!(list.get(2), a1); assert_eq!(list.get(&Default::default(), 2), a1);
} }
} }

View File

@ -30,46 +30,52 @@ pub use self::denominations::*;
use super::spec::*; use super::spec::*;
/// Most recent fork block that we support on Mainnet. /// Most recent fork block that we support on Mainnet.
pub const FORK_SUPPORTED_FRONTIER: u64 = 2675000; pub const FORK_SUPPORTED_FOUNDATION: u64 = 2675000;
/// Most recent fork block that we support on Ropsten. /// Most recent fork block that we support on Ropsten.
pub const FORK_SUPPORTED_ROPSTEN: u64 = 10; pub const FORK_SUPPORTED_ROPSTEN: u64 = 10;
/// Most recent fork block that we support on Kovan.
pub const FORK_SUPPORTED_KOVAN: u64 = 0;
fn load(b: &[u8]) -> Spec { fn load(b: &[u8]) -> Spec {
Spec::load(b).expect("chain spec is invalid") Spec::load(b).expect("chain spec is invalid")
} }
/// Create a new Olympic chain spec. /// Create a new Foundation Olympic chain spec.
pub fn new_olympic() -> Spec { load(include_bytes!("../../res/ethereum/olympic.json")) } pub fn new_olympic() -> Spec { load(include_bytes!("../../res/ethereum/olympic.json")) }
/// Create a new Frontier mainnet chain spec. /// Create a new Foundation Mainnet chain spec.
pub fn new_frontier() -> Spec { load(include_bytes!("../../res/ethereum/frontier.json")) } pub fn new_foundation() -> Spec { load(include_bytes!("../../res/ethereum/foundation.json")) }
/// Create a new Frontier mainnet chain spec without the DAO hardfork. /// Create a new Classic Mainnet chain spec without the DAO hardfork.
pub fn new_classic() -> Spec { load(include_bytes!("../../res/ethereum/classic.json")) } pub fn new_classic() -> Spec { load(include_bytes!("../../res/ethereum/classic.json")) }
/// Create a new Frontier mainnet chain spec without the DAO hardfork. /// Create a new Expanse mainnet chain spec.
pub fn new_expanse() -> Spec { load(include_bytes!("../../res/ethereum/expanse.json")) } pub fn new_expanse() -> Spec { load(include_bytes!("../../res/ethereum/expanse.json")) }
/// Create a new Frontier chain spec as though it never changes to Homestead. /// Create a new Kovan testnet chain spec.
pub fn new_kovan() -> Spec { load(include_bytes!("../../res/ethereum/kovan.json")) }
/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead.
pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/frontier_test.json")) } pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/frontier_test.json")) }
/// Create a new Homestead chain spec as though it never changed from Frontier. /// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier.
pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) } pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) }
/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. /// Create a new Foundation Homestead-EIP150-era chain spec as though it never changed from Homestead/Frontier.
pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) } pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) }
/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. /// Create a new Foundation Homestead-EIP161-era chain spec as though it never changed from Homestead/Frontier.
pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) } pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) }
/// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8. /// Create a new Foundation Frontier/Homestead/DAO chain spec with transition points at #5 and #8.
pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) } pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) }
/// Create a new Frontier main net chain spec without genesis accounts. /// Create a new Foundation Mainnet chain spec without genesis accounts.
pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) }
/// Create a new Ropsten chain spec. /// Create a new Foundation Ropsten chain spec.
pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) }
/// Create a new Morden chain spec. /// Create a new Morden chain spec.
@ -112,7 +118,7 @@ mod tests {
#[test] #[test]
fn frontier() { fn frontier() {
let frontier = new_frontier(); let frontier = new_foundation();
assert_eq!(frontier.state_root(), "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".into()); assert_eq!(frontier.state_root(), "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".into());
let genesis = frontier.genesis_block(); let genesis = frontier.genesis_block();

View File

@ -28,4 +28,4 @@ mod v10;
pub use self::v10::ToV10; pub use self::v10::ToV10;
mod v11; mod v11;
pub use self::v11::ToV11; pub use self::v11::TO_V11;

View File

@ -14,33 +14,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Adds a seventh column for node information. //! Adds a seventh column for node information.
use util::kvdb::Database; use util::migration::ChangeColumns;
use util::migration::{Batch, Config, Error, Migration, Progress};
use std::sync::Arc;
/// Copies over data for all existing columns. /// The migration from v10 to v11.
#[derive(Default)] pub const TO_V11: ChangeColumns = ChangeColumns {
pub struct ToV11(Progress); pre_columns: Some(6),
post_columns: Some(7),
version: 11,
impl Migration for ToV11 { };
fn pre_columns(&self) -> Option<u32> { Some(6) }
fn columns(&self) -> Option<u32> { Some(7) }
fn version(&self) -> u32 { 11 }
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
// just copy everything over.
let mut batch = Batch::new(config, col);
for (key, value) in source.iter(col) {
self.0.tick();
batch.insert(key.to_vec(), value.to_vec(), dest)?
}
batch.commit(dest)
}
}

View File

@ -32,7 +32,7 @@
//! use ethcore::miner::{Miner, MinerService}; //! use ethcore::miner::{Miner, MinerService};
//! //!
//! fn main() { //! fn main() {
//! let miner: Miner = Miner::with_spec(&ethereum::new_frontier()); //! let miner: Miner = Miner::with_spec(&ethereum::new_foundation());
//! // get status //! // get status
//! assert_eq!(miner.status().transactions_in_pending_queue, 0); //! assert_eq!(miner.status().transactions_in_pending_queue, 0);
//! //!

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use types::ids::BlockId;
use client::MiningBlockChainClient; use client::MiningBlockChainClient;
use transaction::SignedTransaction; use transaction::SignedTransaction;
use util::{U256, Uint, Mutex}; use util::{U256, Uint, Mutex};
@ -45,7 +46,7 @@ impl ServiceTransactionChecker {
debug_assert_eq!(tx.gas_price, U256::zero()); debug_assert_eq!(tx.gas_price, U256::zero());
if let Some(ref contract) = *self.contract.lock() { if let Some(ref contract) = *self.contract.lock() {
let do_call = |a, d| client.call_contract(a, d); let do_call = |a, d| client.call_contract(BlockId::Latest, a, d);
contract.certified(&do_call, &tx.sender()) contract.certified(&do_call, &tx.sender())
} else { } else {
Err("contract is not configured".to_owned()) Err("contract is not configured".to_owned())

View File

@ -160,7 +160,7 @@ impl Spec {
fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Engine> { fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Engine> {
match engine_spec { match engine_spec {
ethjson::spec::Engine::Null => Arc::new(NullEngine::new(params, builtins)), ethjson::spec::Engine::Null => Arc::new(NullEngine::new(params, builtins)),
ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), ethjson::spec::Engine::InstantSeal(instant) => Arc::new(InstantSeal::new(params, instant.params.registrar.map_or_else(Address::new, Into::into), builtins)),
ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)),
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)),
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."), ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."),

View File

@ -44,6 +44,8 @@ pub trait Generator {
fn generate(self) -> Result<KeyPair, Error>; fn generate(self) -> Result<KeyPair, Error>;
} }
pub mod math;
pub use self::brain::Brain; pub use self::brain::Brain;
pub use self::error::Error; pub use self::error::Error;
pub use self::keypair::{KeyPair, public_to_address}; pub use self::keypair::{KeyPair, public_to_address};

66
ethkey/src/math.rs Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use super::{SECP256K1, Public, Secret, Error};
use secp256k1::key;
use secp256k1::constants::{GENERATOR_X, GENERATOR_Y};
/// Inplace multiply public key by secret key (EC point * scalar)
pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> {
let key_secret = secret.to_secp256k1_secret()?;
let mut key_public = to_secp256k1_public(public)?;
key_public.mul_assign(&SECP256K1, &key_secret)?;
set_public(public, &key_public);
Ok(())
}
/// Inplace add one public key to another (EC point + EC point)
pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> {
let mut key_public = to_secp256k1_public(public)?;
let other_public = to_secp256k1_public(other)?;
key_public.add_assign(&SECP256K1, &other_public)?;
set_public(public, &key_public);
Ok(())
}
/// Return base point of secp256k1
pub fn generation_point() -> Public {
let mut public_sec_raw = [0u8; 65];
public_sec_raw[0] = 4;
public_sec_raw[1..33].copy_from_slice(&GENERATOR_X);
public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y);
let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw)
.expect("constructing using predefined constants; qed");
let mut public = Public::default();
set_public(&mut public, &public_key);
public
}
fn to_secp256k1_public(public: &Public) -> Result<key::PublicKey, Error> {
let public_data = {
let mut temp = [4u8; 65];
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
temp
};
Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?)
}
fn set_public(public: &mut Public, key_public: &key::PublicKey) {
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
public.copy_from_slice(&key_public_serialized[1..65]);
}

View File

@ -19,7 +19,7 @@ use std::ops::Deref;
use std::str::FromStr; use std::str::FromStr;
use secp256k1::key; use secp256k1::key;
use bigint::hash::H256; use bigint::hash::H256;
use {Error}; use {Error, SECP256K1};
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Secret { pub struct Secret {
@ -45,6 +45,68 @@ impl Secret {
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?; let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
Ok(secret.into()) Ok(secret.into())
} }
/// Inplace add one secret key to another (scalar + scalar)
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
let mut key_secret = self.to_secp256k1_secret()?;
let other_secret = other.to_secp256k1_secret()?;
key_secret.add_assign(&SECP256K1, &other_secret)?;
*self = key_secret.into();
Ok(())
}
/// Inplace subtract one secret key from another (scalar - scalar)
pub fn sub(&mut self, other: &Secret) -> Result<(), Error> {
let mut key_secret = self.to_secp256k1_secret()?;
let mut other_secret = other.to_secp256k1_secret()?;
other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
key_secret.add_assign(&SECP256K1, &other_secret)?;
*self = key_secret.into();
Ok(())
}
/// Inplace multiply one secret key to another (scalar * scalar)
pub fn mul(&mut self, other: &Secret) -> Result<(), Error> {
let mut key_secret = self.to_secp256k1_secret()?;
let other_secret = other.to_secp256k1_secret()?;
key_secret.mul_assign(&SECP256K1, &other_secret)?;
*self = key_secret.into();
Ok(())
}
/// Inplace inverse secret key (1 / scalar)
pub fn inv(&mut self) -> Result<(), Error> {
let mut key_secret = self.to_secp256k1_secret()?;
key_secret.inv_assign(&SECP256K1)?;
*self = key_secret.into();
Ok(())
}
/// Compute power of secret key inplace (secret ^ pow).
/// This function is not intended to be used with large powers.
pub fn pow(&mut self, pow: usize) -> Result<(), Error> {
match pow {
0 => *self = key::ONE_KEY.into(),
1 => (),
_ => {
let c = self.clone();
for _ in 1..pow {
self.mul(&c)?;
}
},
}
Ok(())
}
/// Create `secp256k1::key::SecretKey` based on this secret
pub fn to_secp256k1_secret(&self) -> Result<key::SecretKey, Error> {
Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?)
}
} }
impl FromStr for Secret { impl FromStr for Secret {

View File

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

View File

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

View File

@ -3,7 +3,7 @@ description = "Hardware wallet support."
homepage = "http://parity.io" homepage = "http://parity.io"
license = "GPL-3.0" license = "GPL-3.0"
name = "hardware-wallet" name = "hardware-wallet"
version = "1.6.0" version = "1.7.0"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
[package] [package]
description = "Parity IPFS-compatible API" description = "Parity IPFS-compatible API"
name = "parity-ipfs-api" name = "parity-ipfs-api"
version = "1.6.0" version = "1.7.0"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]

1
js/.gitignore vendored
View File

@ -8,3 +8,4 @@ docs
.happypack .happypack
.npmjs .npmjs
.eslintcache .eslintcache
yarn.lock

View File

@ -1,15 +1,16 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.3.105", "version": "1.7.3",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
"maintainers": [ "maintainers": [
"Jaco Greeff", "Jaco Greeff",
"Nicolas Gotchac", "Nicolas Gotchac"
],
"contributors": [
"Jannis Redmann" "Jannis Redmann"
], ],
"contributors": [],
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": { "repository": {
"type": "git", "type": "git",
@ -56,28 +57,28 @@
"prepush": "npm run lint:cached" "prepush": "npm run lint:cached"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "6.22.2", "babel-cli": "6.23.0",
"babel-core": "6.22.1", "babel-core": "6.23.1",
"babel-eslint": "7.1.1", "babel-eslint": "7.1.1",
"babel-loader": "6.2.10", "babel-loader": "6.3.2",
"babel-plugin-lodash": "3.2.11", "babel-plugin-lodash": "3.2.11",
"babel-plugin-react-intl": "2.3.1", "babel-plugin-react-intl": "2.3.1",
"babel-plugin-recharts": "1.1.0", "babel-plugin-recharts": "1.1.0",
"babel-plugin-transform-class-properties": "6.22.0", "babel-plugin-transform-class-properties": "6.23.0",
"babel-plugin-transform-decorators-legacy": "1.3.4", "babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-object-rest-spread": "6.22.0", "babel-plugin-transform-object-rest-spread": "6.23.0",
"babel-plugin-transform-react-remove-prop-types": "0.3.0", "babel-plugin-transform-react-remove-prop-types": "0.3.2",
"babel-plugin-transform-runtime": "6.22.0", "babel-plugin-transform-runtime": "6.23.0",
"babel-plugin-webpack-alias": "2.1.2", "babel-plugin-webpack-alias": "2.1.2",
"babel-polyfill": "6.22.0", "babel-polyfill": "6.23.0",
"babel-preset-env": "1.1.8", "babel-preset-env": "1.1.9",
"babel-preset-es2015": "6.22.0", "babel-preset-es2015": "6.22.0",
"babel-preset-es2016": "6.22.0", "babel-preset-es2016": "6.22.0",
"babel-preset-es2017": "6.22.0", "babel-preset-es2017": "6.22.0",
"babel-preset-react": "6.22.0", "babel-preset-react": "6.23.0",
"babel-preset-stage-0": "6.22.0", "babel-preset-stage-0": "6.22.0",
"babel-register": "6.22.0", "babel-register": "6.23.0",
"babel-runtime": "6.22.0", "babel-runtime": "6.23.0",
"chai": "3.5.0", "chai": "3.5.0",
"chai-as-promised": "6.0.0", "chai-as-promised": "6.0.0",
"chai-enzyme": "0.6.1", "chai-enzyme": "0.6.1",
@ -85,62 +86,62 @@
"circular-dependency-plugin": "2.0.0", "circular-dependency-plugin": "2.0.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"core-js": "2.4.1", "core-js": "2.4.1",
"coveralls": "2.11.15", "coveralls": "2.11.16",
"css-loader": "0.26.1", "css-loader": "0.26.1",
"ejs-loader": "0.3.0", "ejs-loader": "0.3.0",
"ejsify": "1.0.0", "ejsify": "1.0.0",
"enzyme": "2.7.0", "enzyme": "2.7.1",
"eslint": "3.11.1", "eslint": "3.16.1",
"eslint-config-semistandard": "7.0.0", "eslint-config-semistandard": "7.0.0",
"eslint-config-standard": "6.2.1", "eslint-config-standard": "6.2.1",
"eslint-config-standard-react": "4.2.0", "eslint-config-standard-react": "4.2.0",
"eslint-plugin-promise": "3.4.0", "eslint-plugin-promise": "3.4.2",
"eslint-plugin-react": "6.8.0", "eslint-plugin-react": "6.10.0",
"eslint-plugin-standard": "2.0.1", "eslint-plugin-standard": "2.0.1",
"express": "4.14.0", "express": "4.14.1",
"extract-loader": "0.1.0", "extract-loader": "0.1.0",
"extract-text-webpack-plugin": "2.0.0-beta.4", "extract-text-webpack-plugin": "2.0.0-beta.4",
"file-loader": "0.9.0", "file-loader": "0.10.0",
"happypack": "3.0.2", "happypack": "3.0.3",
"html-loader": "0.4.4", "html-loader": "0.4.4",
"html-webpack-plugin": "2.24.1", "html-webpack-plugin": "2.28.0",
"http-proxy-middleware": "0.17.3", "http-proxy-middleware": "0.17.3",
"husky": "0.11.9", "husky": "0.13.1",
"ignore-styles": "5.0.1", "ignore-styles": "5.0.1",
"image-webpack-loader": "3.1.0", "image-webpack-loader": "3.2.0",
"istanbul": "1.0.0-alpha.2", "istanbul": "1.0.0-alpha.2",
"jsdom": "9.9.1", "jsdom": "9.11.0",
"json-loader": "0.5.4", "json-loader": "0.5.4",
"mocha": "3.2.0", "mocha": "3.2.0",
"mock-local-storage": "1.0.2", "mock-local-storage": "1.0.2",
"mock-socket": "6.0.4", "mock-socket": "6.0.4",
"nock": "9.0.2", "nock": "9.0.7",
"postcss-import": "9.0.0", "postcss-import": "9.1.0",
"postcss-loader": "1.2.1", "postcss-loader": "1.3.2",
"postcss-nested": "1.0.0", "postcss-nested": "1.0.0",
"postcss-simple-vars": "3.0.0", "postcss-simple-vars": "3.0.0",
"progress": "1.1.8", "progress": "1.1.8",
"progress-bar-webpack-plugin": "1.9.1", "progress-bar-webpack-plugin": "1.9.3",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"react-addons-perf": "15.4.1", "react-addons-perf": "15.4.2",
"react-addons-test-utils": "15.4.1", "react-addons-test-utils": "15.4.2",
"react-hot-loader": "3.0.0-beta.6", "react-hot-loader": "3.0.0-beta.6",
"react-intl-aggregate-webpack-plugin": "0.0.1", "react-intl-aggregate-webpack-plugin": "0.0.1",
"rucksack-css": "0.9.1", "rucksack-css": "0.9.1",
"script-ext-html-webpack-plugin": "1.3.5", "script-ext-html-webpack-plugin": "1.7.1",
"serviceworker-webpack-plugin": "0.1.7", "serviceworker-webpack-plugin": "0.2.0",
"sinon": "1.17.6", "sinon": "1.17.7",
"sinon-as-promised": "4.0.2", "sinon-as-promised": "4.0.2",
"sinon-chai": "2.8.0", "sinon-chai": "2.8.0",
"style-loader": "0.13.1", "style-loader": "0.13.1",
"stylelint": "7.7.0", "stylelint": "7.9.0",
"stylelint-config-standard": "15.0.1", "stylelint-config-standard": "16.0.0",
"to-source": "2.0.3", "to-source": "2.0.3",
"url-loader": "0.5.7", "url-loader": "0.5.7",
"webpack": "2.2.1", "webpack": "2.2.1",
"webpack-dev-middleware": "1.9.0", "webpack-dev-middleware": "1.10.1",
"webpack-error-notification": "0.1.6", "webpack-error-notification": "0.1.6",
"webpack-hot-middleware": "2.14.0", "webpack-hot-middleware": "2.17.1",
"websocket": "1.0.24", "websocket": "1.0.24",
"yargs": "6.6.0" "yargs": "6.6.0"
}, },
@ -153,7 +154,7 @@
"debounce": "1.0.0", "debounce": "1.0.0",
"es6-error": "4.0.0", "es6-error": "4.0.0",
"es6-promise": "4.0.5", "es6-promise": "4.0.5",
"ethereumjs-tx": "1.1.4", "ethereumjs-tx": "1.2.5",
"eventemitter3": "2.0.2", "eventemitter3": "2.0.2",
"file-saver": "1.3.3", "file-saver": "1.3.3",
"flat": "2.0.1", "flat": "2.0.1",
@ -200,6 +201,9 @@
"scryptsy": "2.0.0", "scryptsy": "2.0.0",
"solc": "ngotchac/solc-js", "solc": "ngotchac/solc-js",
"store": "1.3.20", "store": "1.3.20",
"u2f-api": "0.0.9",
"u2f-api-polyfill": "0.4.3",
"uglify-js": "2.8.2",
"useragent.js": "0.5.6", "useragent.js": "0.5.6",
"utf8": "2.1.2", "utf8": "2.1.2",
"valid-url": "1.0.9", "valid-url": "1.0.9",

View File

@ -21,15 +21,15 @@ const PAGE_SIZE = 25;
import util from '../../api/util'; import util from '../../api/util';
import { call } from './call'; import { call } from './call';
function _call (method, params, test) { function _call (method, params, test, netVersion) {
return call('account', method, params, test); return call('account', method, params, test, netVersion);
} }
function balance (address, test = false) { function balance (address, test, netVersion) {
return _call('balance', { return _call('balance', {
address: address, address: address,
tag: 'latest' tag: 'latest'
}, test).then((balance) => { }, test, netVersion).then((balance) => {
// same format as balancemulti below // same format as balancemulti below
return { return {
account: address, account: address,
@ -38,21 +38,21 @@ function balance (address, test = false) {
}); });
} }
function balances (addresses, test = false) { function balances (addresses, test, netVersion) {
return _call('balancemulti', { return _call('balancemulti', {
address: addresses.join(','), address: addresses.join(','),
tag: 'latest' tag: 'latest'
}, test); }, test, netVersion);
} }
function transactions (address, page, test = false) { function transactions (address, page, test, netVersion) {
// page offset from 0 // page offset from 0
return _call('txlist', { return _call('txlist', {
address: address, address: address,
offset: PAGE_SIZE, offset: PAGE_SIZE,
page: (page || 0) + 1, page: (page || 0) + 1,
sort: 'desc' sort: 'desc'
}, test).then((transactions) => { }, test, netVersion).then((transactions) => {
return transactions.map((tx) => { return transactions.map((tx) => {
return { return {
blockNumber: new BigNumber(tx.blockNumber || 0), blockNumber: new BigNumber(tx.blockNumber || 0),
@ -67,9 +67,9 @@ function transactions (address, page, test = false) {
} }
const account = { const account = {
balance: balance, balance,
balances: balances, balances,
transactions: transactions transactions
}; };
export { account }; export { account };

View File

@ -23,14 +23,32 @@ const options = {
} }
}; };
export function call (module, action, _params, test) { export function call (module, action, _params, test, netVersion) {
const host = test ? 'testnet.etherscan.io' : 'api.etherscan.io'; let prefix = 'api.';
switch (netVersion) {
case '2':
case '3':
prefix = 'testnet.';
break;
case '42':
prefix = 'kovan.';
break;
case '0':
default:
if (test) {
prefix = 'testnet.';
}
break;
}
const query = stringify(Object.assign({ const query = stringify(Object.assign({
module, action module, action
}, _params || {})); }, _params || {}));
return fetch(`https://${host}/api?${query}`, options) return fetch(`https://${prefix}etherscan.io/api?${query}`, options)
.then((response) => { .then((response) => {
if (!response.ok) { if (!response.ok) {
throw { code: response.status, message: response.statusText }; // eslint-disable-line throw { code: response.status, message: response.statusText }; // eslint-disable-line

View File

@ -19,8 +19,8 @@ import { stringify } from 'qs';
import { url } from './links'; import { url } from './links';
function mockget (requests, test) { function mockget (requests, test, netVersion) {
let scope = nock(url(test)); let scope = nock(url(test, netVersion));
requests.forEach((request) => { requests.forEach((request) => {
scope = scope scope = scope

View File

@ -14,14 +14,35 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export const url = (isTestnet = false) => { // NOTE: Keep 'isTestnet' for backwards library compatibility
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; export const url = (isTestnet = false, netVersion = '0') => {
let prefix = '';
switch (netVersion) {
case '2':
case '3':
prefix = 'testnet.';
break;
case '42':
prefix = 'kovan.';
break;
case '0':
default:
if (isTestnet) {
prefix = 'testnet.';
}
break;
}
return `https://${prefix}etherscan.io`;
}; };
export const txLink = (hash, isTestnet = false) => { export const txLink = (hash, isTestnet = false, netVersion = '0') => {
return `${url(isTestnet)}/tx/${hash}`; return `${url(isTestnet, netVersion)}/tx/${hash}`;
}; };
export const addressLink = (address, isTestnet = false) => { export const addressLink = (address, isTestnet = false, netVersion = '0') => {
return `${url(isTestnet)}/address/${address}`; return `${url(isTestnet, netVersion)}/address/${address}`;
}; };

View File

@ -14,12 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Ledger3 from './vendor/ledger3'; export default from './ledger';
import LedgerEth from './vendor/ledger-eth';
export function create () {
const ledger = new Ledger3('w0w');
const app = new LedgerEth(ledger);
return app;
}

136
js/src/3rdparty/ledger/ledger.js vendored Normal file
View File

@ -0,0 +1,136 @@
// Copyright 2015-2017 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 'u2f-api-polyfill';
import BigNumber from 'bignumber.js';
import Transaction from 'ethereumjs-tx';
import u2fapi from 'u2f-api';
import Ledger3 from './vendor/ledger3';
import LedgerEth from './vendor/ledger-eth';
const LEDGER_PATH_ETC = "44/60/160720'/0'/0";
const LEDGER_PATH_ETH = "44'/60'/0'/0";
const SCRAMBLE_KEY = 'w0w';
function numberToHex (number) {
return `0x${new BigNumber(number).toString(16)}`;
}
export default class Ledger {
constructor (api, ledger) {
this._api = api;
this._ledger = ledger;
this._isSupported = false;
this.checkJSSupport();
}
// FIXME: Until we have https support from Parity u2f will not work. Here we mark it completely
// as unsupported until a full end-to-end environment is available.
get isSupported () {
return false && this._isSupported;
}
checkJSSupport () {
return u2fapi
.isSupported()
.then((isSupported) => {
console.log('Ledger:checkJSSupport', isSupported);
this._isSupported = isSupported;
});
}
getAppConfiguration () {
return new Promise((resolve, reject) => {
this._ledger.getAppConfiguration((response, error) => {
if (error) {
reject(error);
return;
}
resolve(response);
});
});
}
scan () {
return new Promise((resolve, reject) => {
this._ledger.getAddress(LEDGER_PATH_ETH, (response, error) => {
if (error) {
reject(error);
return;
}
resolve([response.address]);
}, true, false);
});
}
signTransaction (transaction) {
return this._api.net.version().then((_chainId) => {
return new Promise((resolve, reject) => {
const chainId = new BigNumber(_chainId).toNumber();
const tx = new Transaction({
data: transaction.data || transaction.input,
gasPrice: numberToHex(transaction.gasPrice),
gasLimit: numberToHex(transaction.gasLimit),
nonce: numberToHex(transaction.nonce),
to: transaction.to ? transaction.to.toLowerCase() : undefined,
value: numberToHex(transaction.value),
v: Buffer.from([chainId]), // pass the chainId to the ledger
r: Buffer.from([]),
s: Buffer.from([])
});
const rawTransaction = tx.serialize().toString('hex');
this._ledger.signTransaction(LEDGER_PATH_ETH, rawTransaction, (response, error) => {
if (error) {
reject(error);
return;
}
tx.v = Buffer.from(response.v, 'hex');
tx.r = Buffer.from(response.r, 'hex');
tx.s = Buffer.from(response.s, 'hex');
if (chainId !== Math.floor((tx.v[0] - 35) / 2)) {
reject(new Error('Invalid EIP155 signature received from Ledger.'));
return;
}
resolve(`0x${tx.serialize().toString('hex')}`);
});
});
});
}
static create (api, ledger) {
if (!ledger) {
ledger = new LedgerEth(new Ledger3(SCRAMBLE_KEY));
}
return new Ledger(api, ledger);
}
}
export {
LEDGER_PATH_ETC,
LEDGER_PATH_ETH
};

120
js/src/3rdparty/ledger/ledger.spec.js vendored Normal file
View File

@ -0,0 +1,120 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import sinon from 'sinon';
import Ledger from './';
const TEST_ADDRESS = '0x63Cf90D3f0410092FC0fca41846f596223979195';
let api;
let ledger;
let vendor;
function createApi () {
api = {
net: {
version: sinon.stub().resolves('2')
}
};
return api;
}
function createVendor (error = null) {
vendor = {
getAddress: (path, callback) => {
callback({
address: TEST_ADDRESS
}, error);
},
getAppConfiguration: (callback) => {
callback({}, error);
},
signTransaction: (path, rawTransaction, callback) => {
callback({
v: [39],
r: [0],
s: [0]
}, error);
}
};
return vendor;
}
function create (error) {
ledger = new Ledger(createApi(), createVendor(error));
return ledger;
}
describe('3rdparty/ledger', () => {
beforeEach(() => {
create();
sinon.spy(vendor, 'getAddress');
sinon.spy(vendor, 'getAppConfiguration');
sinon.spy(vendor, 'signTransaction');
});
afterEach(() => {
vendor.getAddress.restore();
vendor.getAppConfiguration.restore();
vendor.signTransaction.restore();
});
describe('getAppConfiguration', () => {
beforeEach(() => {
return ledger.getAppConfiguration();
});
it('calls into getAppConfiguration', () => {
expect(vendor.getAppConfiguration).to.have.been.called;
});
});
describe('scan', () => {
beforeEach(() => {
return ledger.scan();
});
it('calls into getAddress', () => {
expect(vendor.getAddress).to.have.been.called;
});
});
describe('signTransaction', () => {
beforeEach(() => {
return ledger.signTransaction({
data: '0x0',
gasPrice: 20000000,
gasLimit: 1000000,
nonce: 2,
to: '0x63Cf90D3f0410092FC0fca41846f596223979195',
value: 1
});
});
it('retrieves chainId via API', () => {
expect(api.net.version).to.have.been.called;
});
it('calls into signTransaction', () => {
expect(vendor.signTransaction).to.have.been.called;
});
});
});

View File

@ -21,8 +21,9 @@ export function eventSignature (eventName, params) {
const { strName, name } = parseName(eventName); const { strName, name } = parseName(eventName);
const types = (params || []).map(fromParamType).join(','); const types = (params || []).map(fromParamType).join(',');
const id = `${strName}(${types})`; const id = `${strName}(${types})`;
const signature = strName ? keccak_256(id) : '';
return { id, name, signature: keccak_256(id) }; return { id, name, signature };
} }
export function methodSignature (methodName, params) { export function methodSignature (methodName, params) {

View File

@ -46,7 +46,7 @@ describe('abi/util/signature', () => {
expect(eventSignature(undefined, [])).to.deep.equal({ expect(eventSignature(undefined, [])).to.deep.equal({
id: '()', id: '()',
name: undefined, name: undefined,
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' signature: ''
}); });
}); });
@ -54,7 +54,7 @@ describe('abi/util/signature', () => {
expect(eventSignature(undefined, undefined)).to.deep.equal({ expect(eventSignature(undefined, undefined)).to.deep.equal({
id: '()', id: '()',
name: undefined, name: undefined,
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' signature: ''
}); });
}); });
}); });
@ -96,7 +96,7 @@ describe('abi/util/signature', () => {
expect(methodSignature(undefined, [])).to.deep.equal({ expect(methodSignature(undefined, [])).to.deep.equal({
id: '()', id: '()',
name: undefined, name: undefined,
signature: '861731d5' signature: ''
}); });
}); });
@ -104,7 +104,7 @@ describe('abi/util/signature', () => {
expect(methodSignature(undefined, undefined)).to.deep.equal({ expect(methodSignature(undefined, undefined)).to.deep.equal({
id: '()', id: '()',
name: undefined, name: undefined,
signature: '861731d5' signature: ''
}); });
}); });
}); });

View File

@ -107,34 +107,26 @@ export default class Contract {
}); });
} }
deploy (options, values, statecb) { deploy (options, values, statecb = () => {}) {
const setState = (state) => { statecb(null, { state: 'estimateGas' });
if (!statecb) {
return;
}
return statecb(null, state);
};
setState({ state: 'estimateGas' });
return this return this
.deployEstimateGas(options, values) .deployEstimateGas(options, values)
.then(([gasEst, gas]) => { .then(([gasEst, gas]) => {
options.gas = gas.toFixed(0); options.gas = gas.toFixed(0);
setState({ state: 'postTransaction', gas }); statecb(null, { state: 'postTransaction', gas });
const _options = this._encodeOptions(this.constructors[0], options, values); const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
return this._api.parity return this._api.parity
.postTransaction(_options) .postTransaction(encodedOptions)
.then((requestId) => { .then((requestId) => {
setState({ state: 'checkRequest', requestId }); statecb(null, { state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId); return this._pollCheckRequest(requestId);
}) })
.then((txhash) => { .then((txhash) => {
setState({ state: 'getTransactionReceipt', txhash }); statecb(null, { state: 'getTransactionReceipt', txhash });
return this._pollTransactionReceipt(txhash, gas); return this._pollTransactionReceipt(txhash, gas);
}) })
.then((receipt) => { .then((receipt) => {
@ -142,23 +134,23 @@ export default class Contract {
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
} }
setState({ state: 'hasReceipt', receipt }); statecb(null, { state: 'hasReceipt', receipt });
this._receipt = receipt; this._receipt = receipt;
this._address = receipt.contractAddress; this._address = receipt.contractAddress;
return this._address; return this._address;
}); })
}) .then((address) => {
.then((address) => { statecb(null, { state: 'getCode' });
setState({ state: 'getCode' }); return this._api.eth.getCode(this._address);
return this._api.eth.getCode(this._address); })
}) .then((code) => {
.then((code) => { if (code === '0x') {
if (code === '0x') { throw new Error('Contract not deployed, getCode returned 0x');
throw new Error('Contract not deployed, getCode returned 0x'); }
}
setState({ state: 'completed' }); statecb(null, { state: 'completed' });
return this._address; return this._address;
});
}); });
} }

View File

@ -128,6 +128,18 @@ export function outLog (log) {
return log; return log;
} }
export function outHwAccountInfo (infos) {
return Object
.keys(infos)
.reduce((ret, _address) => {
const address = outAddress(_address);
ret[address] = infos[_address];
return ret;
}, {});
}
export function outNumber (number) { export function outNumber (number) {
return new BigNumber(number || 0); return new BigNumber(number || 0);
} }

View File

@ -16,7 +16,7 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outHwAccountInfo, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output';
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';
describe('api/format/output', () => { describe('api/format/output', () => {
@ -163,6 +163,16 @@ describe('api/format/output', () => {
}); });
}); });
describe('outHwAccountInfo', () => {
it('returns objects with formatted addresses', () => {
expect(outHwAccountInfo(
{ '0x63cf90d3f0410092fc0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } }
)).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' }
});
});
});
describe('outNumber', () => { describe('outNumber', () => {
it('returns a BigNumber equalling the value', () => { it('returns a BigNumber equalling the value', () => {
const bn = outNumber('0x123456'); const bn = outNumber('0x123456');

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';
export default class Parity { export default class Parity {
constructor (transport) { constructor (transport) {
@ -200,6 +200,12 @@ export default class Parity {
.then(outVaultMeta); .then(outVaultMeta);
} }
hardwareAccountsInfo () {
return this._transport
.execute('parity_hardwareAccountsInfo')
.then(outHwAccountInfo);
}
hashContent (url) { hashContent (url) {
return this._transport return this._transport
.execute('parity_hashContent', url); .execute('parity_hashContent', url);

View File

@ -34,7 +34,5 @@ export function abiEncode (methodName, inputTypes, data) {
}) })
}, data); }, data);
return methodName === null return result;
? `0x${result.substr(10)}`
: result;
} }

View File

@ -14,34 +14,18 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import badgereg from './badgereg.json'; export badgereg from './badgereg.json';
import basiccoin from './basiccoin.json'; export basiccoin from './basiccoin.json';
import basiccoinmanager from './basiccoinmanager.json'; export basiccoinmanager from './basiccoinmanager.json';
import dappreg from './dappreg.json'; export dappreg from './dappreg.json';
import eip20 from './eip20.json'; export eip20 from './eip20.json';
import emailverification from './email-verification.json'; export emailverification from './email-verification.json';
import gavcoin from './gavcoin.json'; export gavcoin from './gavcoin.json';
import githubhint from './githubhint.json'; export githubhint from './githubhint.json';
import owned from './owned.json'; export owned from './owned.json';
import registry from './registry.json'; export registry from './registry.json';
import signaturereg from './signaturereg.json'; export registry2 from './registry2.json';
import smsverification from './sms-verification.json'; export signaturereg from './signaturereg.json';
import tokenreg from './tokenreg.json'; export smsverification from './sms-verification.json';
import wallet from './wallet.json'; export tokenreg from './tokenreg.json';
export wallet from './wallet.json';
export {
badgereg,
basiccoin,
basiccoinmanager,
dappreg,
eip20,
emailverification,
gavcoin,
githubhint,
owned,
registry,
signaturereg,
smsverification,
tokenreg,
wallet
};

View File

@ -0,0 +1,466 @@
[
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

File diff suppressed because one or more lines are too long

View File

@ -1 +1,476 @@
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] [
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

File diff suppressed because one or more lines are too long

View File

@ -8,453 +8,454 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed. // interior is executed.
pragma solidity ^0.4.6;
contract multisig { pragma solidity ^0.4.9;
// EVENTS
// this contract can accept a confirmation, in which case contract WalletEvents {
// we record owner and operation (hash) alongside it. // EVENTS
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// some others are in the case of an owner changing. // this contract only has six types of events: it can accept a confirmation, in which case
event OwnerChanged(address oldOwner, address newOwner); // we record owner and operation (hash) alongside it.
event OwnerAdded(address newOwner); event Confirmation(address owner, bytes32 operation);
event OwnerRemoved(address oldOwner); event Revoke(address owner, bytes32 operation);
// the last one is emitted if the required signatures change // some others are in the case of an owner changing.
event RequirementChanged(uint newRequirement); event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// Funds has arrived into the wallet (record how much). // the last one is emitted if the required signatures change
event Deposit(address _from, uint value); event RequirementChanged(uint newRequirement);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data); // Funds has arrived into the wallet (record how much).
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). event Deposit(address _from, uint value);
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
// Confirmation still needed for a transaction. event SingleTransact(address owner, uint value, address to, bytes data, address created);
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
} }
contract multisigAbi is multisig { contract WalletAbi {
function isOwner(address _addr) returns (bool); // Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external;
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); // Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) external;
function confirm(bytes32 _h) returns(bool); function addOwner(address _owner) external;
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. function removeOwner(address _owner) external;
function setDailyLimit(uint _newLimit);
function addOwner(address _owner); function changeRequirement(uint _newRequired) external;
function removeOwner(address _owner); function isOwner(address _addr) constant returns (bool);
function changeRequirement(uint _newRequired); function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
// Revokes a prior confirmation of the given operation // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function revoke(bytes32 _operation); function setDailyLimit(uint _newLimit) external;
function changeOwner(address _from, address _to); function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) returns (bool o_success);
function execute(address _to, uint _value, bytes _data) returns(bool);
} }
contract WalletLibrary is multisig { contract WalletLibrary is WalletEvents {
// TYPES // TYPES
// struct for the status of a pending operation. // struct for the status of a pending operation.
struct PendingState { struct PendingState {
uint yetNeeded; uint yetNeeded;
uint ownersDone; uint ownersDone;
uint index; uint index;
}
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
}
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i) {
delete m_txs[m_pendingIndex[i]];
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
} }
// Transaction structure to remember details of transaction lest it need be saved for a later call. delete m_pendingIndex;
struct Transaction { }
address to;
uint value;
bytes data;
}
/****************************** // FIELDS
***** MULTI OWNED SECTION **** address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
******************************/
// MODIFIERS // the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
// simple single-sig function modifier. uint public m_dailyLimit;
modifier onlyowner { uint public m_spentToday;
if (isOwner(msg.sender)) uint public m_lastDay;
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS // list of owners
uint[256] m_owners;
// constructor is given number of sigs required to do protected "onlymanyowners" transactions uint constant c_maxOwners = 250;
// as well as the selection of addresses capable of confirming them. // index on the list of owners to allow reverse lookup
function initMultiowned(address[] _owners, uint _required) { mapping(uint => uint) m_ownerIndex;
m_numOwners = _owners.length + 1; // the ongoing operations.
m_owners[1] = uint(msg.sender); mapping(bytes32 => PendingState) m_pending;
m_ownerIndex[uint(msg.sender)] = 1; bytes32[] m_pendingIndex;
m_required = _required;
for (uint i = 0; i < _owners.length; ++i) // pending transactions we have at present.
{ mapping (bytes32 => Transaction) m_txs;
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
function isOwner(address _addr) returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
/******************************
****** DAY LIMIT SECTION *****
******************************/
// MODIFIERS
// simple modifier for daily limit.
modifier limitedDaily(uint _value) {
if (underLimit(_value))
_;
}
// METHODS
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
m_spentToday = 0;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
/******************************
********* WALLET SECTION *****
******************************/
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initMultiowned(_owners, _required);
initDaylimit(_daylimit) ;
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
// first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_callValue =_to.call.value(_value)(_data);
} else {
// determine our operation hash.
bytes32 _r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
if (m_txs[_h].to != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function clearWalletPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
delete m_txs[m_pendingIndex[i]];
clearPending();
}
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run.
uint m_required;
// pointer used to find a free slot in m_owners
uint m_numOwners;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
} }
contract Wallet is WalletEvents {
contract Wallet is multisig { // WALLET CONSTRUCTOR
// calls the `initWallet` method of the Library in this context
function Wallet(address[] _owners, uint _required, uint _daylimit) {
// Signature of the Wallet Library's init function
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
address target = _walletLibrary;
// WALLET CONSTRUCTOR // Compute the size of the call data : arrays has 2
// calls the `initWallet` method of the Library in this context // 32bytes for offset and length, plus 32bytes per element ;
function Wallet(address[] _owners, uint _required, uint _daylimit) { // plus 2 32bytes for each uint
// Signature of the Wallet Library's init function uint argarraysize = (2 + _owners.length);
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); uint argsize = (2 + argarraysize) * 32;
address target = _walletLibrary;
// Compute the size of the call data : arrays has 2 assembly {
// 32bytes for offset and length, plus 32bytes per element ; // Add the signature first to memory
// plus 2 32bytes for each uint mstore(0x0, sig)
uint argarraysize = (2 + _owners.length); // Add the call data, which is at the end of the
uint argsize = (2 + argarraysize) * 32; // code
codecopy(0x4, sub(codesize, argsize), argsize)
assembly { // Delegate call to the library
// Add the signature first to memory delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
mstore(0x0, sig)
// Add the call data, which is at the end of the
// code
codecopy(0x4, sub(codesize, argsize), argsize)
// Delegate call to the library
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
}
} }
}
// METHODS // METHODS
// gets called when no other function matches // gets called when no other function matches
function() payable { function() payable {
// just being sent some cash? // just being sent some cash?
if (msg.value > 0) if (msg.value > 0)
Deposit(msg.sender, msg.value); Deposit(msg.sender, msg.value);
else if (msg.data.length > 0) else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data); _walletLibrary.delegatecall(msg.data);
} }
// Gets an owner by 0-indexed position (using numOwners as the count) // Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) constant returns (address) { function getOwner(uint ownerIndex) constant returns (address) {
return address(m_owners[ownerIndex + 1]); return address(m_owners[ownerIndex + 1]);
} }
// As return statement unavailable in fallback, explicit the method here // As return statement unavailable in fallback, explicit the method here
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
return _walletLibrary.delegatecall(msg.data); return _walletLibrary.delegatecall(msg.data);
} }
function isOwner(address _addr) returns (bool) { function isOwner(address _addr) constant returns (bool) {
return _walletLibrary.delegatecall(msg.data); return _walletLibrary.delegatecall(msg.data);
} }
// FIELDS // FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run. // the number of owners that must confirm the same operation before it is run.
uint public m_required; uint public m_required;
// pointer used to find a free slot in m_owners // pointer used to find a free slot in m_owners
uint public m_numOwners; uint public m_numOwners;
uint public m_dailyLimit; uint public m_dailyLimit;
uint public m_spentToday; uint public m_spentToday;
uint public m_lastDay; uint public m_lastDay;
// list of owners // list of owners
uint[256] m_owners; uint[256] m_owners;
} }

View File

@ -8,221 +8,222 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed. // interior is executed.
pragma solidity ^0.4.6;
pragma solidity ^0.4.9;
contract multiowned { contract multiowned {
// TYPES // TYPES
// struct for the status of a pending operation. // struct for the status of a pending operation.
struct PendingState { struct PendingState {
uint yetNeeded; uint yetNeeded;
uint ownersDone; uint ownersDone;
uint index; uint index;
}
// EVENTS
// this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
} }
m_required = _required;
}
// EVENTS // Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
// this contract only has six types of events: it can accept a confirmation, in which case uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// we record owner and operation (hash) alongside it. // make sure they're an owner
event Confirmation(address owner, bytes32 operation); if (ownerIndex == 0) return;
event Revoke(address owner, bytes32 operation); uint ownerIndexBit = 2**ownerIndex;
// some others are in the case of an owner changing. var pending = m_pending[_operation];
event OwnerChanged(address oldOwner, address newOwner); if (pending.ownersDone & ownerIndexBit > 0) {
event OwnerAdded(address newOwner); pending.yetNeeded++;
event OwnerRemoved(address oldOwner); pending.ownersDone -= ownerIndexBit;
// the last one is emitted if the required signatures change Revoke(msg.sender, _operation);
event RequirementChanged(uint newRequirement);
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
} }
// multi-sig function modifier: the operation must have an intrinsic hash in order }
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations. // Replaces an owner `_from` with another `_to`.
modifier onlymanyowners(bytes32 _operation) { function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (confirmAndCheck(_operation)) if (isOwner(_to)) return;
_; uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
} }
// determine the bit to set for this owner.
// METHODS uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
// constructor is given number of sigs required to do protected "onlymanyowners" transactions if (pending.ownersDone & ownerIndexBit == 0) {
// as well as the selection of addresses capable of confirming them. Confirmation(msg.sender, _operation);
function multiowned(address[] _owners, uint _required) { // ok - check if count is enough to go ahead.
m_numOwners = _owners.length + 1; if (pending.yetNeeded <= 1) {
m_owners[1] = uint(msg.sender); // enough confirmations: reset and run interior.
m_ownerIndex[uint(msg.sender)] = 1; delete m_pendingIndex[m_pending[_operation].index];
for (uint i = 0; i < _owners.length; ++i) delete m_pending[_operation];
{ return true;
m_owners[2 + i] = uint(_owners[i]); }
m_ownerIndex[uint(_owners[i])] = 2 + i; else
} {
m_required = _required; // not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
} }
}
// Revokes a prior confirmation of the given operation function reorganizeOwners() private {
function revoke(bytes32 _operation) external { uint free = 1;
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; while (free < m_numOwners)
// make sure they're an owner {
if (ownerIndex == 0) return; while (free < m_numOwners && m_owners[free] != 0) free++;
uint ownerIndexBit = 2**ownerIndex; while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
var pending = m_pending[_operation]; if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
if (pending.ownersDone & ownerIndexBit > 0) { {
pending.yetNeeded++; m_owners[free] = m_owners[m_numOwners];
pending.ownersDone -= ownerIndexBit; m_ownerIndex[m_owners[free]] = free;
Revoke(msg.sender, _operation); m_owners[m_numOwners] = 0;
} }
} }
}
// Replaces an owner `_from` with another `_to`. function clearPending() internal {
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { uint length = m_pendingIndex.length;
if (isOwner(_to)) return; for (uint i = 0; i < length; ++i)
uint ownerIndex = m_ownerIndex[uint(_from)]; if (m_pendingIndex[i] != 0)
if (ownerIndex == 0) return; delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
clearPending(); // FIELDS
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { // the number of owners that must confirm the same operation before it is run.
if (isOwner(_owner)) return; uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
clearPending(); // list of owners
if (m_numOwners >= c_maxOwners) uint[256] m_owners;
reorganizeOwners(); uint constant c_maxOwners = 250;
if (m_numOwners >= c_maxOwners) // index on the list of owners to allow reverse lookup
return; mapping(uint => uint) m_ownerIndex;
m_numOwners++; // the ongoing operations.
m_owners[m_numOwners] = uint(_owner); mapping(bytes32 => PendingState) m_pending;
m_ownerIndex[uint(_owner)] = m_numOwners; bytes32[] m_pendingIndex;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
// FIELDS
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
} }
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) // inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
@ -230,79 +231,70 @@ contract multiowned {
// uses is specified in the modifier. // uses is specified in the modifier.
contract daylimit is multiowned { contract daylimit is multiowned {
// MODIFIERS // METHODS
// simple modifier for daily limit. // constructor - stores initial daily limit and records the present day's index.
modifier limitedDaily(uint _value) { function daylimit(uint _limit) {
if (underLimit(_value)) m_dailyLimit = _limit;
_; m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
} }
// check to see if there's enough left - if so, subtract and return true.
// METHODS // overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
// constructor - stores initial daily limit and records the present day's index. m_spentToday += _value;
function daylimit(uint _limit) { return true;
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
} }
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
// INTERNAL METHODS // FIELDS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and uint public m_dailyLimit;
// returns true. otherwise just returns false. uint public m_spentToday;
function underLimit(uint _value) internal onlyowner returns (bool) { uint public m_lastDay;
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
// FIELDS
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
} }
// interface contract for multisig proxy contracts; see below for docs. // interface contract for multisig proxy contracts; see below for docs.
contract multisig { contract multisig {
// EVENTS // EVENTS
// logged events: // logged events:
// Funds has arrived into the wallet (record how much). // Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value); event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data); event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction. // Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// FUNCTIONS // FUNCTIONS
// TODO: document // TODO: document
function changeOwner(address _from, address _to) external; function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function execute(address _to, uint _value, bytes _data) external returns (bytes32); function confirm(bytes32 _h) external returns (bool o_success);
function confirm(bytes32 _h) returns (bool);
} }
// usage: // usage:
@ -310,79 +302,102 @@ contract multisig {
// Wallet(w).from(anotherOwner).confirm(h); // Wallet(w).from(anotherOwner).confirm(h);
contract Wallet is multisig, multiowned, daylimit { contract Wallet is multisig, multiowned, daylimit {
// TYPES // TYPES
// Transaction structure to remember details of transaction lest it need be saved for a later call. // Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction { struct Transaction {
address to; address to;
uint value; uint value;
bytes data; bytes data;
}
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function Wallet(address[] _owners, uint _required, uint _daylimit)
multiowned(_owners, _required) daylimit(_daylimit) {
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
} }
}
// METHODS function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
// constructor - just pass on the owner array to the multiowned and o_addr := create(_value, add(_code, 0x20), mload(_code))
// the limit to daylimit jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
function Wallet(address[] _owners, uint _required, uint _daylimit)
multiowned(_owners, _required) daylimit(_daylimit) {
} }
}
// kills the contract sending everything to `_to`. // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
function kill(address _to) onlymanyowners(sha3(msg.data)) external { // to determine the body of the transaction from the hash provided.
suicide(_to); function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
} }
}
// gets called when no other function matches // INTERNAL METHODS
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. function clearPending() internal {
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide uint length = m_pendingIndex.length;
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value for (uint i = 0; i < length; ++i)
// and _data arguments). They still get the option of using them if they want, anyways. delete m_txs[m_pendingIndex[i]];
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { super.clearPending();
// first, take the opportunity to check that we're under the daily limit. }
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_to.call.value(_value)(_data);
return 0;
}
// determine our operation hash.
_r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order // FIELDS
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
if (m_txs[_h].to != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS // pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
delete m_txs[m_pendingIndex[i]];
super.clearPending();
}
// FIELDS
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
} }

View File

@ -53,7 +53,13 @@ const renderEvent = (classNames, verb) => (e) => {
return ( return (
<tr key={ e.key } className={ classes }> <tr key={ e.key } className={ classes }>
<td> <td>
<Address address={ e.parameters.owner.value } /> <Address
address={
e.parameters.owner
? e.parameters.owner.value
: e.from
}
/>
</td> </td>
<td> <td>
<abbr title={ e.transaction }>{ verb }</abbr> <abbr title={ e.transaction }>{ verb }</abbr>
@ -80,17 +86,23 @@ const renderDataChanged = (e) => {
return ( return (
<tr key={ e.key } className={ classNames }> <tr key={ e.key } className={ classNames }>
<td> <td>
<Address address={ e.parameters.owner.value } /> <Address
address={
e.parameters.owner
? e.parameters.owner.value
: e.from
}
/>
</td> </td>
<td> <td>
<abbr title={ e.transaction }>updated</abbr> <abbr title={ e.transaction }>updated</abbr>
</td> </td>
<td> <td>
{ 'key ' } key&nbsp;
<code> <code>
{ new Buffer(e.parameters.plainKey.value).toString('utf8') } { new Buffer(e.parameters.plainKey.value).toString('utf8') }
</code> </code>
{ 'of ' } &nbsp;of&nbsp;
<code> <code>
<Hash hash={ bytesToHex(e.parameters.name.value) } /> <Hash hash={ bytesToHex(e.parameters.name.value) } />
</code> </code>

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { registry as registryAbi } from '~/contracts/abi'; import { registry as registryAbi, registry2 as registryAbi2 } from '~/contracts/abi';
import { api } from './parity.js'; import { api } from './parity.js';
import * as addresses from './addresses/actions.js'; import * as addresses from './addresses/actions.js';
@ -27,15 +27,17 @@ import * as reverse from './Reverse/actions.js';
export { addresses, accounts, lookup, events, names, records, reverse }; export { addresses, accounts, lookup, events, names, records, reverse };
export const setIsTestnet = (isTestnet) => ({ type: 'set isTestnet', isTestnet }); const REGISTRY_V1_HASHES = [
'0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
'0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
];
export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion });
export const fetchIsTestnet = () => (dispatch) => export const fetchIsTestnet = () => (dispatch) =>
api.net.version() api.net.version()
.then((netVersion) => { .then((netVersion) => {
dispatch(setIsTestnet( dispatch(setNetVersion(netVersion));
netVersion === '2' || // morden
netVersion === '3' // ropsten
));
}) })
.catch((err) => { .catch((err) => {
console.error('could not check if testnet'); console.error('could not check if testnet');
@ -47,13 +49,28 @@ export const fetchIsTestnet = () => (dispatch) =>
export const setContract = (contract) => ({ type: 'set contract', contract }); export const setContract = (contract) => ({ type: 'set contract', contract });
export const fetchContract = () => (dispatch) => export const fetchContract = () => (dispatch) =>
api.parity.registryAddress() api.parity
.registryAddress()
.then((address) => { .then((address) => {
const contract = api.newContract(registryAbi, address); return api.eth
.getCode(address)
.then((code) => {
const codeHash = api.util.sha3(code);
const isVersion1 = REGISTRY_V1_HASHES.includes(codeHash);
dispatch(setContract(contract)); console.log(`registry at ${address}, code ${codeHash}, version ${isVersion1 ? 1 : 2}`);
dispatch(fetchFee());
dispatch(fetchOwner()); const contract = api.newContract(
isVersion1
? registryAbi
: registryAbi2,
address
);
dispatch(setContract(contract));
dispatch(fetchFee());
dispatch(fetchOwner());
});
}) })
.catch((err) => { .catch((err) => {
console.error('could not fetch contract'); console.error('could not fetch contract');

View File

@ -22,8 +22,8 @@ import namesReducer from './Names/reducers.js';
import recordsReducer from './Records/reducers.js'; import recordsReducer from './Records/reducers.js';
import reverseReducer from './Reverse/reducers.js'; import reverseReducer from './Reverse/reducers.js';
const isTestnetReducer = (state = null, action) => const netVersionReducer = (state = null, action) =>
action.type === 'set isTestnet' ? action.isTestnet : state; action.type === 'set netVersion' ? action.netVersion : state;
const contractReducer = (state = null, action) => const contractReducer = (state = null, action) =>
action.type === 'set contract' ? action.contract : state; action.type === 'set contract' ? action.contract : state;
@ -35,7 +35,7 @@ const ownerReducer = (state = null, action) =>
action.type === 'set owner' ? action.owner : state; action.type === 'set owner' ? action.owner : state;
const initialState = { const initialState = {
isTestnet: isTestnetReducer(undefined, { type: '' }), netVersion: netVersionReducer(undefined, { type: '' }),
accounts: accountsReducer(undefined, { type: '' }), accounts: accountsReducer(undefined, { type: '' }),
contacts: contactsReducer(undefined, { type: '' }), contacts: contactsReducer(undefined, { type: '' }),
contract: contractReducer(undefined, { type: '' }), contract: contractReducer(undefined, { type: '' }),
@ -49,7 +49,7 @@ const initialState = {
}; };
export default (state = initialState, action) => ({ export default (state = initialState, action) => ({
isTestnet: isTestnetReducer(state.isTestnet, action), netVersion: netVersionReducer(state.netVersion, action),
accounts: accountsReducer(state.accounts, action), accounts: accountsReducer(state.accounts, action),
contacts: contactsReducer(state.contacts, action), contacts: contactsReducer(state.contacts, action),
contract: contractReducer(state.contract, action), contract: contractReducer(state.contract, action),

View File

@ -28,7 +28,7 @@ class Address extends Component {
static propTypes = { static propTypes = {
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
account: nullableProptype(PropTypes.object.isRequired), account: nullableProptype(PropTypes.object.isRequired),
isTestnet: PropTypes.bool.isRequired, netVersion: PropTypes.string.isRequired,
key: PropTypes.string, key: PropTypes.string,
shortenHash: PropTypes.bool shortenHash: PropTypes.bool
}; };
@ -56,7 +56,7 @@ class Address extends Component {
} }
renderCaption () { renderCaption () {
const { address, account, isTestnet, shortenHash } = this.props; const { address, account, netVersion, shortenHash } = this.props;
if (account) { if (account) {
const { name } = account; const { name } = account;
@ -64,7 +64,7 @@ class Address extends Component {
return ( return (
<a <a
className={ styles.link } className={ styles.link }
href={ etherscanUrl(address, isTestnet) } href={ etherscanUrl(address, false, netVersion) }
target='_blank' target='_blank'
> >
<abbr <abbr
@ -103,14 +103,14 @@ function mapStateToProps (initState, initProps) {
}); });
return (state, props) => { return (state, props) => {
const { isTestnet } = state; const { netVersion } = state;
const { address = '' } = props; const { address = '' } = props;
const account = allAccounts[address] || null; const account = allAccounts[address] || null;
return { return {
account, account,
isTestnet netVersion
}; };
}; };
} }

View File

@ -26,7 +26,7 @@ const leading0x = /^0x/;
class Hash extends Component { class Hash extends Component {
static propTypes = { static propTypes = {
hash: PropTypes.string.isRequired, hash: PropTypes.string.isRequired,
isTestnet: PropTypes.bool.isRequired, netVersion: PropTypes.string.isRequired,
linked: PropTypes.bool linked: PropTypes.bool
} }
@ -35,7 +35,7 @@ class Hash extends Component {
} }
render () { render () {
const { hash, isTestnet, linked } = this.props; const { hash, netVersion, linked } = this.props;
let shortened = hash.toLowerCase().replace(leading0x, ''); let shortened = hash.toLowerCase().replace(leading0x, '');
@ -47,7 +47,7 @@ class Hash extends Component {
return ( return (
<a <a
className={ styles.link } className={ styles.link }
href={ etherscanUrl(hash, isTestnet) } href={ etherscanUrl(hash, false, netVersion) }
target='_blank' target='_blank'
> >
<abbr title={ hash }>{ shortened }</abbr> <abbr title={ hash }>{ shortened }</abbr>
@ -61,7 +61,7 @@ class Hash extends Component {
export default connect( export default connect(
(state) => ({ // mapStateToProps (state) => ({ // mapStateToProps
isTestnet: state.isTestnet netVersion: state.netVersion
}), }),
null // mapDispatchToProps null // mapDispatchToProps
)(Hash); )(Hash);

View File

@ -14,13 +14,15 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { url as externalUrl } from '~/3rdparty/etherscan/links';
const leading0x = /^0x/; const leading0x = /^0x/;
const etherscanUrl = (hash, isTestnet) => { const etherscanUrl = (hash, isTestnet, netVersion) => {
hash = hash.toLowerCase().replace(leading0x, ''); hash = hash.toLowerCase().replace(leading0x, '');
const type = hash.length === 40 ? 'address' : 'tx'; const type = hash.length === 40 ? 'address' : 'tx';
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/${type}/0x${hash}`; return `https://${externalUrl(isTestnet, netVersion)}/${type}/0x${hash}`;
}; };
export default etherscanUrl; export default etherscanUrl;

View File

@ -16,6 +16,7 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { url as etherscanUrl } from '~/3rdparty/etherscan/links';
import * as abis from '~/contracts/abi'; import * as abis from '~/contracts/abi';
import { api } from './parity'; import { api } from './parity';
@ -28,7 +29,7 @@ const subscriptions = {};
let defaultSubscriptionId; let defaultSubscriptionId;
let nextSubscriptionId = 1000; let nextSubscriptionId = 1000;
let isTest = false; let netVersion = '0';
export function subscribeEvents (addresses, callback) { export function subscribeEvents (addresses, callback) {
const subscriptionId = nextSubscriptionId++; const subscriptionId = nextSubscriptionId++;
@ -117,15 +118,16 @@ export function attachInstances () {
return Promise return Promise
.all([ .all([
api.parity.registryAddress(), api.parity.registryAddress(),
api.parity.netChain() api.parity.netChain(),
api.partiy.netVersion()
]) ])
.then(([registryAddress, netChain]) => { .then(([registryAddress, netChain, _netVersion]) => {
const registry = api.newContract(abis.registry, registryAddress).instance; const registry = api.newContract(abis.registry, registryAddress).instance;
isTest = ['morden', 'ropsten', 'testnet'].includes(netChain); netVersion = _netVersion;
console.log(`contract was found at registry=${registryAddress}`); console.log(`contract was found at registry=${registryAddress}`);
console.log(`running on ${netChain}, isTest=${isTest}`); console.log(`running on ${netChain}, network ${netVersion}`);
return Promise return Promise
.all([ .all([
@ -287,5 +289,5 @@ export function loadTokenBalance (tokenAddress, address) {
} }
export function txLink (txHash) { export function txLink (txHash) {
return `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${txHash}`; return `https://${etherscanUrl(false, netVersion)}/tx/${txHash}`;
} }

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { Chip } from 'material-ui'; import { Chip } from 'material-ui';
import IdentityIcon from '../IdentityIcon' ; import IdentityIcon from '../IdentityIcon';
import styles from './chip.css'; import styles from './chip.css';

View File

@ -18,7 +18,7 @@ export default {
connectingAPI: `Connecting to the Parity Secure API.`, connectingAPI: `Connecting to the Parity Secure API.`,
connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`, connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`,
invalidToken: `invalid signer token`, invalidToken: `invalid signer token`,
noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and supply the token below`, noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.`,
token: { token: {
hint: `a generated token from Parity`, hint: `a generated token from Parity`,
label: `secure token` label: `secure token`

View File

@ -97,54 +97,6 @@ The following options are possible for the \`defaultBlock\` parameter:
} }
}, },
compileSerpent: {
desc: 'Returns compiled serpent code.',
params: [
{
type: String,
desc: 'The source code.',
example: '/* some serpent */'
}
],
returns: {
type: Data,
desc: 'The compiled source code.',
example: '0x603880600c6000396000f3006001600060e060020a600035048063c6888fa114601857005b6021600435602b565b8060005260206000f35b600081600702905091905056'
}
},
compileSolidity: {
desc: 'Returns compiled solidity code.',
params: [
{
type: String,
desc: 'The source code.',
example: 'contract test { function multiply(uint a) returns(uint d) { return a * 7; } }'
}
],
returns: {
type: Data,
desc: 'The compiled source code.',
example: '0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056'
}
},
compileLLL: {
desc: 'Returns compiled LLL code.',
params: [
{
type: String,
desc: 'The source code.',
example: '(returnlll (suicide (caller)))'
}
],
returns: {
type: Data,
desc: 'The compiled source code.',
example: '0x603880600c6000396000f3006001600060e060020a600035048063c6888fa114601857005b6021600435602b565b8060005260206000f35b600081600702905091905056'
}
},
estimateGas: { estimateGas: {
desc: 'Makes a call or transaction, which won\'t be added to the blockchain and returns the used gas, which can be used for estimating the used gas.', desc: 'Makes a call or transaction, which won\'t be added to the blockchain and returns the used gas, which can be used for estimating the used gas.',
params: [ params: [
@ -415,16 +367,6 @@ The following options are possible for the \`defaultBlock\` parameter:
} }
}, },
getCompilers: {
desc: 'Returns a list of available compilers in the client.',
params: [],
returns: {
type: Array,
desc: 'Array of available compilers.',
example: ['solidity', 'lll', 'serpent']
}
},
getFilterChanges: { getFilterChanges: {
desc: 'Polling method for a filter, which returns an array of logs which occurred since last poll.', desc: 'Polling method for a filter, which returns an array of logs which occurred since last poll.',
params: [ params: [

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest } from '../types'; import { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest, TransactionResponse } from '../types';
import { fromDecimal, withComment, Dummy } from '../helpers'; import { fromDecimal, withComment, Dummy } from '../helpers';
const SECTION_ACCOUNTS = 'Accounts (read-only) and Signatures'; const SECTION_ACCOUNTS = 'Accounts (read-only) and Signatures';
@ -27,86 +27,6 @@ const SECTION_VAULT = 'Account Vaults';
const SUBDOC_SET = 'set'; const SUBDOC_SET = 'set';
const SUBDOC_ACCOUNTS = 'accounts'; const SUBDOC_ACCOUNTS = 'accounts';
const transactionDetails = {
hash: {
type: Hash,
desc: '32 Bytes - hash of the transaction.'
},
nonce: {
type: Quantity,
desc: 'The number of transactions made by the sender prior to this one.'
},
blockHash: {
type: Hash,
desc: '32 Bytes - hash of the block where this transaction was in. `null` when its pending.'
},
blockNumber: {
type: BlockNumber,
desc: 'Block number where this transaction was in. `null` when its pending.'
},
transactionIndex: {
type: Quantity,
desc: 'Integer of the transactions index position in the block. `null` when its pending.'
},
from: {
type: Address,
desc: '20 Bytes - address of the sender.'
},
to: {
type: Address,
desc: '20 Bytes - address of the receiver. `null` when its a contract creation transaction.'
},
value: {
type: Quantity,
desc: 'Value transferred in Wei.'
},
gasPrice: {
type: Quantity,
desc: 'Gas price provided by the sender in Wei.'
},
gas: {
type: Quantity,
desc: 'Gas provided by the sender.'
},
input: {
type: Data,
desc: 'The data send along with the transaction.'
},
raw: {
type: Data,
desc: 'Raw transaction data.'
},
publicKey: {
type: Data,
desc: 'Public key of the signer.'
},
networkId: {
type: Quantity,
desc: 'The network id of the transaction, if any.'
},
standardV: {
type: Quantity,
desc: 'The standardized V field of the signature (0 or 1).'
},
v: {
type: Quantity,
desc: 'The V field of the signature.'
},
r: {
type: Quantity,
desc: 'The R field of the signature.'
},
s: {
type: Quantity,
desc: 'The S field of the signature.'
},
condition: {
type: Object,
optional: true,
desc: 'Conditional submission, Block number in `block` or timestamp in `time` or `null`.'
}
};
export default { export default {
accountsInfo: { accountsInfo: {
section: SECTION_ACCOUNTS, section: SECTION_ACCOUNTS,
@ -393,6 +313,32 @@ export default {
} }
}, },
hardwareAccountsInfo: {
section: SECTION_ACCOUNTS,
desc: 'Provides metadata for attached hardware wallets',
params: [],
returns: {
type: Object,
desc: 'Maps account address to metadata.',
details: {
manufacturer: {
type: String,
desc: 'Manufacturer'
},
name: {
type: String,
desc: 'Account name'
}
},
example: {
'0x0024d0c7ab4c52f723f3aaf0872b9ea4406846a4': {
manufacturer: 'Ledger',
name: 'Nano S'
}
}
}
},
listOpenedVaults: { listOpenedVaults: {
desc: 'Returns a list of all opened vaults', desc: 'Returns a list of all opened vaults',
params: [], params: [],
@ -608,7 +554,7 @@ export default {
returns: { returns: {
type: Array, type: Array,
desc: 'Transactions ordered by priority', desc: 'Transactions ordered by priority',
details: transactionDetails, details: TransactionResponse.details,
example: [ example: [
{ {
blockHash: null, blockHash: null,
@ -924,7 +870,7 @@ export default {
returns: { returns: {
type: Array, type: Array,
desc: 'Transaction list.', desc: 'Transaction list.',
details: transactionDetails, details: TransactionResponse.details,
example: [ example: [
{ {
hash: '0x80de421cd2e7e46824a91c343ca42b2ff339409eef09e2d9d73882462f8fce31', hash: '0x80de421cd2e7e46824a91c343ca42b2ff339409eef09e2d9d73882462f8fce31',

View File

@ -109,3 +109,92 @@ export class TransactionRequest {
} }
} }
} }
export class TransactionResponse {
static print = '`Object`';
static details = {
hash: {
type: Hash,
desc: '32 Bytes - hash of the transaction.'
},
nonce: {
type: Quantity,
desc: 'The number of transactions made by the sender prior to this one.'
},
blockHash: {
type: Hash,
desc: '32 Bytes - hash of the block where this transaction was in. `null` when its pending.'
},
blockNumber: {
type: BlockNumber,
desc: 'Block number where this transaction was in. `null` when its pending.'
},
transactionIndex: {
type: Quantity,
desc: 'Integer of the transactions index position in the block. `null` when its pending.'
},
from: {
type: Address,
desc: '20 Bytes - address of the sender.'
},
to: {
type: Address,
desc: '20 Bytes - address of the receiver. `null` when its a contract creation transaction.'
},
value: {
type: Quantity,
desc: 'Value transferred in Wei.'
},
gasPrice: {
type: Quantity,
desc: 'Gas price provided by the sender in Wei.'
},
gas: {
type: Quantity,
desc: 'Gas provided by the sender.'
},
input: {
type: Data,
desc: 'The data send along with the transaction.'
},
creates: {
type: Address,
optional: true,
desc: 'Address of a created contract or `null`.'
},
raw: {
type: Data,
desc: 'Raw transaction data.'
},
publicKey: {
type: Data,
desc: 'Public key of the signer.'
},
networkId: {
type: Quantity,
desc: 'The network id of the transaction, if any.'
},
standardV: {
type: Quantity,
desc: 'The standardized V field of the signature (0 or 1).'
},
v: {
type: Quantity,
desc: 'The V field of the signature.'
},
r: {
type: Quantity,
desc: 'The R field of the signature.'
},
s: {
type: Quantity,
desc: 'The S field of the signature.'
},
condition: {
type: Object,
optional: true,
desc: 'Conditional submission, Block number in `block` or timestamp in `time` or `null`.'
}
}
}

View File

@ -0,0 +1,159 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, computed, observable, transaction } from 'mobx';
import Ledger from '~/3rdparty/ledger';
const HW_SCAN_INTERVAL = 5000;
let instance = null;
export default class HardwareStore {
@observable isScanning = false;
@observable wallets = {};
constructor (api) {
this._api = api;
this._ledger = Ledger.create(api);
this._pollId = null;
this._pollScan();
}
isConnected (address) {
return computed(() => !!this.wallets[address]).get();
}
@action setScanning = (isScanning) => {
this.isScanning = isScanning;
}
@action setWallets = (wallets) => {
this.wallets = wallets;
}
_pollScan = () => {
this._pollId = setTimeout(() => {
this.scan().then(this._pollScan);
}, HW_SCAN_INTERVAL);
}
scanLedger () {
if (!this._ledger.isSupported) {
return Promise.resolve({});
}
return this._ledger
.scan()
.then((wallets) => {
console.log('HardwareStore::scanLedger', wallets);
return wallets.reduce((hwInfo, wallet) => {
wallet.manufacturer = 'Ledger';
wallet.name = 'Nano S';
wallet.via = 'ledger';
hwInfo[wallet.address] = wallet;
return hwInfo;
}, {});
})
.catch((error) => {
console.warn('HardwareStore::scanLedger', error);
return {};
});
}
scanParity () {
return this._api.parity
.hardwareAccountsInfo()
.then((hwInfo) => {
Object
.keys(hwInfo)
.forEach((address) => {
const info = hwInfo[address];
info.address = address;
info.via = 'parity';
});
return hwInfo;
})
.catch((error) => {
console.warn('HardwareStore::scanParity', error);
return {};
});
}
scan () {
this.setScanning(true);
// NOTE: Depending on how the hardware is configured and how the local env setup
// is done, different results will be retrieved via Parity vs. the browser APIs
// (latter is Chrome-only, needs the browser app enabled on a Ledger, former is
// not intended as a network call, i.e. hw wallet is with the user)
return Promise
.all([
this.scanParity(),
this.scanLedger()
])
.then(([hwAccounts, ledgerAccounts]) => {
transaction(() => {
this.setWallets(Object.assign({}, hwAccounts, ledgerAccounts));
this.setScanning(false);
});
});
}
createAccountInfo (entry) {
const { address, manufacturer, name } = entry;
return Promise
.all([
this._api.parity.setAccountName(address, name),
this._api.parity.setAccountMeta(address, {
description: `${manufacturer} ${name}`,
hardware: {
manufacturer
},
tags: ['hardware'],
timestamp: Date.now()
})
])
.catch((error) => {
console.warn('HardwareStore::createEntry', error);
throw error;
});
}
signLedger (transaction) {
return this._ledger.signTransaction(transaction);
}
static get (api) {
if (!instance) {
instance = new HardwareStore(api);
}
return instance;
}
}
export {
HW_SCAN_INTERVAL
};

View File

@ -0,0 +1,220 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import sinon from 'sinon';
import HardwareStore, { HW_SCAN_INTERVAL } from './hardwareStore';
const ADDRESS = '0x1234567890123456789012345678901234567890';
const WALLET = {
address: ADDRESS,
name: 'testing'
};
let api;
let clock;
let ledger;
let store;
function createApi () {
api = {
parity: {
hardwareAccountsInfo: sinon.stub().resolves({ ADDRESS: WALLET }),
setAccountMeta: sinon.stub().resolves(true),
setAccountName: sinon.stub().resolves(true)
}
};
return api;
}
function createLedger () {
ledger = {
isSupported: true,
getAppConfiguration: sinon.stub().resolves(),
scan: sinon.stub().resolves(WALLET),
signTransaction: sinon.stub().resolves()
};
return ledger;
}
function create () {
clock = sinon.useFakeTimers();
store = new HardwareStore(createApi());
store._ledger = createLedger();
return store;
}
function teardown () {
clock.restore();
}
describe('mobx/HardwareStore', () => {
beforeEach(() => {
create();
});
afterEach(() => {
teardown();
});
describe('@computed', () => {
describe('isConnected', () => {
beforeEach(() => {
store.setWallets({ [ADDRESS]: WALLET });
});
it('returns true for available', () => {
expect(store.isConnected(ADDRESS)).to.be.true;
});
it('returns false for non-available', () => {
expect(store.isConnected('nothing')).to.be.false;
});
});
});
describe('background polling', () => {
let pollId;
beforeEach(() => {
pollId = store._pollId;
sinon.spy(store, 'scan');
});
afterEach(() => {
store.scan.restore();
});
it('starts the polling at creation', () => {
expect(pollId).not.to.be.null;
});
it('scans when timer elapsed', () => {
expect(store.scan).not.to.have.been.called;
clock.tick(HW_SCAN_INTERVAL + 1);
expect(store.scan).to.have.been.called;
});
});
describe('@action', () => {
describe('setScanning', () => {
it('sets the flag', () => {
store.setScanning('testScanning');
expect(store.isScanning).to.equal('testScanning');
});
});
describe('setWallets', () => {
it('sets the wallets', () => {
store.setWallets('testWallet');
expect(store.wallets).to.equal('testWallet');
});
});
});
describe('operations', () => {
describe('createAccountInfo', () => {
beforeEach(() => {
return store.createAccountInfo({
address: 'testAddr',
manufacturer: 'testMfg',
name: 'testName'
});
});
it('calls into parity_setAccountName', () => {
expect(api.parity.setAccountName).to.have.been.calledWith('testAddr', 'testName');
});
it('calls into parity_setAccountMeta', () => {
expect(api.parity.setAccountMeta).to.have.been.calledWith('testAddr', sinon.match({
description: 'testMfg testName',
hardware: {
manufacturer: 'testMfg'
}
}));
});
});
describe('scanLedger', () => {
beforeEach(() => {
return store.scanLedger();
});
it('calls scan on the Ledger APIs', () => {
expect(ledger.scan).to.have.been.called;
});
});
describe('scanParity', () => {
beforeEach(() => {
return store.scanParity();
});
it('calls parity_hardwareAccountsInfo', () => {
expect(api.parity.hardwareAccountsInfo).to.have.been.called;
});
});
describe('scan', () => {
beforeEach(() => {
sinon.spy(store, 'setScanning');
sinon.spy(store, 'setWallets');
sinon.spy(store, 'scanLedger');
sinon.spy(store, 'scanParity');
return store.scan();
});
afterEach(() => {
store.setScanning.restore();
store.setWallets.restore();
store.scanLedger.restore();
store.scanParity.restore();
});
it('calls scanLedger', () => {
expect(store.scanLedger).to.have.been.called;
});
it('calls scanParity', () => {
expect(store.scanParity).to.have.been.called;
});
it('sets and resets the scanning state', () => {
expect(store.setScanning).to.have.been.calledWith(true);
expect(store.setScanning).to.have.been.calledWith(false);
});
it('sets the wallets', () => {
expect(store.setWallets).to.have.been.called;
});
});
describe('signLedger', () => {
beforeEach(() => {
return store.signLedger('testTx');
});
it('calls signTransaction on the ledger', () => {
expect(ledger.signTransaction).to.have.been.calledWith('testTx');
});
});
});
});

View File

@ -29,7 +29,7 @@ function create () {
return store; return store;
} }
describe('views/HistoryStore', () => { describe('mobx/HistoryStore', () => {
beforeEach(() => { beforeEach(() => {
create(); create();
}); });

View File

@ -0,0 +1,51 @@
// Copyright 2015-2017 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 { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { VaultSelect } from '~/ui';
@observer
export default class ChangeVault extends Component {
static propTypes = {
store: PropTypes.object.isRequired,
vaultStore: PropTypes.object
}
render () {
const { store, vaultStore } = this.props;
const { vaultName } = store;
if (!vaultStore || vaultStore.vaultsOpened.length === 0) {
return null;
}
return (
<VaultSelect
onSelect={ this.onSelect }
value={ vaultName }
vaultStore={ vaultStore }
/>
);
}
onSelect = (vaultName) => {
const { store } = this.props;
store.setVaultName(vaultName);
}
}

View File

@ -0,0 +1,100 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import ChangeVault from './';
let component;
let instance;
let store;
let vaultStore;
function createStore () {
store = {
setVaultName: sinon.stub(),
vaultName: 'testing'
};
return store;
}
function createVaultStore () {
vaultStore = {
vaultsOpened: ['testing']
};
return vaultStore;
}
function render () {
component = shallow(
<ChangeVault
store={ createStore() }
vaultStore={ createVaultStore() }
/>
);
instance = component.instance();
return component;
}
describe('modals/CreateAccount/ChangeVault', () => {
beforeEach(() => {
render();
});
it('renders defaults', () => {
expect(component).to.be.ok;
});
describe('components', () => {
describe('VaultSelect', () => {
let select;
beforeEach(() => {
select = component.find('VaultSelect');
});
it('renders', () => {
expect(select.get(0)).to.be.ok;
});
it('passes onSelect as instance method', () => {
expect(select.props().onSelect).to.equal(instance.onSelect);
});
it('passes the value', () => {
expect(select.props().value).to.equal('testing');
});
it('passes the vaultStore', () => {
expect(select.props().vaultStore).to.equal(vaultStore);
});
});
});
describe('instance methods', () => {
describe('onSelect', () => {
it('calls into store setVaultName', () => {
instance.onSelect('newName');
expect(store.setVaultName).to.have.been.calledWith('newName');
});
});
});
});

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './changeVault';

View File

@ -24,13 +24,15 @@ import { Form, Input, IdentityIcon } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength'; import PasswordStrength from '~/ui/Form/PasswordStrength';
import { RefreshIcon } from '~/ui/Icons'; import { RefreshIcon } from '~/ui/Icons';
import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
@observer @observer
export default class CreateAccount extends Component { export default class CreateAccount extends Component {
static propTypes = { static propTypes = {
newError: PropTypes.func.isRequired, newError: PropTypes.func.isRequired,
store: PropTypes.object.isRequired store: PropTypes.object.isRequired,
vaultStore: PropTypes.object
} }
state = { state = {
@ -123,6 +125,10 @@ export default class CreateAccount extends Component {
</div> </div>
</div> </div>
<PasswordStrength input={ password } /> <PasswordStrength input={ password } />
<ChangeVault
store={ this.props.store }
vaultStore={ this.props.vaultStore }
/>
{ this.renderIdentitySelector() } { this.renderIdentitySelector() }
{ this.renderIdentities() } { this.renderIdentities() }
</Form> </Form>

View File

@ -14,27 +14,25 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { FloatingActionButton } from 'material-ui';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Form, Input } from '~/ui'; import { Form, FileSelect, Input } from '~/ui';
import { AttachFileIcon } from '~/ui/Icons';
import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
const STYLE_HIDDEN = { display: 'none' };
@observer @observer
export default class NewImport extends Component { export default class NewImport extends Component {
static propTypes = { static propTypes = {
store: PropTypes.object.isRequired store: PropTypes.object.isRequired,
vaultStore: PropTypes.object
} }
render () { render () {
const { name, nameError, password, passwordHint, walletFile, walletFileError } = this.props.store; const { name, nameError, password, passwordHint } = this.props.store;
return ( return (
<Form> <Form>
@ -93,58 +91,52 @@ export default class NewImport extends Component {
/> />
</div> </div>
</div> </div>
<div> <ChangeVault
<Input store={ this.props.store }
disabled vaultStore={ this.props.vaultStore }
error={ walletFileError } />
hint={ { this.renderFileSelector() }
<FormattedMessage
id='createAccount.newImport.file.hint'
defaultMessage='the wallet file for import'
/>
}
label={
<FormattedMessage
id='createAccount.newImport.file.label'
defaultMessage='wallet file'
/>
}
value={ walletFile }
/>
<div className={ styles.upload }>
<FloatingActionButton
mini
onTouchTap={ this.openFileDialog }
>
<AttachFileIcon />
</FloatingActionButton>
<input
onChange={ this.onFileChange }
ref='fileUpload'
style={ STYLE_HIDDEN }
type='file'
/>
</div>
</div>
</Form> </Form>
); );
} }
onFileChange = (event) => { renderFileSelector () {
const { store } = this.props; const { walletFile, walletFileError } = this.props.store;
if (event.target.files.length) { return walletFile
const reader = new FileReader(); ? (
<Input
reader.onload = (event) => store.setWalletJson(event.target.result); disabled
reader.readAsText(event.target.files[0]); error={ walletFileError }
} hint={
<FormattedMessage
store.setWalletFile(event.target.value); id='createAccount.newImport.file.hint'
defaultMessage='the wallet file for import'
/>
}
label={
<FormattedMessage
id='createAccount.newImport.file.label'
defaultMessage='wallet file'
/>
}
value={ walletFile }
/>
)
: (
<FileSelect
className={ styles.fileImport }
error={ walletFileError }
onSelect={ this.onFileSelect }
/>
);
} }
openFileDialog = () => { onFileSelect = (fileName, fileContent) => {
ReactDOM.findDOMNode(this.refs.fileUpload).click(); const { store } = this.props;
store.setWalletFile(fileName);
store.setWalletJson(fileContent);
} }
onEditName = (event, name) => { onEditName = (event, name) => {

View File

@ -21,6 +21,7 @@ import { FormattedMessage } from 'react-intl';
import { Form, Input } from '~/ui'; import { Form, Input } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength'; import PasswordStrength from '~/ui/Form/PasswordStrength';
import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
@observer @observer
@ -30,7 +31,8 @@ export default class RawKey extends Component {
} }
static propTypes = { static propTypes = {
store: PropTypes.object.isRequired store: PropTypes.object.isRequired,
vaultStore: PropTypes.object
} }
render () { render () {
@ -131,6 +133,10 @@ export default class RawKey extends Component {
</div> </div>
</div> </div>
<PasswordStrength input={ password } /> <PasswordStrength input={ password } />
<ChangeVault
store={ this.props.store }
vaultStore={ this.props.vaultStore }
/>
</Form> </Form>
); );
} }

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