Merge branch 'master' into cht-td

This commit is contained in:
Robert Habermeier 2017-02-03 19:56:52 +01:00
commit 02142e3a57
85 changed files with 1178 additions and 726 deletions

96
Cargo.lock generated
View File

@ -73,6 +73,11 @@ name = "ansi_term"
version = "0.7.2" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "antidote"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "app_dirs" name = "app_dirs"
version = "1.1.1" version = "1.1.1"
@ -285,6 +290,11 @@ name = "dtoa"
version = "0.2.2" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dtoa"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "either" name = "either"
version = "1.0.2" version = "1.0.2"
@ -784,7 +794,8 @@ dependencies = [
"futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.2 (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)",
"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)",
"reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -901,6 +912,35 @@ dependencies = [
"vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "hyper"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hyper-native-tls"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.1.0" version = "0.1.0"
@ -962,6 +1002,11 @@ name = "itoa"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "itoa"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "jsonrpc-core" name = "jsonrpc-core"
version = "5.0.0" version = "5.0.0"
@ -1522,7 +1567,7 @@ 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#cb8dc7c61dd7976f062863182a1c6d77ba319a36" source = "git+https://github.com/ethcore/js-precompiled.git#416d00db677b8219f7548bb4dfa2f25c4b19f36e"
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)",
] ]
@ -1720,15 +1765,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.2.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-native-tls 0.2.2 (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)",
"native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1914,6 +1959,11 @@ name = "serde"
version = "0.8.19" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "serde_codegen" name = "serde_codegen"
version = "0.8.19" version = "0.8.19"
@ -1946,11 +1996,24 @@ dependencies = [
] ]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_json"
version = "0.3.0" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_urlencoded"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -2410,6 +2473,7 @@ dependencies = [
"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
"checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03" "checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03"
"checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962" "checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962"
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
@ -2438,6 +2502,7 @@ dependencies = [
"checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
"checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" "checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76"
"checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d"
"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f"
"checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" "checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91"
"checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "<none>" "checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "<none>"
"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5"
@ -2455,13 +2520,16 @@ dependencies = [
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
"checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae"
"checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "<none>" "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "<none>"
"checksum hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "220407e5a263f110ec30a071787c9535918fdfc97def5680c90013c3f30c38c1"
"checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7" "checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7"
"checksum hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afe68f772f0497a7205e751626bb8e1718568b58534b6108c73a74ef80483409"
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
"checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" "checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484"
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
"checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a" "checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a"
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5"
"checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
@ -2536,7 +2604,7 @@ dependencies = [
"checksum rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "655df67c314c30fa3055a365eae276eb88aa4f3413a352a1ab32c1320eda41ea" "checksum rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "655df67c314c30fa3055a365eae276eb88aa4f3413a352a1ab32c1320eda41ea"
"checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29"
"checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9"
"checksum reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83186fee0d4dbeb95e610b77b05b05cf5b31703dd375222acb74c3dff4be957c" "checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6"
"checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
@ -2555,10 +2623,12 @@ dependencies = [
"checksum semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2ff60ecdb19c255841c066cbfa5f8c2a4ada1eb3ae47c77ab6667128da71f5" "checksum semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2ff60ecdb19c255841c066cbfa5f8c2a4ada1eb3ae47c77ab6667128da71f5"
"checksum semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e88e43a5a74dd2a11707f9c21dfd4a423c66bd871df813227bb0a3e78f3a1ae9" "checksum semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e88e43a5a74dd2a11707f9c21dfd4a423c66bd871df813227bb0a3e78f3a1ae9"
"checksum serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "58a19c0871c298847e6b68318484685cd51fa5478c0c905095647540031356e5" "checksum serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "58a19c0871c298847e6b68318484685cd51fa5478c0c905095647540031356e5"
"checksum serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4d8f810025e9d09c4eaa49c16eaf878f34a947889e878cd7d3b5bef3197cc119"
"checksum serde_codegen 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ce29a6ae259579707650ec292199b5fed2c0b8e2a4bdc994452d24d1bcf2242a" "checksum serde_codegen 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ce29a6ae259579707650ec292199b5fed2c0b8e2a4bdc994452d24d1bcf2242a"
"checksum serde_codegen_internals 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "59933a62554548c690d2673c5164f0c4a46be7c5731edfd94b0ecb1048940732" "checksum serde_codegen_internals 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "59933a62554548c690d2673c5164f0c4a46be7c5731edfd94b0ecb1048940732"
"checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b" "checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b"
"checksum serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53d4ebaa8d1d4f90d1b63dfca81ccd98ac20e1e479dbae393cbaf60f6fecd8d8" "checksum serde_json 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fea48f4d4df4e620e3c81fd2bf28c93dd0d266361a76bac4f254b71f0e13f3cd"
"checksum serde_urlencoded 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81f15da4b9780e1524697f73b09076b6e42298ef673bead9ca8f848b334ef84"
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d" "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd" "checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"

View File

@ -214,6 +214,7 @@ impl HeaderChain {
pub fn get_header(&self, id: BlockId) -> Option<Bytes> { pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
match id { match id {
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()), BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
BlockId::Latest if self.headers.read().is_empty() => Some(self.genesis_header.clone()),
BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()), BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()),
BlockId::Number(num) => { BlockId::Number(num) => {
if self.best_block.read().number < num { return None } if self.best_block.read().number < num { return None }

View File

@ -21,8 +21,9 @@ use ethcore::block_status::BlockStatus;
use ethcore::client::ClientReport; use ethcore::client::ClientReport;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::header::Header; use ethcore::header::Header;
use ethcore::views::HeaderView;
use ethcore::verification::queue::{self, HeaderQueue}; use ethcore::verification::queue::{self, HeaderQueue};
use ethcore::transaction::PendingTransaction; use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition};
use ethcore::blockchain_info::BlockChainInfo; use ethcore::blockchain_info::BlockChainInfo;
use ethcore::spec::Spec; use ethcore::spec::Spec;
use ethcore::service::ClientIoMessage; use ethcore::service::ClientIoMessage;
@ -34,6 +35,7 @@ use util::{Bytes, Mutex, RwLock};
use provider::Provider; use provider::Provider;
use request; use request;
use time;
use self::header_chain::HeaderChain; use self::header_chain::HeaderChain;
@ -110,7 +112,11 @@ impl Client {
let best_num = self.chain.best_block().number; let best_num = self.chain.best_block().number;
self.tx_pool.lock() self.tx_pool.lock()
.values() .values()
.filter(|t| t.min_block.as_ref().map_or(true, |x| x <= &best_num)) .filter(|t| match t.condition {
Some(TransactionCondition::Number(ref x)) => x <= &best_num,
Some(TransactionCondition::Timestamp(ref x)) => *x <= time::get_time().sec as u64,
None => true,
})
.cloned() .cloned()
.collect() .collect()
} }
@ -135,6 +141,7 @@ impl Client {
genesis_hash: genesis_hash, genesis_hash: genesis_hash,
best_block_hash: best_block.hash, best_block_hash: best_block.hash,
best_block_number: best_block.number, best_block_number: best_block.number,
best_block_timestamp: HeaderView::new(&self.chain.get_header(BlockId::Latest).expect("Latest hash is always in the chain")).timestamp(),
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None }, ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
ancient_block_number: if first_block.is_some() { Some(0) } else { None }, ancient_block_number: if first_block.is_some() { Some(0) } else { None },
first_block_hash: first_block.as_ref().map(|first| first.hash), first_block_hash: first_block.as_ref().map(|first| first.hash),

View File

@ -9,7 +9,11 @@
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
] ]
} },
"timeoutPropose": 10000,
"timeoutPrevote": 10000,
"timeoutPrecommit": 10000,
"timeoutCommit": 10000
} }
} }
}, },

View File

@ -21,7 +21,7 @@ mod stores;
use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy}; use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
use std::fmt; use std::fmt;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use util::RwLock; use util::RwLock;
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
@ -114,8 +114,8 @@ impl AccountProvider {
pub fn new(sstore: Box<SecretStore>) -> Self { pub fn new(sstore: Box<SecretStore>) -> Self {
AccountProvider { AccountProvider {
unlocked: RwLock::new(HashMap::new()), unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::new(sstore.local_path().into())), address_book: RwLock::new(AddressBook::new(&sstore.local_path())),
dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())), dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
sstore: sstore, sstore: sstore,
transient_sstore: transient_sstore(), transient_sstore: transient_sstore(),
} }
@ -216,7 +216,7 @@ impl AccountProvider {
Some(accounts) => Ok(accounts), Some(accounts) => Ok(accounts),
None => match dapps.policy() { None => match dapps.policy() {
NewDappsPolicy::AllAccounts => self.accounts(), NewDappsPolicy::AllAccounts => self.accounts(),
NewDappsPolicy::Whitelist(accounts) => Ok(accounts), NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts),
} }
} }
} }
@ -231,28 +231,42 @@ impl AccountProvider {
/// Sets addresses visile for dapp. /// Sets addresses visile for dapp.
pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> { pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> {
let addresses = self.filter_addresses(addresses)?;
self.dapps_settings.write().set_accounts(dapp, addresses); self.dapps_settings.write().set_accounts(dapp, addresses);
Ok(()) Ok(())
} }
/// Returns each address along with metadata. /// Removes addresses that are neither accounts nor in address book.
pub fn addresses_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> { fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
Ok(self.address_book.read().get()) let valid = self.addresses_info().into_iter()
.map(|(address, _)| address)
.chain(self.accounts()?)
.collect::<HashSet<_>>();
Ok(addresses.into_iter()
.filter(|a| valid.contains(&a))
.collect()
)
} }
/// Returns each address along with metadata. /// Returns each address along with metadata.
pub fn set_address_name(&self, account: Address, name: String) -> Result<(), Error> { pub fn addresses_info(&self) -> HashMap<Address, AccountMeta> {
Ok(self.address_book.write().set_name(account, name)) self.address_book.read().get()
} }
/// Returns each address along with metadata. /// Returns each address along with metadata.
pub fn set_address_meta(&self, account: Address, meta: String) -> Result<(), Error> { pub fn set_address_name(&self, account: Address, name: String) {
Ok(self.address_book.write().set_meta(account, meta)) self.address_book.write().set_name(account, name)
}
/// Returns each address along with metadata.
pub fn set_address_meta(&self, account: Address, meta: String) {
self.address_book.write().set_meta(account, meta)
} }
/// Removes and address from the addressbook /// Removes and address from the addressbook
pub fn remove_address(&self, addr: Address) -> Result<(), Error> { pub fn remove_address(&self, addr: Address) {
Ok(self.address_book.write().remove(addr)) self.address_book.write().remove(addr)
} }
/// Returns each account along with name and meta. /// Returns each account along with name and meta.
@ -502,9 +516,12 @@ mod tests {
let app = DappId("app1".into()); let app = DappId("app1".into());
// set `AllAccounts` policy // set `AllAccounts` policy
ap.set_new_dapps_whitelist(None).unwrap(); ap.set_new_dapps_whitelist(None).unwrap();
// add accounts to address book
ap.set_address_name(1.into(), "1".into());
ap.set_address_name(2.into(), "2".into());
// when // when
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap(); ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap();
// then // then
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
@ -515,6 +532,7 @@ mod tests {
// given // given
let ap = AccountProvider::transient_provider(); let ap = AccountProvider::transient_provider();
let address = ap.new_account("test").unwrap(); let address = ap.new_account("test").unwrap();
ap.set_address_name(1.into(), "1".into());
// When returning nothing // When returning nothing
ap.set_new_dapps_whitelist(Some(vec![])).unwrap(); ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
@ -524,6 +542,10 @@ mod tests {
ap.set_new_dapps_whitelist(None).unwrap(); ap.set_new_dapps_whitelist(None).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]); assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
// change to non-existent account
ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
// change to a whitelist // change to a whitelist
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]); assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);

View File

@ -19,7 +19,7 @@
use std::{fs, fmt, hash, ops}; use std::{fs, fmt, hash, ops};
use std::sync::atomic::{self, AtomicUsize}; use std::sync::atomic::{self, AtomicUsize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use ethstore::ethkey::Address; use ethstore::ethkey::Address;
use ethjson::misc::{ use ethjson::misc::{
@ -37,9 +37,9 @@ pub struct AddressBook {
impl AddressBook { impl AddressBook {
/// Creates new address book at given directory. /// Creates new address book at given directory.
pub fn new(path: String) -> Self { pub fn new(path: &Path) -> Self {
let mut r = AddressBook { let mut r = AddressBook {
cache: DiskMap::new(path, "address_book.json".into()) cache: DiskMap::new(path, "address_book.json")
}; };
r.cache.revert(AccountMeta::read); r.cache.revert(AccountMeta::read);
r r
@ -200,11 +200,11 @@ pub struct DappsSettingsStore {
impl DappsSettingsStore { impl DappsSettingsStore {
/// Creates new store at given directory path. /// Creates new store at given directory path.
pub fn new(path: String) -> Self { pub fn new(path: &Path) -> Self {
let mut r = DappsSettingsStore { let mut r = DappsSettingsStore {
settings: DiskMap::new(path.clone(), "dapps_accounts.json".into()), settings: DiskMap::new(path, "dapps_accounts.json".into()),
policy: DiskMap::new(path.clone(), "dapps_policy.json".into()), policy: DiskMap::new(path, "dapps_policy.json".into()),
history: DiskMap::new(path.clone(), "dapps_history.json".into()), history: DiskMap::new(path, "dapps_history.json".into()),
time: TimeProvider::Clock, time: TimeProvider::Clock,
}; };
r.settings.revert(JsonSettings::read); r.settings.revert(JsonSettings::read);
@ -297,9 +297,8 @@ impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
} }
impl<K: hash::Hash + Eq, V> DiskMap<K, V> { impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
pub fn new(path: String, file_name: String) -> Self { pub fn new(path: &Path, file_name: &str) -> Self {
trace!(target: "diskmap", "new({})", path); let mut path = path.to_owned();
let mut path: PathBuf = path.into();
path.push(file_name); path.push(file_name);
trace!(target: "diskmap", "path={:?}", path); trace!(target: "diskmap", "path={:?}", path);
DiskMap { DiskMap {
@ -310,7 +309,7 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
} }
pub fn transient() -> Self { pub fn transient() -> Self {
let mut map = DiskMap::new(Default::default(), "diskmap.json".into()); let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into());
map.transient = true; map.transient = true;
map map
} }
@ -354,27 +353,25 @@ mod tests {
#[test] #[test]
fn should_save_and_reload_address_book() { fn should_save_and_reload_address_book() {
let temp = RandomTempPath::create_dir(); let path = RandomTempPath::create_dir();
let path = temp.as_str().to_owned(); let mut b = AddressBook::new(&path);
let mut b = AddressBook::new(path.clone());
b.set_name(1.into(), "One".to_owned()); b.set_name(1.into(), "One".to_owned());
b.set_meta(1.into(), "{1:1}".to_owned()); b.set_meta(1.into(), "{1:1}".to_owned());
let b = AddressBook::new(path); let b = AddressBook::new(&path);
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
} }
#[test] #[test]
fn should_remove_address() { fn should_remove_address() {
let temp = RandomTempPath::create_dir(); let path = RandomTempPath::create_dir();
let path = temp.as_str().to_owned(); let mut b = AddressBook::new(&path);
let mut b = AddressBook::new(path.clone());
b.set_name(1.into(), "One".to_owned()); b.set_name(1.into(), "One".to_owned());
b.set_name(2.into(), "Two".to_owned()); b.set_name(2.into(), "Two".to_owned());
b.set_name(3.into(), "Three".to_owned()); b.set_name(3.into(), "Three".to_owned());
b.remove(2.into()); b.remove(2.into());
let b = AddressBook::new(path); let b = AddressBook::new(&path);
assert_eq!(b.get(), hash_map![ assert_eq!(b.get(), hash_map![
1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}, 1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None},
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None} 3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
@ -384,15 +381,14 @@ mod tests {
#[test] #[test]
fn should_save_and_reload_dapps_settings() { fn should_save_and_reload_dapps_settings() {
// given // given
let temp = RandomTempPath::create_dir(); let path = RandomTempPath::create_dir();
let path = temp.as_str().to_owned(); let mut b = DappsSettingsStore::new(&path);
let mut b = DappsSettingsStore::new(path.clone());
// when // when
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
// then // then
let b = DappsSettingsStore::new(path); let b = DappsSettingsStore::new(&path);
assert_eq!(b.settings(), hash_map![ assert_eq!(b.settings(), hash_map![
"dappOne".into() => DappsSettings { "dappOne".into() => DappsSettings {
accounts: vec![1.into(), 2.into()], accounts: vec![1.into(), 2.into()],
@ -422,9 +418,8 @@ mod tests {
#[test] #[test]
fn should_store_dapps_policy() { fn should_store_dapps_policy() {
// given // given
let temp = RandomTempPath::create_dir(); let path = RandomTempPath::create_dir();
let path = temp.as_str().to_owned(); let mut store = DappsSettingsStore::new(&path);
let mut store = DappsSettingsStore::new(path.clone());
// Test default policy // Test default policy
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts); assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
@ -433,7 +428,7 @@ mod tests {
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])); store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
// then // then
let store = DappsSettingsStore::new(path); let store = DappsSettingsStore::new(&path);
assert_eq!(store.policy.clone(), hash_map![ assert_eq!(store.policy.clone(), hash_map![
"default".into() => NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]) "default".into() => NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])
]); ]);

View File

@ -24,6 +24,8 @@ pub struct BestBlock {
pub hash: H256, pub hash: H256,
/// Best block number. /// Best block number.
pub number: BlockNumber, pub number: BlockNumber,
/// Best block timestamp.
pub timestamp: u64,
/// Best block total difficulty. /// Best block total difficulty.
pub total_difficulty: U256, pub total_difficulty: U256,
/// Best block uncompressed bytes /// Best block uncompressed bytes

View File

@ -485,6 +485,7 @@ impl BlockChain {
let best_block_number = bc.block_number(&best_block_hash).unwrap(); let best_block_number = bc.block_number(&best_block_hash).unwrap();
let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty; let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty;
let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner(); let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner();
let best_block_timestamp = BlockView::new(&best_block_rlp).header().timestamp();
let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec()); let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec());
let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h)); let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h));
@ -533,6 +534,7 @@ impl BlockChain {
number: best_block_number, number: best_block_number,
total_difficulty: best_block_total_difficulty, total_difficulty: best_block_total_difficulty,
hash: best_block_hash, hash: best_block_hash,
timestamp: best_block_timestamp,
block: best_block_rlp, block: best_block_rlp,
}; };
@ -585,6 +587,7 @@ impl BlockChain {
number: extras.number - 1, number: extras.number - 1,
total_difficulty: best_block_total_difficulty, total_difficulty: best_block_total_difficulty,
hash: hash, hash: hash,
timestamp: BlockView::new(&best_block_rlp).header().timestamp(),
block: best_block_rlp, block: best_block_rlp,
}; };
// update parent extras // update parent extras
@ -738,6 +741,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info, info: info,
timestamp: header.timestamp(),
block: bytes block: bytes
}, is_best); }, is_best);
@ -786,6 +790,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info, info: info,
timestamp: header.timestamp(),
block: bytes, block: bytes,
}, is_best); }, is_best);
true true
@ -850,6 +855,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info.clone(), info: info.clone(),
timestamp: header.timestamp(),
block: bytes, block: bytes,
}, true); }, true);
@ -921,6 +927,7 @@ impl BlockChain {
hash: update.info.hash, hash: update.info.hash,
number: update.info.number, number: update.info.number,
total_difficulty: update.info.total_difficulty, total_difficulty: update.info.total_difficulty,
timestamp: update.timestamp,
block: update.block.to_vec(), block: update.block.to_vec(),
}); });
}, },
@ -1206,6 +1213,11 @@ impl BlockChain {
self.best_block.read().number self.best_block.read().number
} }
/// Get best block timestamp.
pub fn best_block_timestamp(&self) -> u64 {
self.best_block.read().timestamp
}
/// Get best block total difficulty. /// Get best block total difficulty.
pub fn best_block_total_difficulty(&self) -> U256 { pub fn best_block_total_difficulty(&self) -> U256 {
self.best_block.read().total_difficulty self.best_block.read().total_difficulty
@ -1293,6 +1305,7 @@ impl BlockChain {
genesis_hash: self.genesis_hash(), genesis_hash: self.genesis_hash(),
best_block_hash: best_block.hash.clone(), best_block_hash: best_block.hash.clone(),
best_block_number: best_block.number, best_block_number: best_block.number,
best_block_timestamp: best_block.timestamp,
first_block_hash: self.first_block(), first_block_hash: self.first_block(),
first_block_number: From::from(self.first_block_number()), first_block_number: From::from(self.first_block_number()),
ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()), ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()),

View File

@ -9,6 +9,8 @@ use super::extras::{BlockDetails, BlockReceipts, TransactionAddress, LogGroupPos
pub struct ExtrasUpdate<'a> { pub struct ExtrasUpdate<'a> {
/// Block info. /// Block info.
pub info: BlockInfo, pub info: BlockInfo,
/// Block timestamp.
pub timestamp: u64,
/// Current block uncompressed rlp bytes /// Current block uncompressed rlp bytes
pub block: &'a [u8], pub block: &'a [u8],
/// Modified block hashes. /// Modified block hashes.

View File

@ -1406,7 +1406,11 @@ impl BlockChainClient for Client {
} }
fn ready_transactions(&self) -> Vec<PendingTransaction> { fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain.read().best_block_number()) let (number, timestamp) = {
let chain = self.chain.read();
(chain.best_block_number(), chain.best_block_timestamp())
};
self.miner.ready_transactions(number, timestamp)
} }
fn queue_consensus_message(&self, message: Bytes) { fn queue_consensus_message(&self, message: Bytes) {

View File

@ -669,12 +669,14 @@ impl BlockChainClient for TestBlockChainClient {
} }
fn chain_info(&self) -> BlockChainInfo { fn chain_info(&self) -> BlockChainInfo {
let number = self.blocks.read().len() as BlockNumber - 1;
BlockChainInfo { BlockChainInfo {
total_difficulty: *self.difficulty.read(), total_difficulty: *self.difficulty.read(),
pending_total_difficulty: *self.difficulty.read(), pending_total_difficulty: *self.difficulty.read(),
genesis_hash: self.genesis_hash.clone(), genesis_hash: self.genesis_hash.clone(),
best_block_hash: self.last_hash.read().clone(), best_block_hash: self.last_hash.read().clone(),
best_block_number: self.blocks.read().len() as BlockNumber - 1, best_block_number: number,
best_block_timestamp: number,
first_block_hash: self.first_block.read().as_ref().map(|x| x.0), first_block_hash: self.first_block.read().as_ref().map(|x| x.0),
first_block_number: self.first_block.read().as_ref().map(|x| x.1), first_block_number: self.first_block.read().as_ref().map(|x| x.1),
ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0), ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0),
@ -709,7 +711,8 @@ impl BlockChainClient for TestBlockChainClient {
} }
fn ready_transactions(&self) -> Vec<PendingTransaction> { fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain_info().best_block_number) let info = self.chain_info();
self.miner.ready_transactions(info.best_block_number, info.best_block_timestamp)
} }
fn signing_network_id(&self) -> Option<u64> { None } fn signing_network_id(&self) -> Option<u64> { None }

