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) => {
if (this._lastDefaultAccount !== defaultAccount) {
this._lastDefaultAccount = defaultAccount;
this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); this._updateSubscriptions('parity_defaultAccount', null, defaultAccount);
}); }
nextTimeout();
})
.catch(() => nextTimeout());
} }
_listAccounts = () => { _listAccounts = () => {
@ -54,15 +80,21 @@ 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);
return this._api.parity
.allAccountsInfo()
.catch(() => {
// NOTE: This fails on non-secure APIs, swallow error
return {};
})
.then((allInfo) => {
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo); this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
}); });
});
} }
_loggingSubscribe () { _loggingSubscribe () {
@ -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,32 +37,23 @@ export default class AddDapps extends Component {
} }
return ( return (
<Modal <Portal
actions={ [ className={ styles.modal }
<Button onClose={ store.closeModal }
icon={ <DoneIcon /> } open
key='done' >
label={ <ContainerTitle
<FormattedMessage
id='dapps.add.button.done'
defaultMessage='Done'
/>
}
onClick={ store.closeModal }
/>
] }
compact
title={ title={
<FormattedMessage <FormattedMessage
id='dapps.add.label' id='dapps.add.label'
defaultMessage='visible applications' defaultMessage='visible applications'
/> />
} }
visible />
> <div className={ styles.container }>
<div className={ styles.warning } /> <div className={ styles.warning } />
{ {
this.renderList(store.sortedLocal, this.renderList(store.sortedLocal, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.local.label' id='dapps.add.local.label'
defaultMessage='Applications locally available' defaultMessage='Applications locally available'
@ -76,7 +65,7 @@ export default class AddDapps extends Component {
) )
} }
{ {
this.renderList(store.sortedBuiltin, this.renderList(store.sortedBuiltin, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.builtin.label' id='dapps.add.builtin.label'
defaultMessage='Applications bundled with Parity' defaultMessage='Applications bundled with Parity'
@ -88,7 +77,7 @@ export default class AddDapps extends Component {
) )
} }
{ {
this.renderList(store.sortedNetwork, this.renderList(store.sortedNetwork, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.network.label' id='dapps.add.network.label'
defaultMessage='Applications on the global network' defaultMessage='Applications on the global network'
@ -99,11 +88,12 @@ export default class AddDapps extends Component {
/> />
) )
} }
</Modal> </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,24 +161,27 @@ 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;
chain = request.estimateGas(options, values) return 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) => {
// TODO: The "request rejected" error doesn't have any property to // The "request rejected" error doesn't have any property to distinguish
// distinguish it from other errors, so we can't give a meaningful error here. // 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) => {
@ -181,9 +195,7 @@ export default class VerificationStore {
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 ( return (
<Link to={ `/web` }> <Link
{ app.name } to={
</Link> app.url === 'web'
); ? '/web'
: `/app/${app.id}`
} }
>
return (
<Link to={ `/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,13 +51,16 @@ 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
.into_iter()
.chain(other.into_iter())
.map(|(address, v)| {
let mut m = map![ let mut m = map![
"name".to_owned() => v.name, "name".to_owned() => v.name,
"meta".to_owned() => v.meta "meta".to_owned() => v.meta
@ -65,8 +68,10 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
if let &Some(ref uuid) = &v.uuid { if let &Some(ref uuid) = &v.uuid {
m.insert("uuid".to_owned(), format!("{}", uuid)); m.insert("uuid".to_owned(), format!("{}", uuid));
} }
(format!("0x{}", a.hex()), m) (address.into(), m)
}).collect()) })
.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);
match self.client() {
Ok(client) => {
self.pool.spawn(FetchTask { self.pool.spawn(FetchTask {
url: url.into(), url: url.into(),
client: self.client.clone(), client: client,
limit: self.limit, limit: self.limit,
abort: abort, 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;