View File

@ -159,7 +159,7 @@ impl IoHandler<()> for TransitionHandler {
fn initialize(&self, io: &IoContext<()>) { fn initialize(&self, io: &IoContext<()>) {
if let Some(engine) = self.engine.upgrade() { if let Some(engine) = self.engine.upgrade() {
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis())
.unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) .unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e))
} }
} }
@ -168,7 +168,7 @@ impl IoHandler<()> for TransitionHandler {
if let Some(engine) = self.engine.upgrade() { if let Some(engine) = self.engine.upgrade() {
engine.step(); engine.step();
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis())
.unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) .unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e))
} }
} }
} }
@ -234,14 +234,14 @@ impl Engine for AuthorityRound {
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(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: "poa", "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);
return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&H520::from(signature) as &[u8])).to_vec()]); return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&H520::from(signature) as &[u8])).to_vec()]);
} else { } else {
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); warn!(target: "engine", "generate_seal: FAIL: Accounts secret key unavailable.");
} }
} else { } else {
trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); trace!(target: "engine", "generate_seal: Not a proposer for step {}.", step);
} }
Seal::None Seal::None
} }
@ -260,7 +260,7 @@ impl Engine for AuthorityRound {
/// Check the number of seal fields. /// Check the number of seal fields.
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
if header.seal().len() != self.seal_fields() { if header.seal().len() != self.seal_fields() {
trace!(target: "poa", "verify_block_basic: wrong number of seal fields"); trace!(target: "engine", "verify_block_basic: wrong number of seal fields");
Err(From::from(BlockError::InvalidSealArity( Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: header.seal().len() } Mismatch { expected: self.seal_fields(), found: header.seal().len() }
))) )))
@ -279,11 +279,11 @@ impl Engine for AuthorityRound {
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
Ok(()) Ok(())
} else { } else {
trace!(target: "poa", "verify_block_unordered: bad proposer for step: {}", header_step); trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
} }
} else { } else {
trace!(target: "poa", "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)?
} }
@ -297,7 +297,7 @@ impl Engine for AuthorityRound {
let step = header_step(header)?; 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: "poa", "Multiple blocks proposed for step {}.", step); trace!(target: "engine", "Multiple blocks proposed for step {}.", step);
self.validators.report_malicious(header.author()); self.validators.report_malicious(header.author());
Err(EngineError::DoubleVote(header.author().clone()))?; Err(EngineError::DoubleVote(header.author().clone()))?;
} }

View File

@ -159,13 +159,13 @@ impl Tendermint {
let message = ConsensusMessage::new(signature, h, r, *s, block_hash); let message = ConsensusMessage::new(signature, h, r, *s, block_hash);
let validator = self.signer.address(); let validator = self.signer.address();
self.votes.vote(message.clone(), &validator); self.votes.vote(message.clone(), &validator);
debug!(target: "poa", "Generated {:?} as {}.", message, validator); debug!(target: "engine", "Generated {:?} as {}.", message, validator);
self.handle_valid_message(&message); self.handle_valid_message(&message);
Some(message_rlp) Some(message_rlp)
}, },
Err(e) => { Err(e) => {
trace!(target: "poa", "Could not sign the message {}", e); trace!(target: "engine", "Could not sign the message {}", e);
None None
}, },
} }
@ -186,7 +186,7 @@ impl Tendermint {
fn to_next_height(&self, height: Height) { fn to_next_height(&self, height: Height) {
let new_height = height + 1; let new_height = height + 1;
debug!(target: "poa", "Received a Commit, transitioning to height {}.", new_height); debug!(target: "engine", "Received a Commit, transitioning to height {}.", new_height);
self.last_lock.store(0, AtomicOrdering::SeqCst); self.last_lock.store(0, AtomicOrdering::SeqCst);
self.height.store(new_height, AtomicOrdering::SeqCst); self.height.store(new_height, AtomicOrdering::SeqCst);
self.view.store(0, AtomicOrdering::SeqCst); self.view.store(0, AtomicOrdering::SeqCst);
@ -196,7 +196,7 @@ impl Tendermint {
/// Use via step_service to transition steps. /// Use via step_service to transition steps.
fn to_step(&self, step: Step) { fn to_step(&self, step: Step) {
if let Err(io_err) = self.step_service.send_message(step) { if let Err(io_err) = self.step_service.send_message(step) {
warn!(target: "poa", "Could not proceed to step {}.", io_err) warn!(target: "engine", "Could not proceed to step {}.", io_err)
} }
*self.step.write() = step; *self.step.write() = step;
match step { match step {
@ -212,10 +212,10 @@ impl Tendermint {
self.generate_and_broadcast_message(block_hash); self.generate_and_broadcast_message(block_hash);
}, },
Step::Precommit => { Step::Precommit => {
trace!(target: "poa", "to_step: Precommit."); trace!(target: "engine", "to_step: Precommit.");
let block_hash = match *self.lock_change.read() { let block_hash = match *self.lock_change.read() {
Some(ref m) if self.is_view(m) && m.block_hash.is_some() => { Some(ref m) if self.is_view(m) && m.block_hash.is_some() => {
trace!(target: "poa", "Setting last lock: {}", m.vote_step.view); trace!(target: "engine", "Setting last lock: {}", m.vote_step.view);
self.last_lock.store(m.vote_step.view, AtomicOrdering::SeqCst); self.last_lock.store(m.vote_step.view, AtomicOrdering::SeqCst);
m.block_hash m.block_hash
}, },
@ -224,7 +224,7 @@ impl Tendermint {
self.generate_and_broadcast_message(block_hash); self.generate_and_broadcast_message(block_hash);
}, },
Step::Commit => { Step::Commit => {
trace!(target: "poa", "to_step: Commit."); trace!(target: "engine", "to_step: Commit.");
// Commit the block using a complete signature set. // Commit the block using a complete signature set.
let view = self.view.load(AtomicOrdering::SeqCst); let view = self.view.load(AtomicOrdering::SeqCst);
let height = self.height.load(AtomicOrdering::SeqCst); let height = self.height.load(AtomicOrdering::SeqCst);
@ -234,7 +234,7 @@ impl Tendermint {
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) {
trace!(target: "poa", "Collected seal: {:?}", seal); trace!(target: "engine", "Collected seal: {:?}", seal);
let seal = vec![ let seal = vec![
::rlp::encode(&view).to_vec(), ::rlp::encode(&view).to_vec(),
::rlp::encode(&seal.proposal).to_vec(), ::rlp::encode(&seal.proposal).to_vec(),
@ -243,7 +243,7 @@ impl Tendermint {
self.submit_seal(block_hash, seal); self.submit_seal(block_hash, seal);
self.to_next_height(height); self.to_next_height(height);
} else { } else {
warn!(target: "poa", "Not enough votes found!"); warn!(target: "engine", "Not enough votes found!");
} }
} }
} }
@ -262,7 +262,7 @@ impl Tendermint {
/// 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, height: Height, view: View) -> Address {
let proposer_nonce = height + view; let proposer_nonce = height + view;
trace!(target: "poa", "Proposer nonce: {}", proposer_nonce); trace!(target: "engine", "Proposer nonce: {}", proposer_nonce);
self.validators.get(proposer_nonce) self.validators.get(proposer_nonce)
} }
@ -291,7 +291,7 @@ impl Tendermint {
} }
fn increment_view(&self, n: View) { fn increment_view(&self, n: View) {
trace!(target: "poa", "increment_view: New view."); trace!(target: "engine", "increment_view: New view.");
self.view.fetch_add(n, AtomicOrdering::SeqCst); self.view.fetch_add(n, AtomicOrdering::SeqCst);
} }
@ -331,7 +331,7 @@ impl Tendermint {
&& message.block_hash.is_some() && message.block_hash.is_some()
&& self.has_enough_aligned_votes(message); && self.has_enough_aligned_votes(message);
if lock_change { if lock_change {
trace!(target: "poa", "handle_valid_message: Lock change."); trace!(target: "engine", "handle_valid_message: Lock change.");
*self.lock_change.write() = Some(message.clone()); *self.lock_change.write() = Some(message.clone());
} }
// Check if it can affect the step transition. // Check if it can affect the step transition.
@ -349,7 +349,7 @@ impl Tendermint {
self.increment_view(vote_step.view - self.view.load(AtomicOrdering::SeqCst)); self.increment_view(vote_step.view - self.view.load(AtomicOrdering::SeqCst));
Some(Step::Precommit) Some(Step::Precommit)
}, },
// Avoid counting twice. // Avoid counting votes twice.
Step::Prevote if lock_change => Some(Step::Precommit), Step::Prevote if lock_change => Some(Step::Precommit),
Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit), Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit),
Step::Prevote if self.has_enough_future_step_votes(&vote_step) => { Step::Prevote if self.has_enough_future_step_votes(&vote_step) => {
@ -360,7 +360,7 @@ impl Tendermint {
}; };
if let Some(step) = next_step { if let Some(step) = next_step {
trace!(target: "poa", "Transition to {:?} triggered.", step); trace!(target: "engine", "Transition to {:?} triggered.", step);
self.to_step(step); self.to_step(step);
} }
} }
@ -429,7 +429,7 @@ impl Engine for Tendermint {
let vote_info = message_info_rlp(&VoteStep::new(height, view, Step::Propose), bh.clone()); let vote_info = message_info_rlp(&VoteStep::new(height, view, Step::Propose), bh.clone());
if let Ok(signature) = self.signer.sign(vote_info.sha3()).map(Into::into) { if let Ok(signature) = self.signer.sign(vote_info.sha3()).map(Into::into) {
// Insert Propose vote. // Insert Propose vote.
debug!(target: "poa", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view); debug!(target: "engine", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view);
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;
@ -439,7 +439,7 @@ impl Engine for Tendermint {
::rlp::EMPTY_LIST_RLP.to_vec() ::rlp::EMPTY_LIST_RLP.to_vec()
]) ])
} else { } else {
warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); warn!(target: "engine", "generate_seal: FAIL: accounts secret key unavailable");
Seal::None Seal::None
} }
} }
@ -457,7 +457,7 @@ impl Engine for Tendermint {
self.validators.report_malicious(&sender); self.validators.report_malicious(&sender);
Err(EngineError::DoubleVote(sender))? Err(EngineError::DoubleVote(sender))?
} }
trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender); trace!(target: "engine", "Handling a valid {:?} from {}.", message, sender);
self.handle_valid_message(&message); self.handle_valid_message(&message);
} }
Ok(()) Ok(())
@ -519,7 +519,7 @@ impl Engine for Tendermint {
if origins.insert(address) { if origins.insert(address) {
signature_count += 1; signature_count += 1;
} else { } else {
warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); warn!(target: "engine", "verify_block_unordered: Duplicate signature from {} on the seal.", address);
Err(BlockError::InvalidSeal)?; Err(BlockError::InvalidSeal)?;
} }
} }
@ -577,12 +577,12 @@ impl Engine for Tendermint {
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
if signatures_len != 1 { if signatures_len != 1 {
// New Commit received, skip to next height. // New Commit received, skip to next height.
trace!(target: "poa", "Received a commit: {:?}.", proposal.vote_step); trace!(target: "engine", "Received a commit: {:?}.", proposal.vote_step);
self.to_next_height(proposal.vote_step.height); self.to_next_height(proposal.vote_step.height);
return false; return false;
} }
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
debug!(target: "poa", "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();
} }
@ -594,7 +594,7 @@ impl Engine for Tendermint {
fn step(&self) { fn step(&self) {
let next_step = match *self.step.read() { let next_step = match *self.step.read() {
Step::Propose => { Step::Propose => {
trace!(target: "poa", "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.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
@ -603,26 +603,26 @@ impl Engine for Tendermint {
Step::Prevote Step::Prevote
}, },
Step::Prevote if self.has_enough_any_votes() => { Step::Prevote if self.has_enough_any_votes() => {
trace!(target: "poa", "Prevote timeout."); trace!(target: "engine", "Prevote timeout.");
Step::Precommit Step::Precommit
}, },
Step::Prevote => { Step::Prevote => {
trace!(target: "poa", "Prevote timeout without enough votes."); trace!(target: "engine", "Prevote timeout without enough votes.");
self.broadcast_old_messages(); self.broadcast_old_messages();
Step::Prevote Step::Prevote
}, },
Step::Precommit if self.has_enough_any_votes() => { Step::Precommit if self.has_enough_any_votes() => {
trace!(target: "poa", "Precommit timeout."); trace!(target: "engine", "Precommit timeout.");
self.increment_view(1); self.increment_view(1);
Step::Propose Step::Propose
}, },
Step::Precommit => { Step::Precommit => {
trace!(target: "poa", "Precommit timeout without enough votes."); trace!(target: "engine", "Precommit timeout without enough votes.");
self.broadcast_old_messages(); self.broadcast_old_messages();
Step::Precommit Step::Precommit
}, },
Step::Commit => { Step::Commit => {
trace!(target: "poa", "Commit timeout."); trace!(target: "engine", "Commit timeout.");
Step::Propose Step::Propose
}, },
}; };
@ -838,7 +838,6 @@ mod tests {
let (b, seal) = propose_default(&spec, proposer); let (b, seal) = propose_default(&spec, proposer);
assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok()); assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok());
spec.engine.stop();
} }
#[test] #[test]
@ -850,7 +849,6 @@ mod tests {
let (b, seal) = propose_default(&spec, proposer); let (b, seal) = propose_default(&spec, proposer);
let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap(); let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap();
assert!(spec.engine.is_proposal(sealed.header())); assert!(spec.engine.is_proposal(sealed.header()));
spec.engine.stop();
} }
#[test] #[test]
@ -858,7 +856,7 @@ mod tests {
let (spec, tap) = setup(); let (spec, tap) = setup();
let engine = spec.engine.clone(); let engine = spec.engine.clone();
let v0 = insert_and_register(&tap, engine.as_ref(), "0"); let v0 = insert_and_unlock(&tap, "0");
let v1 = insert_and_register(&tap, engine.as_ref(), "1"); let v1 = insert_and_register(&tap, engine.as_ref(), "1");
let h = 1; let h = 1;
@ -883,7 +881,6 @@ mod tests {
assert!(notify.messages.read().contains(&prevote_current)); assert!(notify.messages.read().contains(&prevote_current));
assert!(notify.messages.read().contains(&precommit_current)); assert!(notify.messages.read().contains(&precommit_current));
assert!(notify.messages.read().contains(&prevote_future)); assert!(notify.messages.read().contains(&prevote_future));
engine.stop();
} }
#[test] #[test]
@ -933,7 +930,5 @@ mod tests {
// Last precommit. // Last precommit.
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
assert_eq!(client.chain_info().best_block_number, 1); assert_eq!(client.chain_info().best_block_number, 1);
engine.stop();
} }
} }

View File

@ -56,7 +56,9 @@ fn set_timeout<S: Sync + Send + Clone>(io: &IoContext<S>, timeout: Duration) {
impl <S> IoHandler<S> for TransitionHandler<S> where S: Sync + Send + Clone + 'static { impl <S> IoHandler<S> for TransitionHandler<S> where S: Sync + Send + Clone + 'static {
fn initialize(&self, io: &IoContext<S>) { fn initialize(&self, io: &IoContext<S>) {
set_timeout(io, self.timeouts.initial()); let initial = self.timeouts.initial();
trace!(target: "engine", "Setting the initial timeout to {}.", initial);
set_timeout(io, initial);
} }
/// Call step after timeout. /// Call step after timeout.

View File

@ -118,13 +118,13 @@ impl <M: Message + Default + Encodable + Debug> VoteCollector<M> {
.get(&message.round()) .get(&message.round())
.map_or(false, |c| { .map_or(false, |c| {
let is_known = c.messages.contains(message); let is_known = c.messages.contains(message);
if is_known { trace!(target: "poa", "Known message: {:?}.", message); } if is_known { trace!(target: "engine", "Known message: {:?}.", message); }
is_known is_known
}) })
|| { || {
let guard = self.votes.read(); let guard = self.votes.read();
let is_old = guard.keys().next().map_or(true, |oldest| message.round() <= oldest); let is_old = guard.keys().next().map_or(true, |oldest| message.round() <= oldest);
if is_old { trace!(target: "poa", "Old message {:?}.", message); } if is_old { trace!(target: "engine", "Old message {:?}.", message); }
is_old is_old
} }
} }

View File

@ -25,7 +25,7 @@ use client::TransactionImportResult;
use executive::contract_address; use executive::contract_address;
use block::{ClosedBlock, IsBlock, Block}; use block::{ClosedBlock, IsBlock, Block};
use error::*; use error::*;
use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction}; use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction, Condition as TransactionCondition};
use receipt::{Receipt, RichReceipt}; use receipt::{Receipt, RichReceipt};
use spec::Spec; use spec::Spec;
use engines::{Engine, Seal}; use engines::{Engine, Seal};
@ -325,7 +325,7 @@ impl Miner {
let _timer = PerfTimer::new("prepare_block"); let _timer = PerfTimer::new("prepare_block");
let chain_info = chain.chain_info(); let chain_info = chain.chain_info();
let (transactions, mut open_block, original_work_hash) = { let (transactions, mut open_block, original_work_hash) = {
let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number)}; let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp)};
let mut sealing_work = self.sealing_work.lock(); let mut sealing_work = self.sealing_work.lock();
let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash());
let best_hash = chain_info.best_block_hash; let best_hash = chain_info.best_block_hash;
@ -597,7 +597,7 @@ impl Miner {
client: &MiningBlockChainClient, client: &MiningBlockChainClient,
transactions: Vec<UnverifiedTransaction>, transactions: Vec<UnverifiedTransaction>,
default_origin: TransactionOrigin, default_origin: TransactionOrigin,
min_block: Option<BlockNumber>, condition: Option<TransactionCondition>,
transaction_queue: &mut BanningTransactionQueue, transaction_queue: &mut BanningTransactionQueue,
) -> Vec<Result<TransactionImportResult, Error>> { ) -> Vec<Result<TransactionImportResult, Error>> {
let accounts = self.accounts.as_ref() let accounts = self.accounts.as_ref()
@ -635,7 +635,7 @@ impl Miner {
let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action); let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action);
match origin { match origin {
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
transaction_queue.add(transaction, origin, insertion_time, min_block, &details_provider) transaction_queue.add(transaction, origin, insertion_time, condition.clone(), &details_provider)
}, },
TransactionOrigin::External => { TransactionOrigin::External => {
transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider) transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider)
@ -892,7 +892,7 @@ impl MinerService for Miner {
let mut transaction_queue = self.transaction_queue.lock(); let mut transaction_queue = self.transaction_queue.lock();
// We need to re-validate transactions // We need to re-validate transactions
let import = self.add_transactions_to_queue( let import = self.add_transactions_to_queue(
chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.min_block, &mut transaction_queue chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.condition, &mut transaction_queue
).pop().expect("one result returned per added transaction; one added => one result; qed"); ).pop().expect("one result returned per added transaction; one added => one result; qed");
match import { match import {
@ -927,7 +927,7 @@ impl MinerService for Miner {
fn pending_transactions(&self) -> Vec<PendingTransaction> { fn pending_transactions(&self) -> Vec<PendingTransaction> {
let queue = self.transaction_queue.lock(); let queue = self.transaction_queue.lock();
queue.pending_transactions(BlockNumber::max_value()) queue.pending_transactions(BlockNumber::max_value(), u64::max_value())
} }
fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> { fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> {
@ -942,14 +942,14 @@ impl MinerService for Miner {
self.transaction_queue.lock().future_transactions() self.transaction_queue.lock().future_transactions()
} }
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction> {
let queue = self.transaction_queue.lock(); let queue = self.transaction_queue.lock();
match self.options.pending_set { match self.options.pending_set {
PendingSet::AlwaysQueue => queue.pending_transactions(best_block), PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp),
PendingSet::SealingOrElseQueue => { PendingSet::SealingOrElseQueue => {
self.from_pending_block( self.from_pending_block(
best_block, best_block,
|| queue.pending_transactions(best_block), || queue.pending_transactions(best_block, best_block_timestamp),
|sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect()
) )
}, },
@ -1325,7 +1325,7 @@ mod tests {
// then // then
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.ready_transactions(best_block).len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 1);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1);
assert_eq!(miner.pending_receipts(best_block).len(), 1); assert_eq!(miner.pending_receipts(best_block).len(), 1);
// This method will let us know if pending block was created (before calling that method) // This method will let us know if pending block was created (before calling that method)
@ -1345,7 +1345,7 @@ mod tests {
// then // then
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0);
assert_eq!(miner.pending_receipts(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0);
} }
@ -1364,7 +1364,7 @@ mod tests {
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0);
assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0);
assert_eq!(miner.pending_receipts(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0);
// This method will let us know if pending block was created (before calling that method) // This method will let us know if pending block was created (before calling that method)
assert!(miner.prepare_work_sealing(&client)); assert!(miner.prepare_work_sealing(&client));

View File

@ -154,7 +154,7 @@ pub trait MinerService : Send + Sync {
fn pending_transactions(&self) -> Vec<PendingTransaction>; fn pending_transactions(&self) -> Vec<PendingTransaction>;
/// Get a list of all transactions that can go into the given block. /// Get a list of all transactions that can go into the given block.
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>; fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction>;
/// Get a list of all future transactions. /// Get a list of all future transactions.
fn future_transactions(&self) -> Vec<PendingTransaction>; fn future_transactions(&self) -> Vec<PendingTransaction>;

View File

@ -276,17 +276,17 @@ struct VerifiedTransaction {
origin: TransactionOrigin, origin: TransactionOrigin,
/// Insertion time /// Insertion time
insertion_time: QueuingInstant, insertion_time: QueuingInstant,
/// Delay until specifid block. /// Delay until specified condition is met.
min_block: Option<BlockNumber>, condition: Option<Condition>,
} }
impl VerifiedTransaction { impl VerifiedTransaction {
fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option<BlockNumber>) -> Self { fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, condition: Option<Condition>) -> Self {
VerifiedTransaction { VerifiedTransaction {
transaction: transaction, transaction: transaction,
origin: origin, origin: origin,
insertion_time: time, insertion_time: time,
min_block: min_block, condition: condition,
} }
} }
@ -666,14 +666,14 @@ impl TransactionQueue {
tx: SignedTransaction, tx: SignedTransaction,
origin: TransactionOrigin, origin: TransactionOrigin,
time: QueuingInstant, time: QueuingInstant,
min_block: Option<BlockNumber>, condition: Option<Condition>,
details_provider: &TransactionDetailsProvider, details_provider: &TransactionDetailsProvider,
) -> Result<TransactionImportResult, Error> { ) -> Result<TransactionImportResult, Error> {
if origin == TransactionOrigin::Local { if origin == TransactionOrigin::Local {
let hash = tx.hash(); let hash = tx.hash();
let cloned_tx = tx.clone(); let cloned_tx = tx.clone();
let result = self.add_internal(tx, origin, time, min_block, details_provider); let result = self.add_internal(tx, origin, time, condition, details_provider);
match result { match result {
Ok(TransactionImportResult::Current) => { Ok(TransactionImportResult::Current) => {
self.local_transactions.mark_pending(hash); self.local_transactions.mark_pending(hash);
@ -694,7 +694,7 @@ impl TransactionQueue {
} }
result result
} else { } else {
self.add_internal(tx, origin, time, min_block, details_provider) self.add_internal(tx, origin, time, condition, details_provider)
} }
} }
@ -704,7 +704,7 @@ impl TransactionQueue {
tx: SignedTransaction, tx: SignedTransaction,
origin: TransactionOrigin, origin: TransactionOrigin,
time: QueuingInstant, time: QueuingInstant,
min_block: Option<BlockNumber>, condition: Option<Condition>,
details_provider: &TransactionDetailsProvider, details_provider: &TransactionDetailsProvider,
) -> Result<TransactionImportResult, Error> { ) -> Result<TransactionImportResult, Error> {
if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price { if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price {
@ -815,7 +815,7 @@ impl TransactionQueue {
} }
tx.check_low_s()?; tx.check_low_s()?;
// No invalid transactions beyond this point. // No invalid transactions beyond this point.
let vtx = VerifiedTransaction::new(tx, origin, time, min_block); let vtx = VerifiedTransaction::new(tx, origin, time, condition);
let r = self.import_tx(vtx, client_account.nonce).map_err(Error::Transaction); let r = self.import_tx(vtx, client_account.nonce).map_err(Error::Transaction);
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
r r
@ -1068,11 +1068,11 @@ impl TransactionQueue {
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions(&self) -> Vec<SignedTransaction> { pub fn top_transactions(&self) -> Vec<SignedTransaction> {
self.top_transactions_at(BlockNumber::max_value()) self.top_transactions_at(BlockNumber::max_value(), u64::max_value())
} }
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F) fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F)
where F: FnMut(&VerifiedTransaction) { where F: FnMut(&VerifiedTransaction) {
let mut delayed = HashSet::new(); let mut delayed = HashSet::new();
@ -1082,7 +1082,12 @@ impl TransactionQueue {
if delayed.contains(&sender) { if delayed.contains(&sender) {
continue; continue;
} }
if tx.min_block.unwrap_or(0) > best_block { let delay = match tx.condition {
Some(Condition::Number(n)) => n > best_block,
Some(Condition::Timestamp(t)) => t > best_timestamp,
None => false,
};
if delay {
delayed.insert(sender); delayed.insert(sender);
continue; continue;
} }
@ -1091,16 +1096,16 @@ impl TransactionQueue {
} }
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions_at(&self, best_block: BlockNumber) -> Vec<SignedTransaction> { pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<SignedTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(tx.transaction.clone()));
r r
} }
/// Return all ready transactions. /// Return all ready transactions.
pub fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone())));
r r
} }
@ -1109,7 +1114,7 @@ impl TransactionQueue {
self.future.by_priority self.future.by_priority
.iter() .iter()
.map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`"))
.map(|t| PendingTransaction { transaction: t.transaction.clone(), min_block: t.min_block }) .map(|t| PendingTransaction { transaction: t.transaction.clone(), condition: t.condition.clone() })
.collect() .collect()
} }
@ -1382,7 +1387,7 @@ pub mod test {
use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; use super::{TransactionSet, TransactionOrder, VerifiedTransaction};
use miner::local_transactions::LocalTransactionsList; use miner::local_transactions::LocalTransactionsList;
use client::TransactionImportResult; use client::TransactionImportResult;
use transaction::{SignedTransaction, Transaction, Action}; use transaction::{SignedTransaction, Transaction, Action, Condition};
pub struct DummyTransactionDetailsProvider { pub struct DummyTransactionDetailsProvider {
account_details: AccountDetails, account_details: AccountDetails,
@ -2178,15 +2183,15 @@ pub mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when // when
let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(1), &default_tx_provider()).unwrap(); let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(Condition::Number(1)), &default_tx_provider()).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap();
// then // then
assert_eq!(res1, TransactionImportResult::Current); assert_eq!(res1, TransactionImportResult::Current);
assert_eq!(res2, TransactionImportResult::Current); assert_eq!(res2, TransactionImportResult::Current);
let top = txq.top_transactions_at(0); let top = txq.top_transactions_at(0, 0);
assert_eq!(top.len(), 0); assert_eq!(top.len(), 0);
let top = txq.top_transactions_at(1); let top = txq.top_transactions_at(1, 0);
assert_eq!(top.len(), 2); assert_eq!(top.len(), 2);
} }

View File

@ -29,7 +29,7 @@ use spec::Spec;
use views::BlockView; use views::BlockView;
use util::stats::Histogram; use util::stats::Histogram;
use ethkey::{KeyPair, Secret}; use ethkey::{KeyPair, Secret};
use transaction::{PendingTransaction, Transaction, Action}; use transaction::{PendingTransaction, Transaction, Action, Condition};
use miner::MinerService; use miner::MinerService;
#[test] #[test]
@ -299,7 +299,7 @@ fn does_not_propagate_delayed_transactions() {
action: Action::Call(Address::default()), action: Action::Call(Address::default()),
value: 0.into(), value: 0.into(),
data: Vec::new(), data: Vec::new(),
}.sign(secret, None), Some(2)); }.sign(secret, None), Some(Condition::Number(2)));
let tx1 = PendingTransaction::new(Transaction { let tx1 = PendingTransaction::new(Transaction {
nonce: 1.into(), nonce: 1.into(),
gas_price: 0.into(), gas_price: 0.into(),

View File

@ -34,6 +34,8 @@ pub struct BlockChainInfo {
pub best_block_hash: H256, pub best_block_hash: H256,
/// Best blockchain block number. /// Best blockchain block number.
pub best_block_number: BlockNumber, pub best_block_number: BlockNumber,
/// Best blockchain block timestamp.
pub best_block_timestamp: u64,
/// Best ancient block hash. /// Best ancient block hash.
pub ancient_block_hash: Option<H256>, pub ancient_block_hash: Option<H256>,
/// Best ancient block number. /// Best ancient block number.

View File

@ -52,6 +52,16 @@ impl Decodable for Action {
} }
} }
/// Transaction activation condition.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "ipc", binary)]
pub enum Condition {
/// Valid at this block number or later.
Number(BlockNumber),
/// Valid at this unix time or later.
Timestamp(u64),
}
/// A set of information describing an externally-originating message call /// A set of information describing an externally-originating message call
/// or contract creation operation. /// or contract creation operation.
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
@ -448,16 +458,16 @@ impl Deref for LocalizedTransaction {
pub struct PendingTransaction { pub struct PendingTransaction {
/// Signed transaction data. /// Signed transaction data.
pub transaction: SignedTransaction, pub transaction: SignedTransaction,
/// To be activated at this block. `None` for immediately. /// To be activated at this condition. `None` for immediately.
pub min_block: Option<BlockNumber>, pub condition: Option<Condition>,
} }
impl PendingTransaction { impl PendingTransaction {
/// Create a new pending transaction from signed transaction. /// Create a new pending transaction from signed transaction.
pub fn new(signed: SignedTransaction, min_block: Option<BlockNumber>) -> Self { pub fn new(signed: SignedTransaction, condition: Option<Condition>) -> Self {
PendingTransaction { PendingTransaction {
transaction: signed, transaction: signed,
min_block: min_block, condition: condition,
} }
} }
} }
@ -466,7 +476,7 @@ impl From<SignedTransaction> for PendingTransaction {
fn from(t: SignedTransaction) -> Self { fn from(t: SignedTransaction) -> Self {
PendingTransaction { PendingTransaction {
transaction: t, transaction: t,
min_block: None, condition: None,
} }
} }
} }

View File

@ -16,6 +16,7 @@
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::mem; use std::mem;
use std::path::PathBuf;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use crypto::KEY_ITERATIONS; use crypto::KEY_ITERATIONS;
@ -164,8 +165,8 @@ impl SecretStore for EthStore {
self.store.update(account_ref, old, safe_account) self.store.update(account_ref, old, safe_account)
} }
fn local_path(&self) -> String { fn local_path(&self) -> PathBuf {
self.store.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) self.store.dir.path().cloned().unwrap_or_else(PathBuf::new)
} }
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> { fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {

View File

@ -15,6 +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::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use ethkey::{Address, Message, Signature, Secret, Public}; use ethkey::{Address, Message, Signature, Secret, Public};
use Error; use Error;
use json::Uuid; use json::Uuid;
@ -73,7 +74,7 @@ pub trait SecretStore: SimpleSecretStore {
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>; fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>; fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
fn local_path(&self) -> String; fn local_path(&self) -> PathBuf;
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>; fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>; fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.3.58", "version": "0.3.62",
"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>",

View File

@ -16,11 +16,12 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import chalk from 'chalk';
import { isPlainObject } from 'lodash'; import { isPlainObject } from 'lodash';
import { info, warn, error } from './helpers/log';
import { Dummy } from '../src/jsonrpc/helpers'; import { Dummy } from '../src/jsonrpc/helpers';
import interfaces from '../src/jsonrpc'; import interfaces from '../src/jsonrpc';
import rustMethods from './helpers/parsed-rpc-traits';
const ROOT_DIR = path.join(__dirname, '../docs'); const ROOT_DIR = path.join(__dirname, '../docs');
@ -28,20 +29,13 @@ if (!fs.existsSync(ROOT_DIR)) {
fs.mkdirSync(ROOT_DIR); fs.mkdirSync(ROOT_DIR);
} }
// INFO Logging helper Object.keys(rustMethods).forEach((group) => {
function info (log) { Object.keys(rustMethods[group]).forEach((method) => {
console.log(chalk.blue(`INFO:\t${log}`)); if (interfaces[group] == null || interfaces[group][method] == null) {
} error(`${group}_${method} is defined in Rust traits, but not in js/src/jsonrpc/interfaces`);
}
// WARN Logging helper });
function warn (log) { });
console.warn(chalk.yellow(`WARN:\t${log}`));
}
// ERROR Logging helper
function error (log) {
console.error(chalk.red(`ERROR:\t${log}`));
}
function printType (type) { function printType (type) {
return type.print || `\`${type.name}\``; return type.print || `\`${type.name}\``;
@ -291,7 +285,8 @@ Object.keys(interfaces).sort().forEach((group) => {
Object.keys(spec).sort(methodComparator).forEach((iname) => { Object.keys(spec).sort(methodComparator).forEach((iname) => {
const method = spec[iname]; const method = spec[iname];
const name = `${group.replace(/_.*$/, '')}_${iname}`; const groupName = group.replace(/_.*$/, '');
const name = `${groupName}_${iname}`;
if (method.nodoc || method.deprecated) { if (method.nodoc || method.deprecated) {
info(`Skipping ${name}: ${method.nodoc || 'Deprecated'}`); info(`Skipping ${name}: ${method.nodoc || 'Deprecated'}`);
@ -299,6 +294,10 @@ Object.keys(interfaces).sort().forEach((group) => {
return; return;
} }
if (rustMethods[groupName] == null || rustMethods[groupName][iname] == null) {
error(`${name} is defined in js/src/jsonrpc/interfaces, but not in Rust traits`);
}
const desc = method.desc; const desc = method.desc;
const params = buildParameters(method.params); const params = buildParameters(method.params);
const returns = `- ${formatType(method.returns)}`; const returns = `- ${formatType(method.returns)}`;

32
js/scripts/helpers/log.js Normal file
View File

@ -0,0 +1,32 @@
// 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 chalk from 'chalk';
// INFO Logging helper
export function info (log) {
console.log(chalk.blue(`INFO:\t${log}`));
}
// WARN Logging helper
export function warn (log) {
console.warn(chalk.yellow(`WARN:\t${log}`));
}
// ERROR Logging helper
export function error (log) {
console.error(chalk.red(`ERROR:\t${log}`));
}

View File

@ -0,0 +1,81 @@
// 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 fs from 'fs';
import path from 'path';
// ```js
// rustMethods['eth']['call'] === true
// ```
const rustMethods = {};
export default rustMethods;
// Get a list of JSON-RPC from Rust trait source code
function parseMethodsFromRust (source) {
// Matching the custom `rpc` attribute with it's doc comment
const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm;
const commentPattern = /\s*\/\/\/\s*/g;
const separatorPattern = /\s*,\s*/g;
const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/;
const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i;
const methods = [];
source.toString().replace(attributePattern, (match, comment, props) => {
comment = comment.replace(commentPattern, '\n').trim();
// Skip deprecated methods
if (ignorePattern.test(comment)) {
return match;
}
props.split(separatorPattern).forEach((prop) => {
const [, key, value] = prop.split(assignPattern) || [];
if (key === 'name' && value != null) {
methods.push(value);
}
});
return match;
});
return methods;
}
// Get a list of all JSON-RPC methods from all defined traits
function getMethodsFromRustTraits () {
const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits');
return fs.readdirSync(traitsDir)
.filter((name) => name !== 'mod.rs' && /\.rs$/.test(name))
.map((name) => fs.readFileSync(path.join(traitsDir, name)))
.map(parseMethodsFromRust)
.reduce((a, b) => a.concat(b));
}
getMethodsFromRustTraits().sort().forEach((method) => {
const [group, name] = method.split('_');
// Skip methods with malformed names
if (group == null || name == null) {
return;
}
rustMethods[group] = rustMethods[group] || {};
rustMethods[group][name] = true;
});

View File

@ -23,6 +23,7 @@ export default class Eth {
this._started = false; this._started = false;
this._lastBlock = new BigNumber(-1); this._lastBlock = new BigNumber(-1);
this._pollTimerId = null;
} }
get isStarted () { get isStarted () {
@ -37,7 +38,7 @@ export default class Eth {
_blockNumber = () => { _blockNumber = () => {
const nextTimeout = (timeout = 1000) => { const nextTimeout = (timeout = 1000) => {
setTimeout(() => { this._pollTimerId = setTimeout(() => {
this._blockNumber(); this._blockNumber();
}, timeout); }, timeout);
}; };
@ -57,6 +58,6 @@ export default class Eth {
nextTimeout(); nextTimeout();
}) })
.catch(nextTimeout); .catch(() => nextTimeout());
} }
} }

View File

@ -20,6 +20,9 @@ export default class Personal {
this._api = api; this._api = api;
this._updateSubscriptions = updateSubscriptions; this._updateSubscriptions = updateSubscriptions;
this._started = false; this._started = false;
this._lastDefaultAccount = '0x0';
this._pollTimerId = null;
} }
get isStarted () { get isStarted () {
@ -37,12 +40,35 @@ export default class Personal {
]); ]);
} }
_defaultAccount = () => { // FIXME: Because of the different API instances, the "wait for valid changes" approach
// doesn't work. Since the defaultAccount is critical to operation, we poll in exactly
// same way we do in ../eth (ala same as eth_blockNumber) and update. This should be moved
// to pub-sub as it becomes available
_defaultAccount = (timerDisabled = false) => {
const nextTimeout = (timeout = 1000) => {
if (!timerDisabled) {
this._pollTimerId = setTimeout(() => {
this._defaultAccount();
}, timeout);
}
};
if (!this._api.transport.isConnected) {
nextTimeout(500);
return;
}
return this._api.parity return this._api.parity
.defaultAccount() .defaultAccount()
.then((defaultAccount) => { .then((defaultAccount) => {
this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); if (this._lastDefaultAccount !== defaultAccount) {
}); this._lastDefaultAccount = defaultAccount;
this._updateSubscriptions('parity_defaultAccount', null, defaultAccount);
}
nextTimeout();
})
.catch(() => nextTimeout());
} }
_listAccounts = () => { _listAccounts = () => {
@ -54,14 +80,20 @@ export default class Personal {
} }
_accountsInfo = () => { _accountsInfo = () => {
return Promise return this._api.parity
.all([ .accountsInfo()
this._api.parity.accountsInfo(), .then((info) => {
this._api.parity.allAccountsInfo()
])
.then(([info, allInfo]) => {
this._updateSubscriptions('parity_accountsInfo', null, info); this._updateSubscriptions('parity_accountsInfo', null, info);
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
return this._api.parity
.allAccountsInfo()
.catch(() => {
// NOTE: This fails on non-secure APIs, swallow error
return {};
})
.then((allInfo) => {
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
});
}); });
} }
@ -89,7 +121,7 @@ export default class Personal {
case 'parity_setDappsAddresses': case 'parity_setDappsAddresses':
case 'parity_setNewDappsWhitelist': case 'parity_setNewDappsWhitelist':
this._defaultAccount(); this._defaultAccount(true);
return; return;
} }
}); });

View File

@ -36,6 +36,9 @@ function stubApi (accounts, info) {
return { return {
_calls, _calls,
transport: {
isConnected: true
},
parity: { parity: {
accountsInfo: () => { accountsInfo: () => {
const stub = sinon.stub().resolves(info || TEST_INFO)(); const stub = sinon.stub().resolves(info || TEST_INFO)();

View File

@ -20,23 +20,23 @@ export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]); return contract.instance.certified.call({}, [account]);
}; };
export const checkIfRequested = (contract, account) => { export const findLastRequested = (contract, account) => {
let subId = null; let subId = null;
let resolved = false; let resolved = false;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
contract contract
.subscribe('Requested', { .subscribe('Requested', {
fromBlock: 0, toBlock: 'pending' fromBlock: 0,
toBlock: 'pending',
limit: 1,
topics: [account]
}, (err, logs) => { }, (err, logs) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
const e = logs.find((l) => {
return l.type === 'mined' && l.params.who && l.params.who.value === account;
});
resolve(e ? e.transactionHash : false); resolve(logs[0] || null);
resolved = true; resolved = true;
if (subId) { if (subId) {

View File

@ -45,7 +45,7 @@ export default class Application extends Component {
} }
componentDidMount () { componentDidMount () {
this.attachInstance(); return this.attachInstance();
} }
render () { render () {
@ -80,12 +80,12 @@ export default class Application extends Component {
} }
attachInstance () { attachInstance () {
Promise return Promise
.all([ .all([
attachInstances(), api.parity.accountsInfo(),
api.parity.accountsInfo() attachInstances()
]) ])
.then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { .then(([accountsInfo, { managerInstance, registryInstance, tokenregInstance }]) => {
accountsInfo = accountsInfo || {}; accountsInfo = accountsInfo || {};
this.setState({ this.setState({
loading: false, loading: false,

View File

@ -17,7 +17,6 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { api } from '../../parity'; import { api } from '../../parity';
import AddressSelect from '../../AddressSelect';
import Container from '../../Container'; import Container from '../../Container';
import styles from './deployment.css'; import styles from './deployment.css';
@ -122,36 +121,13 @@ export default class Deployment extends Component {
} }
renderForm () { renderForm () {
const { accounts } = this.context;
const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
const hasError = !!(nameError || tlaError || totalSupplyError); const hasError = !!(nameError || tlaError || totalSupplyError);
const error = `${styles.input} ${styles.error}`; const error = `${styles.input} ${styles.error}`;
const addresses = Object.keys(accounts);
// <div className={ styles.input }>
// <label>global registration</label>
// <select onChange={ this.onChangeRegistrar }>
// <option value='no'>No, only for me</option>
// <option value='yes'>Yes, for everybody</option>
// </select>
// <div className={ styles.hint }>
// register on network (fee: { globalFeeText }ETH)
// </div>
// </div>
return ( return (
<Container> <Container>
<div className={ styles.form }> <div className={ styles.form }>
<div className={ styles.input }>
<label>deployment account</label>
<AddressSelect
addresses={ addresses }
onChange={ this.onChangeFrom }
/>
<div className={ styles.hint }>
the owner account to deploy from
</div>
</div>
<div className={ nameError ? error : styles.input }> <div className={ nameError ? error : styles.input }>
<label>token name</label> <label>token name</label>
<input <input
@ -206,12 +182,6 @@ export default class Deployment extends Component {
); );
} }
onChangeFrom = (event) => {
const fromAddress = event.target.value;
this.setState({ fromAddress });
}
onChangeName = (event) => { onChangeName = (event) => {
const name = event.target.value; const name = event.target.value;
const nameError = name && (name.length > 2) && (name.length < 32) const nameError = name && (name.length > 2) && (name.length < 32)
@ -271,7 +241,7 @@ export default class Deployment extends Component {
onDeploy = () => { onDeploy = () => {
const { managerInstance, registryInstance, tokenregInstance } = this.context; const { managerInstance, registryInstance, tokenregInstance } = this.context;
const { base, deployBusy, fromAddress, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const { base, deployBusy, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
const hasError = !!(nameError || tlaError || totalSupplyError); const hasError = !!(nameError || tlaError || totalSupplyError);
if (hasError || deployBusy) { if (hasError || deployBusy) {
@ -281,14 +251,18 @@ export default class Deployment extends Component {
const tokenreg = (globalReg ? tokenregInstance : registryInstance).address; const tokenreg = (globalReg ? tokenregInstance : registryInstance).address;
const values = [base.mul(totalSupply), tla, name, tokenreg]; const values = [base.mul(totalSupply), tla, name, tokenreg];
const options = { const options = {
from: fromAddress,
value: globalReg ? globalFee : 0 value: globalReg ? globalFee : 0
}; };
this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' }); this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' });
managerInstance return api.parity
.deploy.estimateGas(options, values) .defaultAccount()
.then((defaultAddress) => {
options.from = defaultAddress;
return managerInstance.deploy.estimateGas(options, values);
})
.then((gas) => { .then((gas) => {
this.setState({ deployState: 'Gas estimated, Posting transaction to the network' }); this.setState({ deployState: 'Gas estimated, Posting transaction to the network' });

View File

@ -25,6 +25,8 @@ let registryInstance;
const registries = {}; const registries = {};
const subscriptions = {}; const subscriptions = {};
let defaultSubscriptionId;
let nextSubscriptionId = 1000; let nextSubscriptionId = 1000;
let isTest = false; let isTest = false;
@ -65,6 +67,20 @@ export function unsubscribeEvents (subscriptionId) {
delete subscriptions[subscriptionId]; delete subscriptions[subscriptionId];
} }
export function subscribeDefaultAddress (callback) {
return api
.subscribe('parity_defaultAccount', callback)
.then((subscriptionId) => {
defaultSubscriptionId = subscriptionId;
return defaultSubscriptionId;
});
}
export function unsubscribeDefaultAddress () {
return api.unsubscribe(defaultSubscriptionId);
}
function pollEvents () { function pollEvents () {
const loop = Object.values(subscriptions); const loop = Object.values(subscriptions);
const timeout = () => setTimeout(pollEvents, 1000); const timeout = () => setTimeout(pollEvents, 1000);

View File

@ -17,10 +17,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { api } from '../parity'; import { api } from '../parity';
import { attachInterface } from '../services'; import { attachInterface, subscribeDefaultAddress, unsubscribeDefaultAddress } from '../services';
import Button from '../Button'; import Button from '../Button';
import Events from '../Events'; import Events from '../Events';
import IdentityIcon from '../IdentityIcon';
import Loading from '../Loading'; import Loading from '../Loading';
import styles from './application.css'; import styles from './application.css';
@ -32,7 +31,7 @@ let nextEventId = 0;
export default class Application extends Component { export default class Application extends Component {
state = { state = {
fromAddress: null, defaultAddress: null,
loading: true, loading: true,
url: '', url: '',
urlError: null, urlError: null,
@ -47,19 +46,32 @@ export default class Application extends Component {
registerType: 'file', registerType: 'file',
repo: '', repo: '',
repoError: null, repoError: null,
subscriptionId: null,
events: {}, events: {},
eventIds: [] eventIds: []
} }
componentDidMount () { componentDidMount () {
attachInterface() return Promise
.then((state) => { .all([
this.setState(state, () => { attachInterface(),
this.setState({ loading: false }); subscribeDefaultAddress((error, defaultAddress) => {
}); if (!error) {
this.setState({ defaultAddress });
}
})
])
.then(([state]) => {
this.setState(Object.assign({}, state, {
loading: false
}));
}); });
} }
componentWillUnmount () {
return unsubscribeDefaultAddress();
}
render () { render () {
const { loading } = this.state; const { loading } = this.state;
@ -75,12 +87,14 @@ export default class Application extends Component {
} }
renderPage () { renderPage () {
const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state; const { defaultAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state;
let hashClass = null; let hashClass = null;
if (contentHashError) { if (contentHashError) {
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning; hashClass = contentHashOwner !== defaultAddress
? styles.hashError
: styles.hashWarning;
} else if (contentHash) { } else if (contentHash) {
hashClass = styles.hashOk; hashClass = styles.hashOk;
} }
@ -166,20 +180,13 @@ export default class Application extends Component {
} }
renderButtons () { renderButtons () {
const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state; const { defaultAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
const account = accounts[fromAddress];
return ( return (
<div className={ styles.buttons }> <div className={ styles.buttons }>
<div className={ styles.addressSelect }>
<Button invert onClick={ this.onSelectFromAddress }>
<IdentityIcon address={ account.address } />
<div>{ account.name || account.address }</div>
</Button>
</div>
<Button <Button
onClick={ this.onClickRegister } onClick={ this.onClickRegister }
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError } disabled={ (contentHashError && contentHashOwner !== defaultAddress) || urlError || repoError || commitError }
>register url</Button> >register url</Button>
</div> </div>
); );
@ -294,11 +301,11 @@ export default class Application extends Component {
} }
onClickRegister = () => { onClickRegister = () => {
const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state; const { defaultAddress, commit, commitError, contentHashError, contentHashOwner, url, urlError, registerType, repo, repoError } = this.state;
// TODO: No errors are currently set, validation to be expanded and added for each // TODO: No errors are currently set, validation to be expanded and added for each
// field (query is fast to pick up the issues, so not burning atm) // field (query is fast to pick up the issues, so not burning atm)
if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) { if ((contentHashError && contentHashOwner !== defaultAddress) || repoError || urlError || commitError) {
return; return;
} }
@ -368,13 +375,15 @@ export default class Application extends Component {
} }
registerContent (contentRepo, contentCommit) { registerContent (contentRepo, contentCommit) {
const { contentHash, fromAddress, instance } = this.state; const { defaultAddress, contentHash, instance } = this.state;
contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`; contentCommit = contentCommit.substr(0, 2) === '0x'
? contentCommit
: `0x${contentCommit}`;
const eventId = nextEventId++; const eventId = nextEventId++;
const values = [contentHash, contentRepo, contentCommit]; const values = [contentHash, contentRepo, contentCommit];
const options = { from: fromAddress }; const options = { from: defaultAddress };
this.setState({ this.setState({
eventIds: [eventId].concat(this.state.eventIds), eventIds: [eventId].concat(this.state.eventIds),
@ -383,7 +392,7 @@ export default class Application extends Component {
contentHash, contentHash,
contentRepo, contentRepo,
contentCommit, contentCommit,
fromAddress, defaultAddress,
registerBusy: true, registerBusy: true,
registerState: 'Estimating gas for the transaction', registerState: 'Estimating gas for the transaction',
timestamp: new Date() timestamp: new Date()
@ -421,11 +430,11 @@ export default class Application extends Component {
} }
registerUrl (contentUrl) { registerUrl (contentUrl) {
const { contentHash, fromAddress, instance } = this.state; const { contentHash, defaultAddress, instance } = this.state;
const eventId = nextEventId++; const eventId = nextEventId++;
const values = [contentHash, contentUrl]; const values = [contentHash, contentUrl];
const options = { from: fromAddress }; const options = { from: defaultAddress };
this.setState({ this.setState({
eventIds: [eventId].concat(this.state.eventIds), eventIds: [eventId].concat(this.state.eventIds),
@ -433,7 +442,7 @@ export default class Application extends Component {
[eventId]: { [eventId]: {
contentHash, contentHash,
contentUrl, contentUrl,
fromAddress, defaultAddress,
registerBusy: true, registerBusy: true,
registerState: 'Estimating gas for the transaction', registerState: 'Estimating gas for the transaction',
timestamp: new Date() timestamp: new Date()
@ -470,25 +479,6 @@ export default class Application extends Component {
); );
} }
onSelectFromAddress = () => {
const { accounts, fromAddress } = this.state;
const addresses = Object.keys(accounts);
let index = 0;
addresses.forEach((address, _index) => {
if (address === fromAddress) {
index = _index;
}
});
index++;
if (index >= addresses.length) {
index = 0;
}
this.setState({ fromAddress: addresses[index] });
}
lookupHash (url) { lookupHash (url) {
const { instance } = this.state; const { instance } = this.state;

View File

@ -17,48 +17,44 @@
import * as abis from '~/contracts/abi'; import * as abis from '~/contracts/abi';
import { api } from './parity'; import { api } from './parity';
let defaultSubscriptionId;
export function attachInterface () { export function attachInterface () {
return api.parity return api.parity
.registryAddress() .registryAddress()
.then((registryAddress) => { .then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`); console.log(`the registry was found at ${registryAddress}`);
const registry = api.newContract(abis.registry, registryAddress).instance; return api
.newContract(abis.registry, registryAddress).instance
return Promise .getAddress.call({}, [api.util.sha3('githubhint'), 'A']);
.all([
registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']),
api.parity.accountsInfo()
]);
}) })
.then(([address, accountsInfo]) => { .then((address) => {
console.log(`githubhint was found at ${address}`); console.log(`githubhint was found at ${address}`);
const contract = api.newContract(abis.githubhint, address); const contract = api.newContract(abis.githubhint, address);
const accounts = Object
.keys(accountsInfo)
.reduce((obj, address) => {
const account = accountsInfo[address];
return Object.assign(obj, {
[address]: {
address,
name: account.name
}
});
}, {});
const fromAddress = Object.keys(accounts)[0];
return { return {
accounts,
address, address,
accountsInfo,
contract, contract,
instance: contract.instance, instance: contract.instance
fromAddress
}; };
}) })
.catch((error) => { .catch((error) => {
console.error('attachInterface', error); console.error('attachInterface', error);
}); });
} }
export function subscribeDefaultAddress (callback) {
return api
.subscribe('parity_defaultAccount', callback)
.then((subscriptionId) => {
defaultSubscriptionId = subscriptionId;
return defaultSubscriptionId;
});
}
export function unsubscribeDefaultAddress () {
return api.unsubscribe(defaultSubscriptionId);
}

View File

@ -22,7 +22,7 @@ import { api } from '../parity';
import styles from './transaction.css'; import styles from './transaction.css';
import IdentityIcon from '../../githubhint/IdentityIcon'; import IdentityIcon from '../IdentityIcon';
class BaseTransaction extends Component { class BaseTransaction extends Component {
shortHash (hash) { shortHash (hash) {

View File

@ -82,6 +82,7 @@ export const ownerLookup = (name) => (dispatch, getState) => {
return; return;
} }
name = name.toLowerCase();
dispatch(ownerLookupStart(name)); dispatch(ownerLookupStart(name));
return getOwner(contract, name) return getOwner(contract, name)

View File

@ -30,7 +30,6 @@ export default class Application extends Component {
state = { state = {
accounts: {}, accounts: {},
address: null, address: null,
fromAddress: null,
accountsInfo: {}, accountsInfo: {},
blockNumber: new BigNumber(0), blockNumber: new BigNumber(0),
contract: null, contract: null,
@ -41,11 +40,9 @@ export default class Application extends Component {
} }
componentDidMount () { componentDidMount () {
attachInterface() return attachInterface()
.then((state) => { .then((state) => {
this.setState(state, () => { this.setState(Object.assign({}, state, { loading: false }));
this.setState({ loading: false });
});
return attachBlockNumber(state.instance, (state) => { return attachBlockNumber(state.instance, (state) => {
this.setState(state); this.setState(state);
@ -86,17 +83,14 @@ export default class Application extends Component {
} }
renderImport () { renderImport () {
const { accounts, fromAddress, instance, showImport } = this.state; const { instance, showImport } = this.state;
if (showImport) { if (showImport) {
return ( return (
<Import <Import
accounts={ accounts }
fromAddress={ fromAddress }
instance={ instance } instance={ instance }
visible={ showImport } visible={ showImport }
onClose={ this.toggleImport } onClose={ this.toggleImport }
onSetFromAddress={ this.setFromAddress }
/> />
); );
} }
@ -124,10 +118,4 @@ export default class Application extends Component {
showImport: !this.state.showImport showImport: !this.state.showImport
}); });
} }
setFromAddress = (fromAddress) => {
this.setState({
fromAddress
});
}
} }

View File

@ -19,18 +19,14 @@ import React, { Component, PropTypes } from 'react';
import { api } from '../parity'; import { api } from '../parity';
import { callRegister, postRegister } from '../services'; import { callRegister, postRegister } from '../services';
import Button from '../Button'; import Button from '../Button';
import IdentityIcon from '../IdentityIcon';
import styles from './import.css'; import styles from './import.css';
export default class Import extends Component { export default class Import extends Component {
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired,
fromAddress: PropTypes.string.isRequired,
instance: PropTypes.object.isRequired, instance: PropTypes.object.isRequired,
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired
onSetFromAddress: PropTypes.func.isRequired
} }
state = { state = {
@ -83,21 +79,12 @@ export default class Import extends Component {
} }
renderRegister () { renderRegister () {
const { accounts, fromAddress } = this.props;
const account = accounts[fromAddress];
const count = this.countFunctions(); const count = this.countFunctions();
let buttons = null; let buttons = null;
if (count) { if (count) {
buttons = ( buttons = (
<div className={ styles.buttonrow }> <div className={ styles.buttonrow }>
<div className={ styles.addressSelect }>
<Button invert onClick={ this.onSelectFromAddress }>
<IdentityIcon address={ account.address } />
<div>{ account.name || account.address }</div>
</Button>
</div>
<Button onClick={ this.onRegister }> <Button onClick={ this.onRegister }>
register functions register functions
</Button> </Button>
@ -197,15 +184,15 @@ export default class Import extends Component {
} }
onRegister = () => { onRegister = () => {
const { instance, fromAddress, onClose } = this.props; const { instance, onClose } = this.props;
const { functions, fnstate } = this.state; const { functions, fnstate } = this.state;
Promise return Promise
.all( .all(
functions functions
.filter((fn) => !fn.constant) .filter((fn) => !fn.constant)
.filter((fn) => fnstate[fn.signature] === 'fntodo') .filter((fn) => fnstate[fn.signature] === 'fntodo')
.map((fn) => postRegister(instance, fn.id, { from: fromAddress })) .map((fn) => postRegister(instance, fn.id, {}))
) )
.then(() => { .then(() => {
onClose(); onClose();
@ -214,23 +201,4 @@ export default class Import extends Component {
console.error('onRegister', error); console.error('onRegister', error);
}); });
} }
onSelectFromAddress = () => {
const { accounts, fromAddress, onSetFromAddress } = this.props;
const addresses = Object.keys(accounts);
let index = 0;
addresses.forEach((address, _index) => {
if (address === fromAddress) {
index = _index;
}
});
index++;
if (index >= addresses.length) {
index = 0;
}
onSetFromAddress(addresses[index]);
}
} }

View File

@ -166,8 +166,13 @@ export function callRegister (instance, id, options = {}) {
} }
export function postRegister (instance, id, options = {}) { export function postRegister (instance, id, options = {}) {
return instance.register return api.parity
.estimateGas(options, [id]) .defaultAccount()
.then((defaultAddress) => {
options.from = defaultAddress;
return instance.register.estimateGas(options, [id]);
})
.then((gas) => { .then((gas) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`); console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`);

View File

@ -14,8 +14,6 @@
// 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 fs from 'fs';
import path from 'path';
import interfaces from './'; import interfaces from './';
import * as customTypes from './types'; import * as customTypes from './types';
@ -27,91 +25,13 @@ function verifyType (obj) {
} }
} }
// Get a list of JSON-RPC from Rust trait source code
function parseMethodsFromRust (source) {
// Matching the custom `rpc` attribute with it's doc comment
const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm;
const commentPattern = /\s*\/\/\/\s*/g;
const separatorPattern = /\s*,\s*/g;
const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/;
const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i;
const methods = [];
source.toString().replace(attributePattern, (match, comment, props) => {
comment = comment.replace(commentPattern, '\n').trim();
// Skip deprecated methods
if (ignorePattern.test(comment)) {
return match;
}
props.split(separatorPattern).forEach((prop) => {
const [, key, value] = prop.split(assignPattern) || [];
if (key === 'name' && value != null) {
methods.push(value);
}
});
return match;
});
return methods;
}
// Get a list of all JSON-RPC methods from all defined traits
function getMethodsFromRustTraits () {
const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits');
return fs.readdirSync(traitsDir)
.filter((name) => name !== 'mod.rs' && /\.rs$/.test(name))
.map((name) => fs.readFileSync(path.join(traitsDir, name)))
.map(parseMethodsFromRust)
.reduce((a, b) => a.concat(b));
}
const rustMethods = {};
getMethodsFromRustTraits().sort().forEach((method) => {
const [group, name] = method.split('_');
// Skip methods with malformed names
if (group == null || name == null) {
return;
}
rustMethods[group] = rustMethods[group] || {};
rustMethods[group][name] = true;
});
describe('jsonrpc/interfaces', () => { describe('jsonrpc/interfaces', () => {
describe('Rust trait methods', () => {
Object.keys(rustMethods).forEach((group) => {
describe(group, () => {
Object.keys(rustMethods[group]).forEach((name) => {
describe(name, () => {
it('has a defined JS interface', () => {
expect(interfaces[group][name]).to.exist;
});
});
});
});
});
});
Object.keys(interfaces).forEach((group) => { Object.keys(interfaces).forEach((group) => {
describe(group, () => { describe(group, () => {
Object.keys(interfaces[group]).forEach((name) => { Object.keys(interfaces[group]).forEach((name) => {
const method = interfaces[group][name]; const method = interfaces[group][name];
describe(name, () => { describe(name, () => {
if (!method.nodoc) {
it('is present in Rust codebase', () => {
expect(rustMethods[group][name]).to.exist;
});
}
it('has the correct interface', () => { it('has the correct interface', () => {
expect(method.desc).to.be.a('string'); expect(method.desc).to.be.a('string');
expect(method.params).to.be.an('array'); expect(method.params).to.be.an('array');

View File

@ -15,15 +15,24 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.modal {
flex-direction: column;
}
.container {
margin-top: 1.5em;
overflow-y: auto;
}
.description { .description {
margin-top: .5em !important; margin-top: .5em !important;
} }
.list { .list {
margin-bottom: 1.5em;
.background { .background {
background: rgba(255, 255, 255, 0.2); padding: 0.5em 0;
margin: 0 -1.5em;
padding: 0.5em 1.5em;
} }
.header { .header {
@ -37,3 +46,26 @@
opacity: 0.75; opacity: 0.75;
} }
} }
.selectIcon {
position: absolute;
right: 0.5em;
top: 0.5em;
}
.selected,
.unselected {
position: relative;
}
.unselected {
background: rgba(0, 0, 0, 0.4) !important;
.selectIcon {
opacity: 0.15;
}
}
.selected {
background: rgba(255, 255, 255, 0.15) !important;
}

View File

@ -14,14 +14,12 @@
// 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 { Checkbox } from 'material-ui';
import { List, ListItem } from 'material-ui/List';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Modal, Button } from '~/ui'; import { ContainerTitle, DappCard, Portal, SectionList } from '~/ui';
import { DoneIcon } from '~/ui/Icons'; import { CheckIcon } from '~/ui/Icons';
import styles from './addDapps.css'; import styles from './addDapps.css';
@ -39,71 +37,63 @@ export default class AddDapps extends Component {
} }
return ( return (
<Modal <Portal
actions={ [ className={ styles.modal }
<Button onClose={ store.closeModal }
icon={ <DoneIcon /> } open
key='done'
label={
<FormattedMessage
id='dapps.add.button.done'
defaultMessage='Done'
/>
}
onClick={ store.closeModal }
/>
] }
compact
title={
<FormattedMessage
id='dapps.add.label'
defaultMessage='visible applications'
/>
}
visible
> >
<div className={ styles.warning } /> <ContainerTitle
{ title={
this.renderList(store.sortedLocal,
<FormattedMessage <FormattedMessage
id='dapps.add.local.label' id='dapps.add.label'
defaultMessage='Applications locally available' defaultMessage='visible applications'
/>,
<FormattedMessage
id='dapps.add.local.desc'
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
/> />
) }
} />
{ <div className={ styles.container }>
this.renderList(store.sortedBuiltin, <div className={ styles.warning } />
<FormattedMessage {
id='dapps.add.builtin.label' this.renderList(store.sortedLocal, store.displayApps,
defaultMessage='Applications bundled with Parity' <FormattedMessage
/>, id='dapps.add.local.label'
<FormattedMessage defaultMessage='Applications locally available'
id='dapps.add.builtin.desc' />,
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.' <FormattedMessage
/> id='dapps.add.local.desc'
) defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
} />
{ )
this.renderList(store.sortedNetwork, }
<FormattedMessage {
id='dapps.add.network.label' this.renderList(store.sortedBuiltin, store.displayApps,
defaultMessage='Applications on the global network' <FormattedMessage
/>, id='dapps.add.builtin.label'
<FormattedMessage defaultMessage='Applications bundled with Parity'
id='dapps.add.network.desc' />,
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.' <FormattedMessage
/> id='dapps.add.builtin.desc'
) defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.'
} />
</Modal> )
}
{
this.renderList(store.sortedNetwork, store.displayApps,
<FormattedMessage
id='dapps.add.network.label'
defaultMessage='Applications on the global network'
/>,
<FormattedMessage
id='dapps.add.network.desc'
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.'
/>
)
}
</div>
</Portal>
); );
} }
renderList (items, header, byline) { renderList (items, visibleItems, header, byline) {
if (!items || !items.length) { if (!items || !items.length) {
return null; return null;
} }
@ -114,41 +104,40 @@ export default class AddDapps extends Component {
<div className={ styles.header }>{ header }</div> <div className={ styles.header }>{ header }</div>
<div className={ styles.byline }>{ byline }</div> <div className={ styles.byline }>{ byline }</div>
</div> </div>
<List> <SectionList
{ items.map(this.renderApp) } items={ items }
</List> noStretch
renderItem={ this.renderApp }
/>
</div> </div>
); );
} }
renderApp = (app) => { renderApp = (app) => {
const { store } = this.props; const { store } = this.props;
const isHidden = !store.displayApps[app.id].visible; const isVisible = store.displayApps[app.id].visible;
const onCheck = () => { const onClick = () => {
if (isHidden) { if (isVisible) {
store.showApp(app.id);
} else {
store.hideApp(app.id); store.hideApp(app.id);
} else {
store.showApp(app.id);
} }
}; };
return ( return (
<ListItem <DappCard
app={ app }
className={
isVisible
? styles.selected
: styles.unselected
}
key={ app.id } key={ app.id }
leftCheckbox={ onClick={ onClick }
<Checkbox >
checked={ !isHidden } <CheckIcon className={ styles.selectIcon } />
onCheck={ onCheck } </DappCard>
/>
}
primaryText={ app.name }
secondaryText={
<div className={ styles.description }>
{ app.description }
</div>
}
/>
); );
} }
} }

View File

@ -33,13 +33,13 @@ describe('modals/AddDapps', () => {
it('does not render the modal with modalOpen = false', () => { it('does not render the modal with modalOpen = false', () => {
expect( expect(
renderShallow({ modalOpen: false }).find('Connect(Modal)') renderShallow({ modalOpen: false }).find('Portal')
).to.have.length(0); ).to.have.length(0);
}); });
it('does render the modal with modalOpen = true', () => { it('does render the modal with modalOpen = true', () => {
expect( expect(
renderShallow({ modalOpen: true }).find('Connect(Modal)') renderShallow({ modalOpen: true }).find('Portal')
).to.have.length(1); ).to.have.length(1);
}); });
}); });

View File

@ -30,6 +30,10 @@
margin-left: .5em; margin-left: .5em;
} }
.field {
margin-bottom: .5em;
}
.terms { .terms {
line-height: 1.3; line-height: 1.3;
opacity: .7; opacity: .7;

View File

@ -31,19 +31,22 @@ import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service'
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works'; import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
import styles from './gatherData.css'; import styles from './gatherData.css';
const boolOfError = PropTypes.oneOfType([ PropTypes.bool, PropTypes.instanceOf(Error) ]);
export default class GatherData extends Component { export default class GatherData extends Component {
static propTypes = { static propTypes = {
fee: React.PropTypes.instanceOf(BigNumber), fee: React.PropTypes.instanceOf(BigNumber),
fields: PropTypes.array.isRequired, fields: PropTypes.array.isRequired,
hasRequested: nullableProptype(PropTypes.bool.isRequired), accountHasRequested: nullableProptype(PropTypes.bool.isRequired),
isServerRunning: nullableProptype(PropTypes.bool.isRequired), isServerRunning: nullableProptype(PropTypes.bool.isRequired),
isVerified: nullableProptype(PropTypes.bool.isRequired), isAbleToRequest: nullableProptype(boolOfError.isRequired),
accountIsVerified: nullableProptype(PropTypes.bool.isRequired),
method: PropTypes.string.isRequired, method: PropTypes.string.isRequired,
setConsentGiven: PropTypes.func.isRequired setConsentGiven: PropTypes.func.isRequired
} }
render () { render () {
const { method, isVerified } = this.props; const { method, accountIsVerified } = this.props;
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService; const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks; const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
@ -55,6 +58,7 @@ export default class GatherData extends Component {
{ this.renderCertified() } { this.renderCertified() }
{ this.renderRequested() } { this.renderRequested() }
{ this.renderFields() } { this.renderFields() }
{ this.renderIfAbleToRequest() }
<Checkbox <Checkbox
className={ styles.spacing } className={ styles.spacing }
label={ label={
@ -63,7 +67,7 @@ export default class GatherData extends Component {
defaultMessage='I agree to the terms and conditions below.' defaultMessage='I agree to the terms and conditions below.'
/> />
} }
disabled={ isVerified } disabled={ accountIsVerified }
onCheck={ this.consentOnChange } onCheck={ this.consentOnChange }
/> />
<div className={ styles.terms }>{ termsOfService }</div> <div className={ styles.terms }>{ termsOfService }</div>
@ -145,27 +149,27 @@ export default class GatherData extends Component {
} }
renderCertified () { renderCertified () {
const { isVerified } = this.props; const { accountIsVerified } = this.props;
if (isVerified) { if (accountIsVerified) {
return ( return (
<div className={ styles.container }> <div className={ styles.container }>
<ErrorIcon /> <ErrorIcon />
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.isVerified.true' id='ui.verification.gatherData.accountIsVerified.true'
defaultMessage='Your account is already verified.' defaultMessage='Your account is already verified.'
/> />
</p> </p>
</div> </div>
); );
} else if (isVerified === false) { } else if (accountIsVerified === false) {
return ( return (
<div className={ styles.container }> <div className={ styles.container }>
<SuccessIcon /> <SuccessIcon />
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.isVerified.false' id='ui.verification.gatherData.accountIsVerified.false'
defaultMessage='Your account is not verified yet.' defaultMessage='Your account is not verified yet.'
/> />
</p> </p>
@ -175,7 +179,7 @@ export default class GatherData extends Component {
return ( return (
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.isVerified.pending' id='ui.verification.gatherData.accountIsVerified.pending'
defaultMessage='Checking if your account is verified…' defaultMessage='Checking if your account is verified…'
/> />
</p> </p>
@ -183,33 +187,33 @@ export default class GatherData extends Component {
} }
renderRequested () { renderRequested () {
const { isVerified, hasRequested } = this.props; const { accountIsVerified, accountHasRequested } = this.props;
// If the account is verified, don't show that it has requested verification. // If the account is verified, don't show that it has requested verification.
if (isVerified) { if (accountIsVerified) {
return null; return null;
} }
if (hasRequested) { if (accountHasRequested) {
return ( return (
<div className={ styles.container }> <div className={ styles.container }>
<InfoIcon /> <InfoIcon />
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.hasRequested.true' id='ui.verification.gatherData.accountHasRequested.true'
defaultMessage='You already requested verification.' defaultMessage='You already requested verification from this account.'
/> />
</p> </p>
</div> </div>
); );
} else if (hasRequested === false) { } else if (accountHasRequested === false) {
return ( return (
<div className={ styles.container }> <div className={ styles.container }>
<SuccessIcon /> <SuccessIcon />
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.hasRequested.false' id='ui.verification.gatherData.accountHasRequested.false'
defaultMessage='You did not request verification yet.' defaultMessage='You did not request verification from this account yet.'
/> />
</p> </p>
</div> </div>
@ -218,7 +222,7 @@ export default class GatherData extends Component {
return ( return (
<p className={ styles.message }> <p className={ styles.message }>
<FormattedMessage <FormattedMessage
id='ui.verification.gatherData.hasRequested.pending' id='ui.verification.gatherData.accountHasRequested.pending'
defaultMessage='Checking if you requested verification…' defaultMessage='Checking if you requested verification…'
/> />
</p> </p>
@ -226,7 +230,7 @@ export default class GatherData extends Component {
} }
renderFields () { renderFields () {
const { isVerified, fields } = this.props; const { accountIsVerified, fields } = this.props;
const rendered = fields.map((field) => { const rendered = fields.map((field) => {
const onChange = (_, v) => { const onChange = (_, v) => {
@ -236,11 +240,12 @@ export default class GatherData extends Component {
return ( return (
<Input <Input
className={ styles.field }
key={ field.key } key={ field.key }
label={ field.label } label={ field.label }
hint={ field.hint } hint={ field.hint }
error={ field.error } error={ field.error }
disabled={ isVerified } disabled={ accountIsVerified }
onChange={ onChange } onChange={ onChange }
onSubmit={ onSubmit } onSubmit={ onSubmit }
/> />
@ -250,6 +255,36 @@ export default class GatherData extends Component {
return (<div>{rendered}</div>); return (<div>{rendered}</div>);
} }
renderIfAbleToRequest () {
const { accountIsVerified, isAbleToRequest } = this.props;
// If the account is verified, don't show a warning.
// If the client is able to send the request, don't show a warning
if (accountIsVerified || isAbleToRequest === true) {
return null;
}
if (isAbleToRequest === null) {
return (
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isAbleToRequest.pending'
defaultMessage='Validating your input…'
/>
</p>
);
} else if (isAbleToRequest) {
return (
<div className={ styles.container }>
<ErrorIcon />
<p className={ styles.message }>
{ isAbleToRequest.message }
</p>
</div>
);
}
}
consentOnChange = (_, consentGiven) => { consentOnChange = (_, consentGiven) => {
this.props.setConsentGiven(consentGiven); this.props.setConsentGiven(consentGiven);
} }

View File

@ -16,6 +16,7 @@
import { observable, computed, action } from 'mobx'; import { observable, computed, action } from 'mobx';
import { sha3 } from '~/api/util/sha3'; import { sha3 } from '~/api/util/sha3';
import { bytesToHex } from '~/api/util/format';
import EmailVerificationABI from '~/contracts/abi/email-verification.json'; import EmailVerificationABI from '~/contracts/abi/email-verification.json';
import VerificationStore, { import VerificationStore, {
@ -23,6 +24,8 @@ import VerificationStore, {
} from './store'; } from './store';
import { isServerRunning, hasReceivedCode, postToServer } from '~/3rdparty/email-verification'; import { isServerRunning, hasReceivedCode, postToServer } from '~/3rdparty/email-verification';
const ZERO20 = '0x0000000000000000000000000000000000000000';
// name in the `BadgeReg.sol` contract // name in the `BadgeReg.sol` contract
const EMAIL_VERIFICATION = 'emailverification'; const EMAIL_VERIFICATION = 'emailverification';
@ -44,9 +47,9 @@ export default class EmailVerificationStore extends VerificationStore {
switch (this.step) { switch (this.step) {
case LOADING: case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
case QUERY_DATA: case QUERY_DATA:
return this.isEmailValid && this.consentGiven; return this.isEmailValid && this.consentGiven && this.isAbleToRequest === true;
case QUERY_CODE: case QUERY_CODE:
return this.requestTx && this.isCodeValid === true; return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION: case POSTED_CONFIRMATION:
@ -68,8 +71,53 @@ export default class EmailVerificationStore extends VerificationStore {
return hasReceivedCode(this.email, this.account, this.isTestnet); return hasReceivedCode(this.email, this.account, this.isTestnet);
} }
// If the email has already been used for verification of another account,
// we prevent the user from wasting ETH to request another verification.
@action setIfAbleToRequest = () => {
const { isEmailValid } = this;
if (!isEmailValid) {
this.isAbleToRequest = true;
return;
}
const { contract, email } = this;
const emailHash = sha3.text(email);
this.isAbleToRequest = null;
contract
.instance.reverse
.call({}, [ emailHash ])
.then((address) => {
if (address === ZERO20) {
this.isAbleToRequest = true;
} else {
this.isAbleToRequest = new Error('Another account has been verified using this e-mail.');
}
})
.catch((err) => {
this.error = 'Failed to check if able to send request: ' + err.message;
});
}
// Determine the values relevant for checking if the last request contains
// the same data as the current one.
requestValues = () => [ sha3.text(this.email) ] requestValues = () => [ sha3.text(this.email) ]
shallSkipRequest = (currentValues) => {
const { accountHasRequested } = this;
const lastRequest = this.lastRequestValues;
if (!accountHasRequested) {
return Promise.resolve(false);
}
// If the last email verification `request` for the selected address contains
// the same email as the current one, don't send another request to save ETH.
const skip = currentValues[0] === bytesToHex(lastRequest.emailHash.value);
return Promise.resolve(skip);
}
@action setEmail = (email) => { @action setEmail = (email) => {
this.email = email; this.email = email;
} }

View File

@ -43,7 +43,7 @@ export default class SMSVerificationStore extends VerificationStore {
switch (this.step) { switch (this.step) {
case LOADING: case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
case QUERY_DATA: case QUERY_DATA:
return this.isNumberValid && this.consentGiven; return this.isNumberValid && this.consentGiven;
case QUERY_CODE: case QUERY_CODE:
@ -67,6 +67,18 @@ export default class SMSVerificationStore extends VerificationStore {
return hasReceivedCode(this.number, this.account, this.isTestnet); return hasReceivedCode(this.number, this.account, this.isTestnet);
} }
// SMS verification events don't contain the phone number, so we will have to
// send a new request every single time. See below.
@action setIfAbleToRequest = () => {
this.isAbleToRequest = true;
}
// SMS verification `request` & `confirm` transactions and events don't contain the
// phone number, so we will have to send a new request every single time. This may
// cost the user more money, but given that it fails otherwise, it seems like a
// reasonable tradeoff.
shallSkipRequest = () => Promise.resolve(false)
@action setNumber = (number) => { @action setNumber = (number) => {
this.number = number; this.number = number;
} }

View File

@ -19,7 +19,7 @@ import { sha3 } from '~/api/util/sha3';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; import { checkIfVerified, findLastRequested, awaitPuzzle } from '~/contracts/verification';
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
export const LOADING = 'fetching-contract'; export const LOADING = 'fetching-contract';
@ -38,8 +38,10 @@ export default class VerificationStore {
@observable contract = null; @observable contract = null;
@observable fee = null; @observable fee = null;
@observable isVerified = null; @observable accountIsVerified = null;
@observable hasRequested = null; @observable accountHasRequested = null;
@observable isAbleToRequest = null;
@observable lastRequestValues = null;
@observable isServerRunning = null; @observable isServerRunning = null;
@observable consentGiven = false; @observable consentGiven = false;
@observable requestTx = null; @observable requestTx = null;
@ -68,6 +70,14 @@ export default class VerificationStore {
console.error('verification: ' + this.error); console.error('verification: ' + this.error);
} }
}); });
autorun(() => {
if (this.step !== QUERY_DATA) {
return;
}
this.setIfAbleToRequest();
});
} }
@action load = () => { @action load = () => {
@ -91,19 +101,20 @@ export default class VerificationStore {
this.error = 'Failed to fetch the fee: ' + err.message; this.error = 'Failed to fetch the fee: ' + err.message;
}); });
const isVerified = checkIfVerified(contract, account) const accountIsVerified = checkIfVerified(contract, account)
.then((isVerified) => { .then((accountIsVerified) => {
this.isVerified = isVerified; this.accountIsVerified = accountIsVerified;
}) })
.catch((err) => { .catch((err) => {
this.error = 'Failed to check if verified: ' + err.message; this.error = 'Failed to check if verified: ' + err.message;
}); });
const hasRequested = checkIfRequested(contract, account) const accountHasRequested = findLastRequested(contract, account)
.then((txHash) => { .then((log) => {
this.hasRequested = !!txHash; this.accountHasRequested = !!log;
if (txHash) { if (log) {
this.requestTx = txHash; this.lastRequestValues = log.params;
this.requestTx = log.transactionHash;
} }
}) })
.catch((err) => { .catch((err) => {
@ -111,7 +122,7 @@ export default class VerificationStore {
}); });
Promise Promise
.all([ isServerRunning, fee, isVerified, hasRequested ]) .all([ isServerRunning, fee, accountIsVerified, accountHasRequested ])
.then(() => { .then(() => {
this.step = QUERY_DATA; this.step = QUERY_DATA;
}); });
@ -150,40 +161,41 @@ export default class VerificationStore {
requestValues = () => [] requestValues = () => []
@action sendRequest = () => { @action sendRequest = () => {
const { api, account, contract, fee, hasRequested } = this; const { api, account, contract, fee } = this;
const request = contract.functions.find((fn) => fn.name === 'request'); const request = contract.functions.find((fn) => fn.name === 'request');
const options = { from: account, value: fee.toString() }; const options = { from: account, value: fee.toString() };
const values = this.requestValues(); const values = this.requestValues();
let chain = Promise.resolve(); this.shallSkipRequest(values)
.then((skipRequest) => {
if (skipRequest) {
return;
}
if (!hasRequested) { this.step = POSTING_REQUEST;
this.step = POSTING_REQUEST; return request.estimateGas(options, values)
chain = request.estimateGas(options, values) .then((gas) => {
.then((gas) => { options.gas = gas.mul(1.2).toFixed(0);
options.gas = gas.mul(1.2).toFixed(0); return request.postTransaction(options, values);
return request.postTransaction(options, values); })
}) .then((handle) => {
.then((handle) => { // The "request rejected" error doesn't have any property to distinguish
// TODO: The "request rejected" error doesn't have any property to // it from other errors, so we can't give a meaningful error here.
// distinguish it from other errors, so we can't give a meaningful error here. return api.pollMethod('parity_checkRequest', handle);
return api.pollMethod('parity_checkRequest', handle); })
}) .then((txHash) => {
.then((txHash) => { this.requestTx = txHash;
this.requestTx = txHash; return checkIfTxFailed(api, txHash, options.gas)
return checkIfTxFailed(api, txHash, options.gas) .then((hasFailed) => {
.then((hasFailed) => { if (hasFailed) {
if (hasFailed) { throw new Error('Transaction failed, all gas used up.');
throw new Error('Transaction failed, all gas used up.'); }
} this.step = POSTED_REQUEST;
this.step = POSTED_REQUEST; return waitForConfirmations(api, txHash, 1);
return waitForConfirmations(api, txHash, 1); });
}); });
}); })
}
chain
.then(() => this.checkIfReceivedCode()) .then(() => this.checkIfReceivedCode())
.then((hasReceived) => { .then((hasReceived) => {
if (hasReceived) { if (hasReceived) {

View File

@ -15,6 +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 React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { observable } from 'mobx'; import { observable } from 'mobx';
@ -206,7 +207,7 @@ class Verification extends Component {
const { const {
step, step,
isServerRunning, fee, isVerified, hasRequested, isServerRunning, isAbleToRequest, fee, accountIsVerified, accountHasRequested,
requestTx, isCodeValid, confirmationTx, requestTx, isCodeValid, confirmationTx,
setCode setCode
} = this.store; } = this.store;
@ -223,17 +224,37 @@ class Verification extends Component {
if (method === 'sms') { if (method === 'sms') {
fields.push({ fields.push({
key: 'number', key: 'number',
label: 'phone number in international format', label: (
hint: 'the SMS will be sent to this number', <FormattedMessage
id='ui.verification.gatherData.phoneNumber.label'
defaultMessage='phone number in international format'
/>
),
hint: (
<FormattedMessage
id='ui.verification.gatherData.phoneNumber.hint'
defaultMessage='the SMS will be sent to this number'
/>
),
error: this.store.isNumberValid ? null : 'invalid number', error: this.store.isNumberValid ? null : 'invalid number',
onChange: this.store.setNumber onChange: this.store.setNumber
}); });
} else if (method === 'email') { } else if (method === 'email') {
fields.push({ fields.push({
key: 'email', key: 'email',
label: 'email address', label: (
hint: 'the code will be sent to this address', <FormattedMessage
error: this.store.isEmailValid ? null : 'invalid email', id='ui.verification.gatherData.email.label'
defaultMessage='e-mail address'
/>
),
hint: (
<FormattedMessage
id='ui.verification.gatherData.email.hint'
defaultMessage='the code will be sent to this address'
/>
),
error: this.store.isEmailValid ? null : 'invalid e-mail',
onChange: this.store.setEmail onChange: this.store.setEmail
}); });
} }
@ -241,10 +262,12 @@ class Verification extends Component {
return ( return (
<GatherData <GatherData
fee={ fee } fee={ fee }
hasRequested={ hasRequested } accountHasRequested={ accountHasRequested }
isServerRunning={ isServerRunning } isServerRunning={ isServerRunning }
isVerified={ isVerified } isAbleToRequest={ isAbleToRequest }
method={ method } fields={ fields } accountIsVerified={ accountIsVerified }
method={ method }
fields={ fields }
setConsentGiven={ setConsentGiven } setConsentGiven={ setConsentGiven }
/> />
); );

View File

@ -16,10 +16,10 @@
import AddAddress from './AddAddress'; import AddAddress from './AddAddress';
import AddContract from './AddContract'; import AddContract from './AddContract';
import AddDapps from './AddDapps';
import CreateAccount from './CreateAccount'; import CreateAccount from './CreateAccount';
import CreateWallet from './CreateWallet'; import CreateWallet from './CreateWallet';
import DappPermissions from './DappPermissions'; import DappPermissions from './DappPermissions';
import DappsVisible from './AddDapps';
import DeleteAccount from './DeleteAccount'; import DeleteAccount from './DeleteAccount';
import DeployContract from './DeployContract'; import DeployContract from './DeployContract';
import EditMeta from './EditMeta'; import EditMeta from './EditMeta';
@ -37,10 +37,10 @@ import WalletSettings from './WalletSettings';
export { export {
AddAddress, AddAddress,
AddContract, AddContract,
AddDapps,
CreateAccount, CreateAccount,
CreateWallet, CreateWallet,
DappPermissions, DappPermissions,
DappsVisible,
DeleteAccount, DeleteAccount,
DeployContract, DeployContract,
EditMeta, EditMeta,

View File

@ -29,15 +29,14 @@ export default class Container extends Component {
className: PropTypes.string, className: PropTypes.string,
compact: PropTypes.bool, compact: PropTypes.bool,
light: PropTypes.bool, light: PropTypes.bool,
onClick: PropTypes.func,
style: PropTypes.object, style: PropTypes.object,
tabIndex: PropTypes.number, tabIndex: PropTypes.number,
title: nodeOrStringProptype() title: nodeOrStringProptype()
} }
render () { render () {
const { children, className, compact, light, style, tabIndex } = this.props; const { children, className, compact, light, onClick, style, tabIndex } = this.props;
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
const props = {}; const props = {};
if (Number.isInteger(tabIndex)) { if (Number.isInteger(tabIndex)) {
@ -45,8 +44,27 @@ export default class Container extends Component {
} }
return ( return (
<div className={ classes } style={ style } { ...props }> <div
<Card className={ compact ? styles.compact : styles.padded }> className={
[
styles.container,
light
? styles.light
: '',
className
].join(' ')
}
style={ style }
{ ...props }
>
<Card
className={
compact
? styles.compact
: styles.padded
}
onClick={ onClick }
>
{ this.renderTitle() } { this.renderTitle() }
{ children } { children }
</Card> </Card>

View File

@ -17,59 +17,80 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { Container, ContainerTitle, DappIcon, Tags } from '~/ui'; import Container, { Title as ContainerTitle } from '~/ui/Container';
import DappIcon from '~/ui/DappIcon';
import Tags from '~/ui/Tags';
import styles from './summary.css'; import styles from './dappCard.css';
export default class Summary extends Component { export default class DappCard extends Component {
static propTypes = { static propTypes = {
app: PropTypes.object.isRequired, app: PropTypes.object.isRequired,
children: PropTypes.node children: PropTypes.node,
} className: PropTypes.string,
onClick: PropTypes.func,
showLink: PropTypes.bool,
showTags: PropTypes.bool
};
static defaultProps = {
showLink: false,
showTags: false
};
render () { render () {
const { app } = this.props; const { app, children, className, onClick, showLink, showTags } = this.props;
if (!app) { if (!app) {
return null; return null;
} }
const link = this.renderLink(app);
return ( return (
<Container className={ styles.container }> <Container
className={
[styles.container, className].join(' ')
}
onClick={ onClick }
>
<DappIcon <DappIcon
app={ app } app={ app }
className={ styles.image } className={ styles.image }
/> />
<Tags tags={ [app.type] } /> <Tags
tags={
showTags
? [app.type]
: null
}
/>
<div className={ styles.description }> <div className={ styles.description }>
<ContainerTitle <ContainerTitle
className={ styles.title } className={ styles.title }
title={ link } title={
showLink
? this.renderLink(app)
: app.name
}
byline={ app.description } byline={ app.description }
/> />
<div className={ styles.author }> <div className={ styles.author }>
{ app.author }, v{ app.version } { app.author }, v{ app.version }
</div> </div>
{ this.props.children } { children }
</div> </div>
</Container> </Container>
); );
} }
renderLink (app) { renderLink (app) {
// Special case for web dapp
if (app.url === 'web') {
return (
<Link to={ `/web` }>
{ app.name }
</Link>
);
}
return ( return (
<Link to={ `/app/${app.id}` }> <Link
to={
app.url === 'web'
? '/web'
: `/app/${app.id}`
}
>
{ app.name } { app.name }
</Link> </Link>
); );

View File

@ -14,4 +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/>.
export default from './summary'; export default from './dappCard';

View File

@ -30,6 +30,7 @@ import Container, { Title as ContainerTitle } from './Container';
import ContextProvider from './ContextProvider'; import ContextProvider from './ContextProvider';
import CopyToClipboard from './CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
import CurrencySymbol from './CurrencySymbol'; import CurrencySymbol from './CurrencySymbol';
import DappCard from './DappCard';
import DappIcon from './DappIcon'; import DappIcon from './DappIcon';
import Editor from './Editor'; import Editor from './Editor';
import Errors from './Errors'; import Errors from './Errors';
@ -79,6 +80,7 @@ export {
CopyToClipboard, CopyToClipboard,
CurrencySymbol, CurrencySymbol,
DappIcon, DappIcon,
DappCard,
Editor, Editor,
Errors, Errors,
FEATURES, FEATURES,

View File

@ -21,14 +21,13 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { AddDapps, DappPermissions } from '~/modals'; import { DappPermissions, DappsVisible } from '~/modals';
import PermissionStore from '~/modals/DappPermissions/store'; import PermissionStore from '~/modals/DappPermissions/store';
import { Actionbar, Button, Page } from '~/ui'; import { Actionbar, Button, DappCard, Page } from '~/ui';
import { LockedIcon, VisibleIcon } from '~/ui/Icons'; import { LockedIcon, VisibleIcon } from '~/ui/Icons';
import UrlButton from './UrlButton'; import UrlButton from './UrlButton';
import DappsStore from './dappsStore'; import DappsStore from './dappsStore';
import Summary from './Summary';
import styles from './dapps.css'; import styles from './dapps.css';
@ -82,8 +81,8 @@ class Dapps extends Component {
return ( return (
<div> <div>
<AddDapps store={ this.store } />
<DappPermissions store={ this.permissionStore } /> <DappPermissions store={ this.permissionStore } />
<DappsVisible store={ this.store } />
<Actionbar <Actionbar
className={ styles.toolbar } className={ styles.toolbar }
title={ title={
@ -146,7 +145,11 @@ class Dapps extends Component {
className={ styles.item } className={ styles.item }
key={ app.id } key={ app.id }
> >
<Summary app={ app } /> <DappCard
app={ app }
showLink
showTags
/>
</div> </div>
); );
} }

View File

@ -192,7 +192,7 @@ pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider
{ {
let network_id = client.signing_network_id(); let network_id = client.signing_network_id();
let min_block = filled.min_block.clone(); let condition = filled.condition.clone();
let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?; let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?;
let (signed_transaction, token) = match signed_transaction { let (signed_transaction, token) = match signed_transaction {
@ -201,7 +201,7 @@ pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider
}; };
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id);
let pending_transaction = PendingTransaction::new(signed_transaction, min_block); let pending_transaction = PendingTransaction::new(signed_transaction, condition.map(Into::into));
dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| { dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| {
match token { match token {
Some(ref token) => WithToken::Yes(hash, token.clone()), Some(ref token) => WithToken::Yes(hash, token.clone()),
@ -222,7 +222,7 @@ pub fn fill_optional_fields<C, M>(request: TransactionRequest, default_sender: A
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()), value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new), data: request.data.unwrap_or_else(Vec::new),
min_block: request.min_block, condition: request.condition,
} }
} }

View File

@ -15,6 +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 util::{Address, U256, Bytes}; use util::{Address, U256, Bytes};
use v1::types::TransactionCondition;
/// Transaction request coming from RPC /// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
@ -33,8 +34,8 @@ pub struct TransactionRequest {
pub data: Option<Bytes>, pub data: Option<Bytes>,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this condition is met.
pub min_block: Option<u64>, pub condition: Option<TransactionCondition>,
} }
/// Transaction request coming from RPC with default values filled in. /// Transaction request coming from RPC with default values filled in.
@ -56,8 +57,8 @@ pub struct FilledTransactionRequest {
pub data: Bytes, pub data: Bytes,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this condition is met.
pub min_block: Option<u64>, pub condition: Option<TransactionCondition>,
} }
impl From<FilledTransactionRequest> for TransactionRequest { impl From<FilledTransactionRequest> for TransactionRequest {
@ -70,7 +71,7 @@ impl From<FilledTransactionRequest> for TransactionRequest {
value: Some(r.value), value: Some(r.value),
data: Some(r.data), data: Some(r.data),
nonce: r.nonce, nonce: r.nonce,
min_block: r.min_block, condition: r.condition,
} }
} }
} }

View File

@ -349,7 +349,7 @@ mod test {
value: 10_000_000.into(), value: 10_000_000.into(),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
}) })
} }

View File

@ -130,7 +130,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
.into_iter().collect::<HashSet<_>>(); .into_iter().collect::<HashSet<_>>();
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); let other = store.addresses_info();
Ok(info Ok(info
.into_iter() .into_iter()

View File

@ -51,22 +51,27 @@ impl<C> ParityAccountsClient<C> where C: MiningBlockChainClient {
} }
impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlockChainClient { impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlockChainClient {
fn all_accounts_info(&self) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> { fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, Error> {
self.active()?; self.active()?;
let store = take_weak!(self.accounts); let store = take_weak!(self.accounts);
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); let other = store.addresses_info();
Ok(info.into_iter().chain(other.into_iter()).map(|(a, v)| { Ok(info
let mut m = map![ .into_iter()
"name".to_owned() => v.name, .chain(other.into_iter())
"meta".to_owned() => v.meta .map(|(address, v)| {
]; let mut m = map![
if let &Some(ref uuid) = &v.uuid { "name".to_owned() => v.name,
m.insert("uuid".to_owned(), format!("{}", uuid)); "meta".to_owned() => v.meta
} ];
(format!("0x{}", a.hex()), m) if let &Some(ref uuid) = &v.uuid {
}).collect()) m.insert("uuid".to_owned(), format!("{}", uuid));
}
(address.into(), m)
})
.collect()
)
} }
fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> { fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> {
@ -132,8 +137,7 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
let store = take_weak!(self.accounts); let store = take_weak!(self.accounts);
let addr: Address = addr.into(); let addr: Address = addr.into();
store.remove_address(addr) store.remove_address(addr);
.expect("remove_address always returns Ok; qed");
Ok(true) Ok(true)
} }
@ -143,8 +147,7 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
let addr: Address = addr.into(); let addr: Address = addr.into();
store.set_account_name(addr.clone(), name.clone()) store.set_account_name(addr.clone(), name.clone())
.or_else(|_| store.set_address_name(addr, name)) .unwrap_or_else(|_| store.set_address_name(addr, name));
.expect("set_address_name always returns Ok; qed");
Ok(true) Ok(true)
} }
@ -154,8 +157,7 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
let addr: Address = addr.into(); let addr: Address = addr.into();
store.set_account_meta(addr.clone(), meta.clone()) store.set_account_meta(addr.clone(), meta.clone())
.or_else(|_| store.set_address_meta(addr, meta)) .unwrap_or_else(|_| store.set_address_meta(addr, meta));
.expect("set_address_meta always returns Ok; qed");
Ok(true) Ok(true)
} }

View File

@ -87,8 +87,8 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient,
if let Some(gas) = modification.gas { if let Some(gas) = modification.gas {
request.gas = gas.into(); request.gas = gas.into();
} }
if let Some(ref min_block) = modification.min_block { if let Some(ref condition) = modification.condition {
request.min_block = min_block.as_ref().and_then(|b| b.to_min_block_num()); request.condition = condition.clone().map(Into::into);
} }
} }
let result = f(&*client, &*miner, &*accounts, payload); let result = f(&*client, &*miner, &*accounts, payload);
@ -160,7 +160,7 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
// Dispatch if everything is ok // Dispatch if everything is ok
if sender_matches && data_matches && value_matches && nonce_matches { if sender_matches && data_matches && value_matches && nonce_matches {
let pending_transaction = PendingTransaction::new(signed_transaction, request.min_block); let pending_transaction = PendingTransaction::new(signed_transaction, request.condition.map(Into::into));
dispatch_transaction(&*client, &*miner, pending_transaction) dispatch_transaction(&*client, &*miner, pending_transaction)
.map(Into::into) .map(Into::into)
.map(ConfirmationResponse::SendTransaction) .map(ConfirmationResponse::SendTransaction)

View File

@ -212,7 +212,7 @@ impl MinerService for TestMinerService {
self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect()
} }
fn ready_transactions(&self, _best_block: BlockNumber) -> Vec<PendingTransaction> { fn ready_transactions(&self, _best_block: BlockNumber, _best_timestamp: u64) -> Vec<PendingTransaction> {
self.pending_transactions.lock().values().cloned().map(Into::into).collect() self.pending_transactions.lock().values().cloned().map(Into::into).collect()
} }

View File

@ -365,6 +365,8 @@ fn rpc_eth_accounts() {
let tester = EthTester::default(); let tester = EthTester::default();
let address = tester.accounts_provider.new_account("").unwrap(); let address = tester.accounts_provider.new_account("").unwrap();
tester.accounts_provider.set_new_dapps_whitelist(None).unwrap(); tester.accounts_provider.set_new_dapps_whitelist(None).unwrap();
tester.accounts_provider.set_address_name(1.into(), "1".into());
tester.accounts_provider.set_address_name(10.into(), "10".into());
// with current policy it should return the account // with current policy it should return the account
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
@ -514,7 +516,7 @@ fn rpc_eth_pending_transaction_by_hash() {
tester.miner.pending_transactions.lock().insert(H256::zero(), tx); tester.miner.pending_transactions.lock().insert(H256::zero(), tx);
} }
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","minBlock":null,"networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#;
let request = r#"{ let request = r#"{
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "eth_getTransactionByHash", "method": "eth_getTransactionByHash",
@ -830,12 +832,11 @@ fn rpc_eth_sign_transaction() {
let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() +
r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""raw":"0x"# + &rlp.to_hex() + r#"","# +
r#""tx":{"# + r#""tx":{"# +
r#""blockHash":null,"blockNumber":null,"creates":null,"# + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# +
&format!("\"from\":\"0x{:?}\",", &address) + &format!("\"from\":\"0x{:?}\",", &address) +
r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# +
&format!("\"hash\":\"0x{:?}\",", t.hash()) + &format!("\"hash\":\"0x{:?}\",", t.hash()) +
r#""input":"0x","# + r#""input":"0x","# +
r#""minBlock":null,"# +
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
r#""nonce":"0x1","# + r#""nonce":"0x1","# +
&format!("\"publicKey\":\"0x{:?}\",", t.recover_public().unwrap()) + &format!("\"publicKey\":\"0x{:?}\",", t.recover_public().unwrap()) +

View File

@ -120,10 +120,11 @@ fn should_be_able_to_set_meta() {
fn rpc_parity_set_and_get_dapps_accounts() { fn rpc_parity_set_and_get_dapps_accounts() {
// given // given
let tester = setup(); let tester = setup();
tester.accounts.set_address_name(10.into(), "10".into());
assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]); assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]);
// when // when
let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a"]], "id": 1}"#; let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));

View File

@ -85,7 +85,7 @@ fn should_return_list_of_items_to_confirm() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap();
@ -93,7 +93,7 @@ fn should_return_list_of_items_to_confirm() {
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
let response = concat!( let response = concat!(
r#"{"jsonrpc":"2.0","result":["#, r#"{"jsonrpc":"2.0","result":["#,
r#"{"id":"0x1","payload":{"sendTransaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","minBlock":null,"nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, r#"{"id":"0x1","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#,
r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#,
r#"],"id":1}"# r#"],"id":1}"#
); );
@ -116,7 +116,7 @@ fn should_reject_transaction_from_queue_without_dispatching() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
@ -143,7 +143,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
@ -187,7 +187,7 @@ fn should_confirm_transaction_and_dispatch() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -233,7 +233,7 @@ fn should_alter_the_sender_and_nonce() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: Some(10.into()), nonce: Some(10.into()),
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -283,7 +283,7 @@ fn should_confirm_transaction_with_token() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -332,7 +332,7 @@ fn should_confirm_transaction_with_rlp() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -380,7 +380,7 @@ fn should_return_error_when_sender_does_not_match() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {

View File

@ -277,12 +277,11 @@ fn should_add_sign_transaction_to_the_queue() {
let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() +
r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""raw":"0x"# + &rlp.to_hex() + r#"","# +
r#""tx":{"# + r#""tx":{"# +
r#""blockHash":null,"blockNumber":null,"creates":null,"# + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# +
&format!("\"from\":\"0x{:?}\",", &address) + &format!("\"from\":\"0x{:?}\",", &address) +
r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# +
&format!("\"hash\":\"0x{:?}\",", t.hash()) + &format!("\"hash\":\"0x{:?}\",", t.hash()) +
r#""input":"0x","# + r#""input":"0x","# +
r#""minBlock":null,"# +
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
r#""nonce":"0x1","# + r#""nonce":"0x1","# +
&format!("\"publicKey\":\"0x{:?}\",", t.public_key()) + &format!("\"publicKey\":\"0x{:?}\",", t.public_key()) +

View File

@ -25,7 +25,7 @@ build_rpc_trait! {
pub trait ParityAccounts { pub trait ParityAccounts {
/// Returns accounts information. /// Returns accounts information.
#[rpc(name = "parity_allAccountsInfo")] #[rpc(name = "parity_allAccountsInfo")]
fn all_accounts_info(&self) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error>; fn all_accounts_info(&self) -> Result<BTreeMap<H160, BTreeMap<String, String>>, Error>;
/// Creates new account from the given phrase using standard brainwallet mechanism. /// Creates new account from the given phrase using standard brainwallet mechanism.
/// Second parameter is password for the new account. /// Second parameter is password for the new account.

View File

@ -139,7 +139,7 @@ mod tests {
fn test_serialize_block_transactions() { fn test_serialize_block_transactions() {
let t = BlockTransactions::Full(vec![Transaction::default()]); let t = BlockTransactions::Full(vec![Transaction::default()]);
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}]"#); assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}]"#);
let t = BlockTransactions::Hashes(vec![H256::default().into()]); let t = BlockTransactions::Hashes(vec![H256::default().into()]);
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();

View File

@ -21,7 +21,7 @@ use serde::{Serialize, Serializer};
use util::log::Colour; use util::log::Colour;
use util::bytes::ToPretty; use util::bytes::ToPretty;
use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, BlockNumber}; use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition};
use v1::helpers; use v1::helpers;
/// Confirmation waiting in a queue /// Confirmation waiting in a queue
@ -196,9 +196,8 @@ pub struct TransactionModification {
pub gas_price: Option<U256>, pub gas_price: Option<U256>,
/// Modified gas /// Modified gas
pub gas: Option<U256>, pub gas: Option<U256>,
/// Modified min block /// Modified transaction condition.
#[serde(rename="minBlock")] pub condition: Option<Option<TransactionCondition>>,
pub min_block: Option<Option<BlockNumber>>,
} }
/// Represents two possible return values. /// Represents two possible return values.
@ -240,7 +239,7 @@ impl<A, B> Serialize for Either<A, B> where
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;
use serde_json; use serde_json;
use v1::types::{U256, H256, BlockNumber}; use v1::types::{U256, H256, TransactionCondition};
use v1::helpers; use v1::helpers;
use super::*; use super::*;
@ -274,13 +273,13 @@ mod tests {
value: 100_000.into(), value: 100_000.into(),
data: vec![1, 2, 3], data: vec![1, 2, 3],
nonce: Some(1.into()), nonce: Some(1.into()),
min_block: None, condition: None,
}), }),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#;
// then // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -300,13 +299,13 @@ mod tests {
value: 100_000.into(), value: 100_000.into(),
data: vec![1, 2, 3], data: vec![1, 2, 3],
nonce: Some(1.into()), nonce: Some(1.into()),
min_block: None, condition: None,
}), }),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#;
// then // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -336,7 +335,7 @@ mod tests {
let s1 = r#"{ let s1 = r#"{
"sender": "0x000000000000000000000000000000000000000a", "sender": "0x000000000000000000000000000000000000000a",
"gasPrice":"0xba43b7400", "gasPrice":"0xba43b7400",
"minBlock":"0x42" "condition": { "block": 66 }
}"#; }"#;
let s2 = r#"{"gas": "0x1233"}"#; let s2 = r#"{"gas": "0x1233"}"#;
let s3 = r#"{}"#; let s3 = r#"{}"#;
@ -351,19 +350,19 @@ mod tests {
sender: Some(10.into()), sender: Some(10.into()),
gas_price: Some(U256::from_str("0ba43b7400").unwrap()), gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
gas: None, gas: None,
min_block: Some(Some(BlockNumber::Num(0x42))), condition: Some(Some(TransactionCondition::Number(0x42))),
}); });
assert_eq!(res2, TransactionModification { assert_eq!(res2, TransactionModification {
sender: None, sender: None,
gas_price: None, gas_price: None,
gas: Some(U256::from_str("1233").unwrap()), gas: Some(U256::from_str("1233").unwrap()),
min_block: None, condition: None,
}); });
assert_eq!(res3, TransactionModification { assert_eq!(res3, TransactionModification {
sender: None, sender: None,
gas_price: None, gas_price: None,
gas: None, gas: None,
min_block: None, condition: None,
}); });
} }

View File

@ -27,6 +27,7 @@ mod log;
mod sync; mod sync;
mod transaction; mod transaction;
mod transaction_request; mod transaction_request;
mod transaction_condition;
mod receipt; mod receipt;
mod rpc_settings; mod rpc_settings;
mod trace; mod trace;
@ -55,6 +56,7 @@ pub use self::sync::{
}; };
pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus};
pub use self::transaction_request::TransactionRequest; pub use self::transaction_request::TransactionRequest;
pub use self::transaction_condition::TransactionCondition;
pub use self::receipt::Receipt; pub use self::receipt::Receipt;
pub use self::rpc_settings::RpcSettings; pub use self::rpc_settings::RpcSettings;
pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace::{LocalizedTrace, TraceResults};

View File

@ -19,7 +19,7 @@ use ethcore::miner;
use ethcore::contract_address; use ethcore::contract_address;
use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction};
use v1::helpers::errors; use v1::helpers::errors;
use v1::types::{Bytes, H160, H256, U256, H512, BlockNumber}; use v1::types::{Bytes, H160, H256, U256, H512, TransactionCondition};
/// Transaction /// Transaction
#[derive(Debug, Default, Clone, PartialEq, Serialize)] #[derive(Debug, Default, Clone, PartialEq, Serialize)]
@ -70,8 +70,7 @@ pub struct Transaction {
/// The S field of the signature. /// The S field of the signature.
pub s: U256, pub s: U256,
/// Transaction activates at specified block. /// Transaction activates at specified block.
#[serde(rename="minBlock")] pub condition: Option<TransactionCondition>,
pub min_block: Option<BlockNumber>,
} }
/// Local Transaction Status /// Local Transaction Status
@ -190,7 +189,7 @@ impl From<LocalizedTransaction> for Transaction {
v: t.original_v().into(), v: t.original_v().into(),
r: signature.r().into(), r: signature.r().into(),
s: signature.s().into(), s: signature.s().into(),
min_block: None, condition: None,
} }
} }
} }
@ -224,7 +223,7 @@ impl From<SignedTransaction> for Transaction {
v: t.original_v().into(), v: t.original_v().into(),
r: signature.r().into(), r: signature.r().into(),
s: signature.s().into(), s: signature.s().into(),
min_block: None, condition: None,
} }
} }
} }
@ -232,7 +231,7 @@ impl From<SignedTransaction> for Transaction {
impl From<PendingTransaction> for Transaction { impl From<PendingTransaction> for Transaction {
fn from(t: PendingTransaction) -> Transaction { fn from(t: PendingTransaction) -> Transaction {
let mut r = Transaction::from(t.transaction); let mut r = Transaction::from(t.transaction);
r.min_block = t.min_block.map(|b| BlockNumber::Num(b)); r.condition = t.condition.map(|b| b.into());
r r
} }
} }
@ -261,7 +260,7 @@ mod tests {
fn test_transaction_serialize() { fn test_transaction_serialize() {
let t = Transaction::default(); let t = Transaction::default();
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}"#); assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}"#);
} }
#[test] #[test]

View File

@ -0,0 +1,67 @@
// 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 ethcore;
/// Represents condition on minimum block number or block timestamp.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum TransactionCondition {
/// Valid at this minimum block number.
#[serde(rename="block")]
Number(u64),
/// Valid at given unix time.
#[serde(rename="time")]
Timestamp(u64),
}
impl Into<ethcore::transaction::Condition> for TransactionCondition {
fn into(self) -> ethcore::transaction::Condition {
match self {
TransactionCondition::Number(n) => ethcore::transaction::Condition::Number(n),
TransactionCondition::Timestamp(n) => ethcore::transaction::Condition::Timestamp(n),
}
}
}
impl From<ethcore::transaction::Condition> for TransactionCondition {
fn from(condition: ethcore::transaction::Condition) -> Self {
match condition {
ethcore::transaction::Condition::Number(n) => TransactionCondition::Number(n),
ethcore::transaction::Condition::Timestamp(n) => TransactionCondition::Timestamp(n),
}
}
}
#[cfg(test)]
mod tests {
use ethcore;
use super::*;
use serde_json;
#[test]
fn condition_deserialization() {
let s = r#"[{ "block": 51 }, { "time": 10 }]"#;
let deserialized: Vec<TransactionCondition> = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, vec![TransactionCondition::Number(51), TransactionCondition::Timestamp(10)])
}
#[test]
fn condition_into() {
assert_eq!(ethcore::transaction::Condition::Number(100), TransactionCondition::Number(100).into());
assert_eq!(ethcore::transaction::Condition::Timestamp(100), TransactionCondition::Timestamp(100).into());
}
}

View File

@ -16,7 +16,7 @@
//! `TransactionRequest` type //! `TransactionRequest` type
use v1::types::{Bytes, H160, U256, BlockNumber}; use v1::types::{Bytes, H160, U256, TransactionCondition};
use v1::helpers; use v1::helpers;
use util::log::Colour; use util::log::Colour;
@ -41,9 +41,8 @@ pub struct TransactionRequest {
pub data: Option<Bytes>, pub data: Option<Bytes>,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this block condition.
#[serde(rename="minBlock")] pub condition: Option<TransactionCondition>,
pub min_block: Option<BlockNumber>,
} }
pub fn format_ether(i: U256) -> String { pub fn format_ether(i: U256) -> String {
@ -93,7 +92,7 @@ impl From<helpers::TransactionRequest> for TransactionRequest {
value: r.value.map(Into::into), value: r.value.map(Into::into),
data: r.data.map(Into::into), data: r.data.map(Into::into),
nonce: r.nonce.map(Into::into), nonce: r.nonce.map(Into::into),
min_block: r.min_block.map(|b| BlockNumber::Num(b)), condition: r.condition.map(Into::into),
} }
} }
} }
@ -108,7 +107,7 @@ impl From<helpers::FilledTransactionRequest> for TransactionRequest {
value: Some(r.value.into()), value: Some(r.value.into()),
data: Some(r.data.into()), data: Some(r.data.into()),
nonce: r.nonce.map(Into::into), nonce: r.nonce.map(Into::into),
min_block: r.min_block.map(|b| BlockNumber::Num(b)), condition: r.condition.map(Into::into),
} }
} }
} }
@ -123,7 +122,7 @@ impl Into<helpers::TransactionRequest> for TransactionRequest {
value: self.value.map(Into::into), value: self.value.map(Into::into),
data: self.data.map(Into::into), data: self.data.map(Into::into),
nonce: self.nonce.map(Into::into), nonce: self.nonce.map(Into::into),
min_block: self.min_block.and_then(|b| b.to_min_block_num()), condition: self.condition.map(Into::into),
} }
} }
} }
@ -134,7 +133,7 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
use rustc_serialize::hex::FromHex; use rustc_serialize::hex::FromHex;
use serde_json; use serde_json;
use v1::types::{U256, H160, BlockNumber}; use v1::types::{U256, H160, TransactionCondition};
use super::*; use super::*;
#[test] #[test]
@ -147,7 +146,7 @@ mod tests {
"value":"0x3", "value":"0x3",
"data":"0x123456", "data":"0x123456",
"nonce":"0x4", "nonce":"0x4",
"minBlock":"0x13" "condition": { "block": 19 }
}"#; }"#;
let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); let deserialized: TransactionRequest = serde_json::from_str(s).unwrap();
@ -159,7 +158,7 @@ mod tests {
value: Some(U256::from(3)), value: Some(U256::from(3)),
data: Some(vec![0x12, 0x34, 0x56].into()), data: Some(vec![0x12, 0x34, 0x56].into()),
nonce: Some(U256::from(4)), nonce: Some(U256::from(4)),
min_block: Some(BlockNumber::Num(0x13)), condition: Some(TransactionCondition::Number(0x13)),
}); });
} }
@ -183,7 +182,7 @@ mod tests {
value: Some(U256::from_str("9184e72a").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()),
data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()),
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }
@ -200,7 +199,7 @@ mod tests {
value: None, value: None,
data: None, data: None,
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }
@ -224,7 +223,7 @@ mod tests {
value: None, value: None,
data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()),
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }

View File

@ -1,5 +1,5 @@
use client::{Rpc, RpcError}; use client::{Rpc, RpcError};
use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, BlockNumber}; use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
use serde_json::{Value as JsonValue, to_value}; use serde_json::{Value as JsonValue, to_value};
use std::path::PathBuf; use std::path::PathBuf;
use futures::{BoxFuture, Canceled}; use futures::{BoxFuture, Canceled};
@ -22,13 +22,13 @@ impl SignerRpc {
id: U256, id: U256,
new_gas: Option<U256>, new_gas: Option<U256>,
new_gas_price: Option<U256>, new_gas_price: Option<U256>,
new_min_block: Option<Option<BlockNumber>>, new_condition: Option<Option<TransactionCondition>>,
pwd: &str pwd: &str
) -> BoxFuture<Result<U256, RpcError>, Canceled> ) -> BoxFuture<Result<U256, RpcError>, Canceled>
{ {
self.rpc.request("signer_confirmRequest", vec![ self.rpc.request("signer_confirmRequest", vec![
to_value(&format!("{:#x}", id)), to_value(&format!("{:#x}", id)),
to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, min_block: new_min_block }), to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
to_value(&pwd), to_value(&pwd),
]) ])
} }

View File

@ -25,7 +25,7 @@ echo "Homepage: https://ethcore.io" >> $control
echo "Vcs-Git: git://github.com/ethcore/parity.git" >> $control echo "Vcs-Git: git://github.com/ethcore/parity.git" >> $control
echo "Vcs-Browser: https://github.com/ethcore/parity" >> $control echo "Vcs-Browser: https://github.com/ethcore/parity" >> $control
echo "Architecture: $1" >> $control echo "Architecture: $1" >> $control
echo "Depends: libssl1.0.0" >> $control echo "Depends: libssl1.0.0 (>=1.0.0)" >> $control
echo "Description: Ethereum network client by Ethcore" >> $control echo "Description: Ethereum network client by Ethcore" >> $control
#build .deb package #build .deb package

View File

@ -9,8 +9,9 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1" futures-cpupool = "0.1"
parking_lot = "0.3"
log = "0.3" log = "0.3"
reqwest = "0.2" reqwest = "0.4"
mime = "0.2" mime = "0.2"
clippy = { version = "0.0.90", optional = true} clippy = { version = "0.0.90", optional = true}

View File

@ -16,13 +16,14 @@
//! Fetching //! Fetching
use std::{io, fmt}; use std::{io, fmt, time};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool}; use std::sync::atomic::{self, AtomicBool};
use futures::{self, BoxFuture, Future}; use futures::{self, BoxFuture, Future};
use futures_cpupool::{CpuPool, CpuFuture}; use futures_cpupool::{CpuPool, CpuFuture};
use mime::{self, Mime}; use mime::{self, Mime};
use parking_lot::RwLock;
use reqwest; use reqwest;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@ -73,24 +74,52 @@ pub trait Fetch: Clone + Send + Sync + 'static {
fn close(self) where Self: Sized {} fn close(self) where Self: Sized {}
} }
#[derive(Clone)] const CLIENT_TIMEOUT_SECONDS: u64 = 5;
pub struct Client { pub struct Client {
client: Arc<reqwest::Client>, client: RwLock<(time::Instant, Arc<reqwest::Client>)>,
pool: CpuPool, pool: CpuPool,
limit: Option<usize>, limit: Option<usize>,
} }
impl Clone for Client {
fn clone(&self) -> Self {
let (ref time, ref client) = *self.client.read();
Client {
client: RwLock::new((time.clone(), client.clone())),
pool: self.pool.clone(),
limit: self.limit.clone(),
}
}
}
impl Client { impl Client {
fn with_limit(limit: Option<usize>) -> Result<Self, Error> { fn new_client() -> Result<Arc<reqwest::Client>, Error> {
let mut client = reqwest::Client::new()?; let mut client = reqwest::Client::new()?;
client.redirect(reqwest::RedirectPolicy::limited(5)); client.redirect(reqwest::RedirectPolicy::limited(5));
Ok(Arc::new(client))
}
fn with_limit(limit: Option<usize>) -> Result<Self, Error> {
Ok(Client { Ok(Client {
client: Arc::new(client), client: RwLock::new((time::Instant::now(), Self::new_client()?)),
pool: CpuPool::new(4), pool: CpuPool::new(4),
limit: limit, limit: limit,
}) })
} }
fn client(&self) -> Result<Arc<reqwest::Client>, Error> {
{
let (ref time, ref client) = *self.client.read();
if time.elapsed() < time::Duration::from_secs(CLIENT_TIMEOUT_SECONDS) {
return Ok(client.clone());
}
}
let client = Self::new_client()?;
*self.client.write() = (time::Instant::now(), client.clone());
Ok(client)
}
} }
impl Fetch for Client { impl Fetch for Client {
@ -112,12 +141,19 @@ impl Fetch for Client {
fn fetch_with_abort(&self, url: &str, abort: Abort) -> Self::Result { fn fetch_with_abort(&self, url: &str, abort: Abort) -> Self::Result {
debug!(target: "fetch", "Fetching from: {:?}", url); debug!(target: "fetch", "Fetching from: {:?}", url);
self.pool.spawn(FetchTask { match self.client() {
url: url.into(), Ok(client) => {
client: self.client.clone(), self.pool.spawn(FetchTask {
limit: self.limit, url: url.into(),
abort: abort, client: client,
}) limit: self.limit,
abort: abort,
})
},
Err(err) => {
self.pool.spawn(futures::future::err(err))
},
}
} }
} }

View File

@ -21,6 +21,7 @@ extern crate log;
extern crate futures; extern crate futures;
extern crate futures_cpupool; extern crate futures_cpupool;
extern crate parking_lot;
extern crate reqwest; extern crate reqwest;
pub extern crate mime; pub extern crate mime;