Merge branch 'master' into ui-2

This commit is contained in:
Jaco Greeff 2017-04-26 11:35:49 +02:00
commit 17448b370c
151 changed files with 6144 additions and 2224 deletions

82
Cargo.lock generated
View File

@ -339,8 +339,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "elastic-array" name = "elastic-array"
version = "0.6.0" version = "0.7.0"
source = "git+https://github.com/paritytech/elastic-array#346f1ba5982576dab9d0b8fa178b50e1db0a21cd" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -603,6 +603,7 @@ dependencies = [
"ethcore-ipc 1.7.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.7.0", "ethcore-ipc-codegen 1.7.0",
"ethcore-ipc-nano 1.7.0", "ethcore-ipc-nano 1.7.0",
"ethcore-logger 1.7.0",
"ethcore-util 1.7.0", "ethcore-util 1.7.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",
@ -617,7 +618,7 @@ dependencies = [
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-service 0.1.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)",
@ -670,7 +671,7 @@ version = "1.7.0"
dependencies = [ dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"elastic-array 0.6.0 (git+https://github.com/paritytech/elastic-array)", "elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)", "eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)",
"ethcore-bigint 0.1.2", "ethcore-bigint 0.1.2",
@ -1037,7 +1038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "jsonrpc-core" name = "jsonrpc-core"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1049,7 +1050,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-http-server" name = "jsonrpc-http-server"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1062,19 +1063,20 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-ipc-server" name = "jsonrpc-ipc-server"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-tokio-ipc 0.1.0 (git+https://github.com/nikvolf/parity-tokio-ipc)", "parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "jsonrpc-macros" name = "jsonrpc-macros"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-pubsub 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1084,7 +1086,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-minihttp-server" name = "jsonrpc-minihttp-server"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1098,7 +1100,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-pubsub" name = "jsonrpc-pubsub"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1108,19 +1110,20 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-server-utils" name = "jsonrpc-server-utils"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "jsonrpc-tcp-server" name = "jsonrpc-tcp-server"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1132,7 +1135,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-ws-server" name = "jsonrpc-ws-server"
version = "7.0.0" version = "7.0.0"
source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#ad0682785a693eba3069e48b57ec89abb62c3b60" source = "git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7#306e200900e12313837733aa0a01966ff20610b1"
dependencies = [ dependencies = [
"jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
"jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "jsonrpc-server-utils 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)",
@ -1740,18 +1743,19 @@ dependencies = [
[[package]] [[package]]
name = "parity-tokio-ipc" name = "parity-tokio-ipc"
version = "0.1.0" version = "0.1.5"
source = "git+https://github.com/nikvolf/parity-tokio-ipc#3d4234de6bdc78688ef803935111003080fd5375" source = "git+https://github.com/nikvolf/parity-tokio-ipc#d6c5b3cfcc913a1b9cf0f0562a10b083ceb9fb7c"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-named-pipes 0.1.4 (git+https://github.com/alexcrichton/mio-named-pipes)", "mio-named-pipes 0.1.4 (git+https://github.com/alexcrichton/mio-named-pipes)",
"miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-line 0.1.0 (git+https://github.com/tokio-rs/tokio-line)", "tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-named-pipes 0.1.0 (git+https://github.com/alexcrichton/tokio-named-pipes)", "tokio-named-pipes 0.1.0 (git+https://github.com/nikvolf/tokio-named-pipes)",
"tokio-uds 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-uds 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2030,7 +2034,7 @@ name = "rlp"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"elastic-array 0.6.0 (git+https://github.com/paritytech/elastic-array)", "elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-bigint 0.1.2", "ethcore-bigint 0.1.2",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2468,12 +2472,12 @@ dependencies = [
"mio 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "tokio-io" name = "tokio-io"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2481,17 +2485,6 @@ dependencies = [
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "tokio-line"
version = "0.1.0"
source = "git+https://github.com/tokio-rs/tokio-line#482614ae0c82daf584727ae65a80d854fe861f81"
dependencies = [
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "tokio-minihttp" name = "tokio-minihttp"
version = "0.1.0" version = "0.1.0"
@ -2510,11 +2503,13 @@ dependencies = [
[[package]] [[package]]
name = "tokio-named-pipes" name = "tokio-named-pipes"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alexcrichton/tokio-named-pipes#3a22f8fc9a441b548aec25bd5df3b1e0ab99fabe" source = "git+https://github.com/nikvolf/tokio-named-pipes#0b9b728eaeb0a6673c287ac7692be398fd651752"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-named-pipes 0.1.4 (git+https://github.com/alexcrichton/mio-named-pipes)", "mio-named-pipes 0.1.4 (git+https://github.com/alexcrichton/mio-named-pipes)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2559,14 +2554,18 @@ dependencies = [
[[package]] [[package]]
name = "tokio-uds" name = "tokio-uds"
version = "0.1.2" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-uds 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "mio-uds 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2817,7 +2816,7 @@ dependencies = [
"checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8" "checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8"
"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "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/paritytech/elastic-array)" = "<none>" "checksum elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71a64decd4b8cd06654a4e643c45cb558ad554abbffd82a7e16e34f45f51b605"
"checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83" "checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83"
"checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "<none>" "checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "<none>"
"checksum ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63df67d0af5e3cb906b667ca1a6e00baffbed87d0d8f5f78468a1f5eb3a66345" "checksum ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63df67d0af5e3cb906b667ca1a6e00baffbed87d0d8f5f78468a1f5eb3a66345"
@ -2901,7 +2900,7 @@ dependencies = [
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb" "checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
"checksum parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1d06f6ee0fda786df3784a96ee3f0629f529b91cbfb7d142f6410e6bcd1ce2c" "checksum parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1d06f6ee0fda786df3784a96ee3f0629f529b91cbfb7d142f6410e6bcd1ce2c"
"checksum parity-tokio-ipc 0.1.0 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>" "checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>"
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "<none>" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "<none>"
"checksum parity-wordlist 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07779ab11d958acbee30fcf644c99d3fae132d8fcb41282a25e1ee284097bdd2" "checksum parity-wordlist 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07779ab11d958acbee30fcf644c99d3fae132d8fcb41282a25e1ee284097bdd2"
"checksum parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aebb68eebde2c99f89592d925288600fde220177e46b5c9a91ca218d245aeedf" "checksum parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aebb68eebde2c99f89592d925288600fde220177e46b5c9a91ca218d245aeedf"
@ -2980,14 +2979,13 @@ dependencies = [
"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af"
"checksum tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7aef43048292ca0bae4ab32180e85f6202cf2816c2a210c396a84b99dab9270" "checksum tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7aef43048292ca0bae4ab32180e85f6202cf2816c2a210c396a84b99dab9270"
"checksum tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "99e958104a67877907c1454386d5482fe8e965a55d60be834a15a44328e7dc76" "checksum tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "99e958104a67877907c1454386d5482fe8e965a55d60be834a15a44328e7dc76"
"checksum tokio-io 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6a278fde45f1be68e44995227d426aaa4841e0980bb0a21b981092f28c3c8473" "checksum tokio-io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "48f55df1341bb92281f229a6030bc2abffde2c7a44c6d6b802b7687dd8be0775"
"checksum tokio-line 0.1.0 (git+https://github.com/tokio-rs/tokio-line)" = "<none>"
"checksum tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)" = "<none>" "checksum tokio-minihttp 0.1.0 (git+https://github.com/tomusdrw/tokio-minihttp)" = "<none>"
"checksum tokio-named-pipes 0.1.0 (git+https://github.com/alexcrichton/tokio-named-pipes)" = "<none>" "checksum tokio-named-pipes 0.1.0 (git+https://github.com/nikvolf/tokio-named-pipes)" = "<none>"
"checksum tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)" = "<none>" "checksum tokio-proto 0.1.0 (git+https://github.com/tomusdrw/tokio-proto)" = "<none>"
"checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808" "checksum tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c0d6031f94d78d7b4d509d4a7c5e1cdf524a17e7b08d1c188a83cf720e69808"
"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
"checksum tokio-uds 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc7b5fc8e19e220b29566d1750949224a518478eab9cebc8df60583242ca30a" "checksum tokio-uds 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bd209039933255ea77c6d7a1d18abc20b997d161acb900acca6eb74cdd049f31"
"checksum toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "fcd27a04ca509aff336ba5eb2abc58d456f52c4ff64d9724d88acb85ead560b6" "checksum toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "fcd27a04ca509aff336ba5eb2abc58d456f52c4ff64d9724d88acb85ead560b6"
"checksum toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a442dfc13508e603c3f763274361db7f79d7469a0e95c411cde53662ab30fc72" "checksum toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a442dfc13508e603c3f763274361db7f79d7469a0e95c411cde53662ab30fc72"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"

View File

@ -258,7 +258,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
}.fake_sign(req.from); }.fake_sign(req.from);
self.prove_transaction(transaction, id) self.prove_transaction(transaction, id)
.map(|proof| ::request::ExecutionResponse { items: proof }) .map(|(_, proof)| ::request::ExecutionResponse { items: proof })
} }
fn ready_transactions(&self) -> Vec<PendingTransaction> { fn ready_transactions(&self) -> Vec<PendingTransaction> {

View File

@ -22,9 +22,17 @@ use std::io::Write;
// TODO: `include!` these from files where they're pretty-printed? // TODO: `include!` these from files where they're pretty-printed?
const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#; const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#;
const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#; const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#;
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#; const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
// be very careful changing these: ensure `ethcore/engines` validator sets have corresponding
// changes.
const VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"}]"#;
const VALIDATOR_REPORT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}]"#;
fn build_file(name: &str, abi: &str, filename: &str) { fn build_file(name: &str, abi: &str, filename: &str) {
let code = ::native_contract_generator::generate_module(name, abi).unwrap(); let code = ::native_contract_generator::generate_module(name, abi).unwrap();
@ -39,4 +47,6 @@ fn main() {
build_file("Registry", REGISTRY_ABI, "registry.rs"); build_file("Registry", REGISTRY_ABI, "registry.rs");
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs"); build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
build_file("ValidatorSet", VALIDATOR_SET_ABI, "validator_set.rs");
build_file("ValidatorReport", VALIDATOR_REPORT_ABI, "validator_report.rs");
} }

View File

@ -46,8 +46,8 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result<String, Error> {
Ok(format!(r##" Ok(format!(r##"
use byteorder::{{BigEndian, ByteOrder}}; use byteorder::{{BigEndian, ByteOrder}};
use futures::{{future, Future, BoxFuture}}; use futures::{{future, Future, IntoFuture, BoxFuture}};
use ethabi::{{Contract, Interface, Token}}; use ethabi::{{Contract, Interface, Token, Event}};
use util::{{self, Uint}}; use util::{{self, Uint}};
pub struct {name} {{ pub struct {name} {{
@ -70,6 +70,11 @@ impl {name} {{
}} }}
}} }}
/// Access the underlying `ethabi` contract.
pub fn contract(this: &Self) -> &Contract {{
&this.contract
}}
{functions} {functions}
}} }}
"##, "##,
@ -99,7 +104,10 @@ fn generate_functions(contract: &Contract) -> Result<String, Error> {
/// Inputs: {abi_inputs:?} /// Inputs: {abi_inputs:?}
/// Outputs: {abi_outputs:?} /// Outputs: {abi_outputs:?}
pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String> pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String>
where F: Fn(util::Address, Vec<u8>) -> U, U: Future<Item=Vec<u8>, Error=String> + Send + 'static where
F: Fn(util::Address, Vec<u8>) -> U,
U: IntoFuture<Item=Vec<u8>, Error=String>,
U::Future: Send + 'static
{{ {{
let function = self.contract.function(r#"{abi_name}"#.to_string()) let function = self.contract.function(r#"{abi_name}"#.to_string())
.expect("function existence checked at compile-time; qed"); .expect("function existence checked at compile-time; qed");
@ -111,6 +119,7 @@ pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type},
}}; }};
call_future call_future
.into_future()
.and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e))) .and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e)))
.map(::std::collections::VecDeque::from) .map(::std::collections::VecDeque::from)
.and_then(|mut outputs| {decode_outputs}) .and_then(|mut outputs| {decode_outputs})
@ -299,10 +308,10 @@ fn detokenize(name: &str, output_type: ParamType) -> String {
ParamType::Bool => format!("{}.to_bool()", name), ParamType::Bool => format!("{}.to_bool()", name),
ParamType::String => format!("{}.to_string()", name), ParamType::String => format!("{}.to_string()", name),
ParamType::Array(kind) => { ParamType::Array(kind) => {
let read_array = format!("x.into_iter().map(|a| {{ {} }}).collect::<Option<Vec<_>>()", let read_array = format!("x.into_iter().map(|a| {{ {} }}).collect::<Option<Vec<_>>>()",
detokenize("a", *kind)); detokenize("a", *kind));
format!("{}.to_array().and_then(|x| {})", format!("{}.to_array().and_then(|x| {{ {} }})",
name, read_array) name, read_array)
} }
ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported.") ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported.")

View File

@ -26,7 +26,11 @@ extern crate ethcore_util as util;
mod registry; mod registry;
mod service_transaction; mod service_transaction;
mod secretstore_acl_storage; mod secretstore_acl_storage;
mod validator_set;
mod validator_report;
pub use self::registry::Registry; pub use self::registry::Registry;
pub use self::service_transaction::ServiceTransactionChecker; pub use self::service_transaction::ServiceTransactionChecker;
pub use self::secretstore_acl_storage::SecretStoreAclStorage; pub use self::secretstore_acl_storage::SecretStoreAclStorage;
pub use self::validator_set::ValidatorSet;
pub use self::validator_report::ValidatorReport;

View File

@ -0,0 +1,22 @@
// 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/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Validator reporting.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/validator_report.rs"));

View File

@ -0,0 +1,22 @@
// 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/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Validator set contract.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/validator_set.rs"));

View File

@ -484,7 +484,11 @@ impl LockedBlock {
/// Provide a valid seal in order to turn this into a `SealedBlock`. /// Provide a valid seal in order to turn this into a `SealedBlock`.
/// This does check the validity of `seal` with the engine. /// This does check the validity of `seal` with the engine.
/// Returns the `ClosedBlock` back again if the seal is no good. /// Returns the `ClosedBlock` back again if the seal is no good.
pub fn try_seal(self, engine: &Engine, seal: Vec<Bytes>) -> Result<SealedBlock, (Error, LockedBlock)> { pub fn try_seal(
self,
engine: &Engine,
seal: Vec<Bytes>,
) -> Result<SealedBlock, (Error, LockedBlock)> {
let mut s = self; let mut s = self;
s.block.header.set_seal(seal); s.block.header.set_seal(seal);
match engine.verify_block_seal(&s.block.header) { match engine.verify_block_seal(&s.block.header) {

View File

@ -419,6 +419,45 @@ impl<'a> Iterator for AncestryIter<'a> {
} }
} }
/// An iterator which walks all epoch transitions.
/// Returns epoch transitions.
pub struct EpochTransitionIter<'a> {
chain: &'a BlockChain,
prefix_iter: Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>,
}
impl<'a> Iterator for EpochTransitionIter<'a> {
type Item = (u64, EpochTransition);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.prefix_iter.next() {
Some((key, val)) => {
// iterator may continue beyond values beginning with this
// prefix.
if !key.starts_with(&EPOCH_KEY_PREFIX[..]) { return None }
let transitions: EpochTransitions = ::rlp::decode(&val[..]);
// if there are multiple candidates, at most one will be on the
// canon chain.
for transition in transitions.candidates.into_iter() {
let is_in_canon_chain = self.chain.block_hash(transition.block_number)
.map_or(false, |hash| hash == transition.block_hash);
if is_in_canon_chain {
return Some((transitions.number, transition))
}
}
// some epochs never occurred on the main chain.
}
None => return None,
}
}
}
}
impl BlockChain { impl BlockChain {
/// Create new instance of blockchain from given Genesis. /// Create new instance of blockchain from given Genesis.
pub fn new(config: Config, genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain { pub fn new(config: Config, genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain {
@ -804,6 +843,35 @@ impl BlockChain {
} }
} }
/// Insert an epoch transition. Provide an epoch number being transitioned to
/// and epoch transition object.
///
/// The block the transition occurred at should have already been inserted into the chain.
pub fn insert_epoch_transition(&self, batch: &mut DBTransaction, epoch_num: u64, transition: EpochTransition) {
let mut transitions = match self.db.read(db::COL_EXTRA, &epoch_num) {
Some(existing) => existing,
None => EpochTransitions {
number: epoch_num,
candidates: Vec::with_capacity(1),
}
};
// ensure we don't write any duplicates.
if transitions.candidates.iter().find(|c| c.block_hash == transition.block_hash).is_none() {
transitions.candidates.push(transition);
batch.write(db::COL_EXTRA, &epoch_num, &transitions);
}
}
/// Iterate over all epoch transitions.
pub fn epoch_transitions(&self) -> EpochTransitionIter {
let iter = self.db.iter_from_prefix(db::COL_EXTRA, &EPOCH_KEY_PREFIX[..]);
EpochTransitionIter {
chain: self,
prefix_iter: iter,
}
}
/// Add a child to a given block. Assumes that the block hash is in /// Add a child to a given block. Assumes that the block hash is in
/// the chain and the child's parent is this block. /// the chain and the child's parent is this block.
/// ///
@ -2114,4 +2182,58 @@ mod tests {
assert_eq!(bc.rewind(), Some(genesis_hash.clone())); assert_eq!(bc.rewind(), Some(genesis_hash.clone()));
assert_eq!(bc.rewind(), None); assert_eq!(bc.rewind(), None);
} }
#[test]
fn epoch_transitions_iter() {
use blockchain::extras::EpochTransition;
let mut canon_chain = ChainGenerator::default();
let mut finalizer = BlockFinalizer::default();
let genesis = canon_chain.generate(&mut finalizer).unwrap();
let db = new_db();
{
let bc = new_chain(&genesis, db.clone());
let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
let mut batch = db.transaction();
// create a longer fork
for i in 0..5 {
let canon_block = canon_chain.generate(&mut finalizer).unwrap();
let hash = BlockView::new(&canon_block).header_view().sha3();
bc.insert_block(&mut batch, &canon_block, vec![]);
bc.insert_epoch_transition(&mut batch, i, EpochTransition {
block_hash: hash,
block_number: i + 1,
proof: vec![],
state_proof: vec![],
});
bc.commit();
}
assert_eq!(bc.best_block_number(), 5);
let hash = BlockView::new(&uncle).header_view().sha3();
bc.insert_block(&mut batch, &uncle, vec![]);
bc.insert_epoch_transition(&mut batch, 999, EpochTransition {
block_hash: hash,
block_number: 1,
proof: vec![],
state_proof: vec![]
});
db.write(batch).unwrap();
bc.commit();
// epoch 999 not in canonical chain.
assert_eq!(bc.epoch_transitions().map(|(i, _)| i).collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
}
// re-loading the blockchain should load the correct best block.
let bc = new_chain(&genesis, db);
assert_eq!(bc.best_block_number(), 5);
assert_eq!(bc.epoch_transitions().map(|(i, _)| i).collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
}
} }

View File

@ -18,6 +18,7 @@
use bloomchain; use bloomchain;
use util::*; use util::*;
use util::kvdb::PREFIX_LEN as DB_PREFIX_LEN;
use rlp::*; use rlp::*;
use header::BlockNumber; use header::BlockNumber;
use receipt::Receipt; use receipt::Receipt;
@ -37,6 +38,8 @@ pub enum ExtrasIndex {
BlocksBlooms = 3, BlocksBlooms = 3,
/// Block receipts index /// Block receipts index
BlockReceipts = 4, BlockReceipts = 4,
/// Epoch transition data index.
EpochTransitions = 5,
} }
fn with_index(hash: &H256, i: ExtrasIndex) -> H264 { fn with_index(hash: &H256, i: ExtrasIndex) -> H264 {
@ -134,6 +137,36 @@ impl Key<BlockReceipts> for H256 {
} }
} }
/// length of epoch keys.
pub const EPOCH_KEY_LEN: usize = DB_PREFIX_LEN + 16;
/// epoch key prefix.
/// used to iterate over all epoch transitions in order from genesis.
pub const EPOCH_KEY_PREFIX: &'static [u8; DB_PREFIX_LEN] = &[
ExtrasIndex::EpochTransitions as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
pub struct EpochTransitionsKey([u8; EPOCH_KEY_LEN]);
impl Deref for EpochTransitionsKey {
type Target = [u8];
fn deref(&self) -> &[u8] { &self.0[..] }
}
impl Key<EpochTransitions> for u64 {
type Target = EpochTransitionsKey;
fn key(&self) -> Self::Target {
let mut arr = [0u8; EPOCH_KEY_LEN];
arr[..DB_PREFIX_LEN].copy_from_slice(&EPOCH_KEY_PREFIX[..]);
write!(&mut arr[DB_PREFIX_LEN..], "{:016x}", self)
.expect("format arg is valid; no more than 16 chars will be written; qed");
EpochTransitionsKey(arr)
}
}
/// Familial details concerning a block /// Familial details concerning a block
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BlockDetails { pub struct BlockDetails {
@ -144,7 +177,7 @@ pub struct BlockDetails {
/// Parent block hash /// Parent block hash
pub parent: H256, pub parent: H256,
/// List of children block hashes /// List of children block hashes
pub children: Vec<H256> pub children: Vec<H256>,
} }
impl HeapSizeOf for BlockDetails { impl HeapSizeOf for BlockDetails {
@ -241,6 +274,63 @@ impl HeapSizeOf for BlockReceipts {
} }
} }
/// Candidate transitions to an epoch with specific number.
#[derive(Clone)]
pub struct EpochTransitions {
pub number: u64,
pub candidates: Vec<EpochTransition>,
}
impl Encodable for EpochTransitions {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(2).append(&self.number).append_list(&self.candidates);
}
}
impl Decodable for EpochTransitions {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(EpochTransitions {
number: rlp.val_at(0)?,
candidates: rlp.list_at(1)?,
})
}
}
#[derive(Debug, Clone)]
pub struct EpochTransition {
pub block_hash: H256, // block hash at which the transition occurred.
pub block_number: BlockNumber, // block number at which the tranition occurred.
pub proof: Vec<u8>, // "transition/epoch" proof from the engine.
pub state_proof: Vec<DBValue>, // state items necessary to regenerate proof.
}
impl Encodable for EpochTransition {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4)
.append(&self.block_hash)
.append(&self.block_number)
.append(&self.proof)
.begin_list(self.state_proof.len());
for item in &self.state_proof {
s.append(&&**item);
}
}
}
impl Decodable for EpochTransition {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(EpochTransition {
block_hash: rlp.val_at(0)?,
block_number: rlp.val_at(1)?,
proof: rlp.val_at(2)?,
state_proof: rlp.at(3)?.iter().map(|x| {
Ok(DBValue::from_slice(x.data()?))
}).collect::<Result<Vec<_>, _>>()?,
})
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rlp::*; use rlp::*;

View File

@ -31,5 +31,6 @@ pub mod generator;
pub use self::blockchain::{BlockProvider, BlockChain}; pub use self::blockchain::{BlockProvider, BlockChain};
pub use self::cache::CacheSize; pub use self::cache::CacheSize;
pub use self::config::Config; pub use self::config::Config;
pub use self::extras::EpochTransition;
pub use types::tree_route::TreeRoute; pub use types::tree_route::TreeRoute;
pub use self::import_route::ImportRoute; pub use self::import_route::ImportRoute;

View File

@ -32,13 +32,13 @@ use util::kvdb::*;
// other // other
use basic_types::Seal; use basic_types::Seal;
use block::*; use block::*;
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use blockchain::{BlockChain, BlockProvider, EpochTransition, TreeRoute, ImportRoute};
use blockchain::extras::TransactionAddress; use blockchain::extras::TransactionAddress;
use client::Error as ClientError; use client::Error as ClientError;
use client::{ use client::{
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode, MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
ChainNotify, PruningInfo, ChainNotify, PruningInfo, ProvingBlockChainClient,
}; };
use encoded; use encoded;
use engines::Engine; use engines::Engine;
@ -49,7 +49,7 @@ use evm::{Factory as EvmFactory, Schedule};
use executive::{Executive, Executed, TransactOptions, contract_address}; use executive::{Executive, Executed, TransactOptions, contract_address};
use factory::Factories; use factory::Factories;
use futures::{future, Future}; use futures::{future, Future};
use header::BlockNumber; use header::{BlockNumber, Header};
use io::*; use io::*;
use log_entry::LocalizedLogEntry; use log_entry::LocalizedLogEntry;
use miner::{Miner, MinerService, TransactionImportResult}; use miner::{Miner, MinerService, TransactionImportResult};
@ -247,17 +247,27 @@ impl Client {
exit_handler: Mutex::new(None), exit_handler: Mutex::new(None),
}); });
// prune old states.
{ {
let state_db = client.state_db.lock().boxed_clone(); let state_db = client.state_db.lock().boxed_clone();
let chain = client.chain.read(); let chain = client.chain.read();
client.prune_ancient(state_db, &chain)?; client.prune_ancient(state_db, &chain)?;
} }
// ensure genesis epoch proof in the DB.
{
let chain = client.chain.read();
client.generate_epoch_proof(&spec.genesis_header(), 0, &*chain);
}
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
trace!(target: "client", "Found registrar at {}", reg_addr); trace!(target: "client", "Found registrar at {}", reg_addr);
let registrar = Registry::new(reg_addr); let registrar = Registry::new(reg_addr);
*client.registrar.lock() = Some(registrar); *client.registrar.lock() = Some(registrar);
} }
// ensure buffered changes are flushed.
client.db.read().flush().map_err(ClientError::Database)?;
Ok(client) Ok(client)
} }
@ -380,6 +390,12 @@ impl Client {
return Err(()); return Err(());
}; };
let verify_external_result = self.verifier.verify_block_external(header, &block.bytes, engine);
if let Err(e) = verify_external_result {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
return Err(());
};
// Check if Parent is in chain // Check if Parent is in chain
let chain_has_parent = chain.block_header(header.parent_hash()); let chain_has_parent = chain.block_header(header.parent_hash());
if let Some(parent) = chain_has_parent { if let Some(parent) = chain_has_parent {
@ -398,7 +414,7 @@ impl Client {
// Final Verification // Final Verification
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); warn!(target: "client", "Stage 5 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
return Err(()); return Err(());
} }
@ -569,6 +585,22 @@ impl Client {
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new)); //let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
let mut batch = DBTransaction::new(); let mut batch = DBTransaction::new();
// generate validation proof if the engine requires them.
// TODO: make conditional?
let entering_new_epoch = {
use engines::EpochChange;
match self.engine.is_epoch_end(block.header(), Some(block_data), Some(&receipts)) {
EpochChange::Yes(e, _) => Some((block.header().clone(), e)),
EpochChange::No => None,
EpochChange::Unsure(_) => {
warn!(target: "client", "Detected invalid engine implementation.");
warn!(target: "client", "Engine claims to require more block data, but everything provided.");
None
}
}
};
// CHECK! I *think* this is fine, even if the state_root is equal to another // CHECK! I *think* this is fine, even if the state_root is equal to another
// already-imported block of the same number. // already-imported block of the same number.
// TODO: Prove it with a test. // TODO: Prove it with a test.
@ -576,6 +608,7 @@ impl Client {
state.journal_under(&mut batch, number, hash).expect("DB commit failed"); state.journal_under(&mut batch, number, hash).expect("DB commit failed");
let route = chain.insert_block(&mut batch, block_data, receipts); let route = chain.insert_block(&mut batch, block_data, receipts);
self.tracedb.read().import(&mut batch, TraceImportRequest { self.tracedb.read().import(&mut batch, TraceImportRequest {
traces: traces.into(), traces: traces.into(),
block_hash: hash.clone(), block_hash: hash.clone(),
@ -595,9 +628,58 @@ impl Client {
warn!("Failed to prune ancient state data: {}", e); warn!("Failed to prune ancient state data: {}", e);
} }
if let Some((header, epoch)) = entering_new_epoch {
self.generate_epoch_proof(&header, epoch, &chain);
}
route route
} }
// generate an epoch transition proof at the given block, and write it into the given blockchain.
fn generate_epoch_proof(&self, header: &Header, epoch_number: u64, chain: &BlockChain) {
use std::cell::RefCell;
use std::collections::BTreeSet;
let mut batch = DBTransaction::new();
let hash = header.hash();
debug!(target: "client", "Generating validation proof for block {}", hash);
// proof is two-part. state items read in lexicographical order,
// and the secondary "proof" part.
let read_values = RefCell::new(BTreeSet::new());
let block_id = BlockId::Hash(hash.clone());
let proof = {
let call = |a, d| {
let tx = self.contract_call_tx(block_id, a, d);
let (result, items) = self.prove_transaction(tx, block_id)
.ok_or_else(|| format!("Unable to make call to generate epoch proof."))?;
read_values.borrow_mut().extend(items);
Ok(result)
};
self.engine.epoch_proof(&header, &call)
};
// insert into database, using the generated proof.
match proof {
Ok(proof) => {
chain.insert_epoch_transition(&mut batch, epoch_number, EpochTransition {
block_hash: hash.clone(),
block_number: header.number(),
proof: proof,
state_proof: read_values.into_inner().into_iter().collect(),
});
self.db.read().write_buffered(batch);
}
Err(e) => {
warn!(target: "client", "Error generating epoch change proof for block {}: {}", hash, e);
warn!(target: "client", "Snapshots generated by this node will be incomplete.");
}
}
}
// prune ancient states until below the memory limit or only the minimum amount remain. // prune ancient states until below the memory limit or only the minimum amount remain.
fn prune_ancient(&self, mut state_db: StateDB, chain: &BlockChain) -> Result<(), ClientError> { fn prune_ancient(&self, mut state_db: StateDB, chain: &BlockChain) -> Result<(), ClientError> {
let number = match state_db.journal_db().latest_era() { let number = match state_db.journal_db().latest_era() {
@ -814,7 +896,7 @@ impl Client {
}, },
}; };
snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p)?; snapshot::take_snapshot(&*self.engine, &self.chain.read(), start_hash, db.as_hashdb(), writer, p)?;
Ok(()) Ok(())
} }
@ -865,6 +947,20 @@ impl Client {
} }
} }
} }
// transaction for calling contracts from services like engine.
// from the null sender, with 50M gas.
fn contract_call_tx(&self, block_id: BlockId, address: Address, data: Bytes) -> SignedTransaction {
let from = Address::default();
Transaction {
nonce: self.nonce(&from, block_id).unwrap_or_else(|| self.engine.account_start_nonce()),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
value: U256::default(),
data: data,
}.fake_sign(from)
}
} }
impl snapshot::DatabaseRestore for Client { impl snapshot::DatabaseRestore for Client {
@ -1456,15 +1552,7 @@ impl BlockChainClient for Client {
} }
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> { fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
let from = Address::default(); let transaction = self.contract_call_tx(block_id, address, data);
let transaction = Transaction {
nonce: self.latest_nonce(&from),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
value: U256::default(),
data: data,
}.fake_sign(from);
self.call(&transaction, block_id, Default::default()) self.call(&transaction, block_id, Default::default())
.map_err(|e| format!("{:?}", e)) .map_err(|e| format!("{:?}", e))
@ -1620,7 +1708,7 @@ impl MayPanic for Client {
} }
} }
impl ::client::ProvingBlockChainClient for Client { impl ProvingBlockChainClient for Client {
fn prove_storage(&self, key1: H256, key2: H256, id: BlockId) -> Option<(Vec<Bytes>, H256)> { fn prove_storage(&self, key1: H256, key2: H256, id: BlockId) -> Option<(Vec<Bytes>, H256)> {
self.state_at(id) self.state_at(id)
.and_then(move |state| state.prove_storage(key1, key2).ok()) .and_then(move |state| state.prove_storage(key1, key2).ok())
@ -1631,7 +1719,7 @@ impl ::client::ProvingBlockChainClient for Client {
.and_then(move |state| state.prove_account(key1).ok()) .and_then(move |state| state.prove_account(key1).ok())
} }
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> { fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec<DBValue>)> {
let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) { let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) {
(Some(s), Some(e)) => (s, e), (Some(s), Some(e)) => (s, e),
_ => return None, _ => return None,
@ -1646,8 +1734,9 @@ impl ::client::ProvingBlockChainClient for Client {
let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options); let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options);
match res { match res {
Err(ExecutionError::Internal(_)) => return None, Err(ExecutionError::Internal(_)) => None,
_ => return Some(state.drop().1.extract_proof()), Err(_) => Some((Vec::new(), state.drop().1.extract_proof())),
Ok(res) => Some((res.output, state.drop().1.extract_proof())),
} }
} }
} }

View File

@ -769,7 +769,7 @@ impl ProvingBlockChainClient for TestBlockChainClient {
None None
} }
fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option<Vec<DBValue>> { fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option<(Bytes, Vec<DBValue>)> {
None None
} }
} }

View File

@ -327,5 +327,7 @@ pub trait ProvingBlockChainClient: BlockChainClient {
fn prove_account(&self, key1: H256, id: BlockId) -> Option<(Vec<Bytes>, BasicAccount)>; fn prove_account(&self, key1: H256, id: BlockId) -> Option<(Vec<Bytes>, BasicAccount)>;
/// Prove execution of a transaction at the given block. /// Prove execution of a transaction at the given block.
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>>; /// Returns the output of the call and a vector of database items necessary
/// to reproduce it.
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec<DBValue>)>;
} }

View File

@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, encode};
use account_provider::AccountProvider; use account_provider::AccountProvider;
use block::*; use block::*;
use spec::CommonParams; use spec::CommonParams;
use engines::{Engine, Seal, EngineError}; use engines::{Call, Engine, Seal, EngineError};
use header::{Header, BlockNumber}; use header::{Header, BlockNumber};
use error::{Error, TransactionError, BlockError}; use error::{Error, TransactionError, BlockError};
use evm::Schedule; use evm::Schedule;
@ -36,7 +36,7 @@ use transaction::UnverifiedTransaction;
use client::{Client, EngineClient}; use client::{Client, EngineClient};
use state::CleanupMode; use state::CleanupMode;
use super::signer::EngineSigner; use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, new_validator_set}; use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
/// `AuthorityRound` params. /// `AuthorityRound` params.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -74,27 +74,78 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
} }
} }
/// Engine using `AuthorityRound` proof-of-work consensus algorithm, suitable for Ethereum // Helper for managing the step.
/// mainnet chains in the Olympic, Frontier and Homestead eras. #[derive(Debug)]
struct Step {
calibrate: bool, // whether calibration is enabled.
inner: AtomicUsize,
duration: Duration,
}
impl Step {
fn load(&self) -> usize { self.inner.load(AtomicOrdering::SeqCst) }
fn duration_remaining(&self) -> Duration {
let now = unix_now();
let step_end = self.duration * (self.load() as u32 + 1);
if step_end > now {
step_end - now
} else {
Duration::from_secs(0)
}
}
fn increment(&self) {
self.inner.fetch_add(1, AtomicOrdering::SeqCst);
}
fn calibrate(&self) {
if self.calibrate {
let new_step = unix_now().as_secs() / self.duration.as_secs();
self.inner.store(new_step as usize, AtomicOrdering::SeqCst);
}
}
fn is_future(&self, given: usize) -> bool {
if given > self.load() + 1 {
// Make absolutely sure that the given step is correct.
self.calibrate();
given > self.load() + 1
} else {
false
}
}
}
/// Engine using `AuthorityRound` proof-of-authority BFT consensus.
pub struct AuthorityRound { pub struct AuthorityRound {
params: CommonParams, params: CommonParams,
gas_limit_bound_divisor: U256, gas_limit_bound_divisor: U256,
block_reward: U256, block_reward: U256,
registrar: Address, registrar: Address,
step_duration: Duration,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<()>, transition_service: IoService<()>,
step: AtomicUsize, step: Arc<Step>,
proposed: AtomicBool, proposed: AtomicBool,
client: RwLock<Option<Weak<EngineClient>>>, client: RwLock<Option<Weak<EngineClient>>>,
signer: EngineSigner, signer: EngineSigner,
validators: Box<ValidatorSet>, validators: Box<ValidatorSet>,
/// Is this Engine just for testing (prevents step calibration).
calibrate_step: bool,
validate_score_transition: u64, validate_score_transition: u64,
eip155_transition: u64, eip155_transition: u64,
} }
// header-chain validator.
struct EpochVerifier {
epoch_number: u64,
step: Arc<Step>,
subchain_validators: SimpleList,
}
impl super::EpochVerifier for EpochVerifier {
fn epoch_number(&self) -> u64 { self.epoch_number.clone() }
fn verify_light(&self, header: &Header) -> Result<(), Error> {
// always check the seal since it's fast.
// nothing heavier to do.
verify_external(header, &self.subchain_validators, &*self.step)
}
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> { fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val() UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val()
} }
@ -103,6 +154,26 @@ fn header_signature(header: &Header) -> Result<Signature, ::rlp::DecoderError> {
UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::<H520>().map(Into::into) UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::<H520>().map(Into::into)
} }
fn verify_external(header: &Header, validators: &ValidatorSet, step: &Step) -> Result<(), Error> {
let header_step = header_step(header)?;
// Give one step slack if step is lagging, double vote is still not possible.
if step.is_future(header_step) {
trace!(target: "engine", "verify_block_unordered: block from the future");
validators.report_benign(header.author());
Err(BlockError::InvalidSeal)?
} else {
let proposer_signature = header_signature(header)?;
let correct_proposer = validators.get(header.parent_hash(), header_step);
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
} else {
Ok(())
}
}
}
trait AsMillis { trait AsMillis {
fn as_millis(&self) -> u64; fn as_millis(&self) -> u64;
} }
@ -124,15 +195,17 @@ impl AuthorityRound {
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
block_reward: our_params.block_reward, block_reward: our_params.block_reward,
registrar: our_params.registrar, registrar: our_params.registrar,
step_duration: our_params.step_duration,
builtins: builtins, builtins: builtins,
transition_service: IoService::<()>::start()?, transition_service: IoService::<()>::start()?,
step: AtomicUsize::new(initial_step), step: Arc::new(Step {
inner: AtomicUsize::new(initial_step),
calibrate: our_params.start_step.is_none(),
duration: our_params.step_duration,
}),
proposed: AtomicBool::new(false), proposed: AtomicBool::new(false),
client: RwLock::new(None), client: RwLock::new(None),
signer: Default::default(), signer: Default::default(),
validators: new_validator_set(our_params.validators), validators: new_validator_set(our_params.validators),
calibrate_step: our_params.start_step.is_none(),
validate_score_transition: our_params.validate_score_transition, validate_score_transition: our_params.validate_score_transition,
eip155_transition: our_params.eip155_transition, eip155_transition: our_params.eip155_transition,
}); });
@ -144,22 +217,6 @@ impl AuthorityRound {
Ok(engine) Ok(engine)
} }
fn calibrate_step(&self) {
if self.calibrate_step {
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
}
}
fn remaining_step_duration(&self) -> Duration {
let now = unix_now();
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
if step_end > now {
step_end - now
} else {
Duration::from_secs(0)
}
}
fn step_proposer(&self, bh: &H256, step: usize) -> Address { fn step_proposer(&self, bh: &H256, step: usize) -> Address {
self.validators.get(bh, step) self.validators.get(bh, step)
} }
@ -167,16 +224,6 @@ impl AuthorityRound {
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool { fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
self.step_proposer(bh, step) == *address self.step_proposer(bh, step) == *address
} }
fn is_future_step(&self, step: usize) -> bool {
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
// Make absolutely sure that the step is correct.
self.calibrate_step();
step > self.step.load(AtomicOrdering::SeqCst) + 1
} else {
false
}
}
} }
fn unix_now() -> Duration { fn unix_now() -> Duration {
@ -192,7 +239,8 @@ const ENGINE_TIMEOUT_TOKEN: TimerToken = 23;
impl IoHandler<()> for TransitionHandler { 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()) let remaining = engine.step.duration_remaining();
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, remaining.as_millis())
.unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e)) .unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e))
} }
} }
@ -201,7 +249,8 @@ impl IoHandler<()> for TransitionHandler {
if timer == ENGINE_TIMEOUT_TOKEN { if timer == ENGINE_TIMEOUT_TOKEN {
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()) let remaining = engine.step.duration_remaining();
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, remaining.as_millis())
.unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e)) .unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e))
} }
} }
@ -223,7 +272,7 @@ impl Engine for AuthorityRound {
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
fn step(&self) { fn step(&self) {
self.step.fetch_add(1, AtomicOrdering::SeqCst); self.step.increment();
self.proposed.store(false, AtomicOrdering::SeqCst); self.proposed.store(false, AtomicOrdering::SeqCst);
if let Some(ref weak) = *self.client.read() { if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() { if let Some(c) = weak.upgrade() {
@ -247,7 +296,7 @@ impl Engine for AuthorityRound {
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
// Chain scoring: total weight is sqrt(U256::max_value())*height - step // Chain scoring: total weight is sqrt(U256::max_value())*height - step
let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load(AtomicOrdering::SeqCst).into(); let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load().into();
header.set_difficulty(new_difficulty); header.set_difficulty(new_difficulty);
header.set_gas_limit({ header.set_gas_limit({
let gas_limit = parent.gas_limit().clone(); let gas_limit = parent.gas_limit().clone();
@ -271,7 +320,7 @@ impl Engine for AuthorityRound {
fn generate_seal(&self, block: &ExecutedBlock) -> Seal { fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
let header = block.header(); let header = block.header();
let step = self.step.load(AtomicOrdering::SeqCst); let step = self.step.load();
if self.is_step_proposer(header.parent_hash(), step, header.author()) { if self.is_step_proposer(header.parent_hash(), step, header.author()) {
if let Ok(signature) = self.signer.sign(header.bare_hash()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) {
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
@ -319,32 +368,19 @@ impl Engine for AuthorityRound {
Ok(()) Ok(())
} }
/// Do the validator and gas limit validation. /// Do the step and gas limit validation.
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let step = header_step(header)?; let step = header_step(header)?;
// Give one step slack if step is lagging, double vote is still not possible.
if self.is_future_step(step) {
trace!(target: "engine", "verify_block_unordered: block from the future");
self.validators.report_benign(header.author());
Err(BlockError::InvalidSeal)?
} else {
let proposer_signature = header_signature(header)?;
let correct_proposer = self.step_proposer(header.parent_hash(), step);
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
}
}
// Do not calculate difficulty for genesis blocks. // Do not calculate difficulty for genesis blocks.
if header.number() == 0 { if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
} }
// Check if parent is from a previous step. // Ensure header is from the step after parent.
let parent_step = header_step(parent)?; let parent_step = header_step(parent)?;
if step == parent_step { if step <= parent_step {
trace!(target: "engine", "Multiple blocks proposed for step {}.", step); trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_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()))?;
} }
@ -358,6 +394,34 @@ impl Engine for AuthorityRound {
Ok(()) Ok(())
} }
// Check the validators.
fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
verify_external(header, &*self.validators, &*self.step)
}
// the proofs we need just allow us to get the full validator set.
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Bytes, Error> {
self.validators.epoch_proof(header, caller)
.map_err(|e| EngineError::InsufficientProof(e).into())
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
}
fn epoch_verifier(&self, header: &Header, proof: &[u8]) -> Result<Box<super::EpochVerifier>, Error> {
// extract a simple list from the proof.
let (num, simple_list) = self.validators.epoch_set(header, proof)?;
Ok(Box::new(EpochVerifier {
epoch_number: num,
step: self.step.clone(),
subchain_validators: simple_list,
}))
}
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> { fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> {
t.check_low_s()?; t.check_low_s()?;
@ -432,7 +496,7 @@ mod tests {
let mut header: Header = Header::default(); let mut header: Header = Header::default();
header.set_seal(vec![encode(&H520::default()).to_vec()]); header.set_seal(vec![encode(&H520::default()).to_vec()]);
let verify_result = engine.verify_block_family(&header, &Default::default(), None); let verify_result = engine.verify_block_external(&header, None);
assert!(verify_result.is_err()); assert!(verify_result.is_err());
} }
@ -486,9 +550,11 @@ mod tests {
// Two validators. // Two validators.
// Spec starts with step 2. // Spec starts with step 2.
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
assert!(engine.verify_block_external(&header, None).is_err());
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
assert!(engine.verify_block_external(&header, None).is_ok());
} }
#[test] #[test]
@ -511,7 +577,33 @@ mod tests {
// Spec starts with step 2. // Spec starts with step 2.
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
assert!(engine.verify_block_external(&header, None).is_ok());
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
assert!(engine.verify_block_external(&header, None).is_err());
}
#[test]
fn rejects_step_backwards() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&4usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(addr);
let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
header.set_seal(vec![encode(&3usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
} }
} }

View File

@ -23,14 +23,14 @@ use account_provider::AccountProvider;
use block::*; use block::*;
use builtin::Builtin; use builtin::Builtin;
use spec::CommonParams; use spec::CommonParams;
use engines::{Engine, Seal}; use engines::{Engine, EngineError, Seal, Call, EpochChange};
use error::{BlockError, Error}; use error::{BlockError, Error};
use evm::Schedule; use evm::Schedule;
use ethjson; use ethjson;
use header::{Header, BlockNumber}; use header::{Header, BlockNumber};
use client::Client; use client::Client;
use super::signer::EngineSigner; use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, new_validator_set}; use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
/// `BasicAuthority` params. /// `BasicAuthority` params.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -50,8 +50,32 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
} }
} }
/// Engine using `BasicAuthority` proof-of-work consensus algorithm, suitable for Ethereum struct EpochVerifier {
/// mainnet chains in the Olympic, Frontier and Homestead eras. epoch_number: u64,
list: SimpleList,
}
impl super::EpochVerifier for EpochVerifier {
fn epoch_number(&self) -> u64 { self.epoch_number.clone() }
fn verify_light(&self, header: &Header) -> Result<(), Error> {
verify_external(header, &self.list)
}
}
fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Error> {
use rlp::UntrustedRlp;
// Check if the signature belongs to a validator, can depend on parent state.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
match validators.contains(header.parent_hash(), &signer) {
false => Err(BlockError::InvalidSeal.into()),
true => Ok(())
}
}
/// Engine using `BasicAuthority`, trivial proof-of-authority consensus.
pub struct BasicAuthority { pub struct BasicAuthority {
params: CommonParams, params: CommonParams,
gas_limit_bound_divisor: U256, gas_limit_bound_divisor: U256,
@ -137,14 +161,6 @@ impl Engine for BasicAuthority {
} }
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
use rlp::UntrustedRlp;
// Check if the signature belongs to a validator, can depend on parent state.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.validators.contains(header.parent_hash(), &signer) {
return Err(BlockError::InvalidSeal)?;
}
// Do not calculate difficulty for genesis blocks. // Do not calculate difficulty for genesis blocks.
if header.number() == 0 { if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
@ -163,6 +179,32 @@ impl Engine for BasicAuthority {
Ok(()) Ok(())
} }
fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
verify_external(header, &*self.validators)
}
// the proofs we need just allow us to get the full validator set.
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Bytes, Error> {
self.validators.epoch_proof(header, caller)
.map_err(|e| EngineError::InsufficientProof(e).into())
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
}
fn epoch_verifier(&self, header: &Header, proof: &[u8]) -> Result<Box<super::EpochVerifier>, Error> {
// extract a simple list from the proof.
let (num, simple_list) = self.validators.epoch_set(header, proof)?;
Ok(Box::new(EpochVerifier {
epoch_number: num,
list: simple_list,
}))
}
fn register_client(&self, client: Weak<Client>) { fn register_client(&self, client: Weak<Client>) {
self.validators.register_contract(client); self.validators.register_contract(client);
} }

View File

@ -0,0 +1,45 @@
// 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/>.
// Epoch verifiers.
use error::Error;
use header::Header;
/// Verifier for all blocks within an epoch with self-contained state.
///
/// See docs on `Engine` relating to proving functions for more details.
pub trait EpochVerifier: Sync {
/// Get the epoch number.
fn epoch_number(&self) -> u64;
/// Lightly verify the next block header.
/// This may not be a header belonging to a different epoch.
fn verify_light(&self, header: &Header) -> Result<(), Error>;
/// Perform potentially heavier checks on the next block header.
fn verify_heavy(&self, header: &Header) -> Result<(), Error> {
self.verify_light(header)
}
}
/// Special "no-op" verifier for stateless, epoch-less engines.
pub struct NoOp;
impl EpochVerifier for NoOp {
fn epoch_number(&self) -> u64 { 0 }
fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
}

View File

@ -16,35 +16,41 @@
//! Consensus engine specification and basic implementations. //! Consensus engine specification and basic implementations.
mod transition;
mod vote_collector;
mod null_engine;
mod instant_seal;
mod basic_authority;
mod authority_round; mod authority_round;
mod tendermint; mod basic_authority;
mod validator_set; mod epoch_verifier;
mod instant_seal;
mod null_engine;
mod signer; mod signer;
mod tendermint;
mod transition;
mod validator_set;
mod vote_collector;
pub use self::null_engine::NullEngine;
pub use self::instant_seal::InstantSeal;
pub use self::basic_authority::BasicAuthority;
pub use self::authority_round::AuthorityRound; pub use self::authority_round::AuthorityRound;
pub use self::basic_authority::BasicAuthority;
pub use self::epoch_verifier::EpochVerifier;
pub use self::instant_seal::InstantSeal;
pub use self::null_engine::NullEngine;
pub use self::tendermint::Tendermint; pub use self::tendermint::Tendermint;
use std::sync::Weak; use std::sync::Weak;
use util::*;
use ethkey::Signature;
use account_provider::AccountProvider; use account_provider::AccountProvider;
use block::ExecutedBlock; use block::ExecutedBlock;
use builtin::Builtin; use builtin::Builtin;
use client::Client;
use env_info::EnvInfo; use env_info::EnvInfo;
use error::Error; use error::Error;
use spec::CommonParams;
use evm::Schedule; use evm::Schedule;
use header::{Header, BlockNumber}; use header::{Header, BlockNumber};
use receipt::Receipt;
use snapshot::SnapshotComponents;
use spec::CommonParams;
use transaction::{UnverifiedTransaction, SignedTransaction}; use transaction::{UnverifiedTransaction, SignedTransaction};
use client::Client;
use ethkey::Signature;
use util::*;
/// Voting errors. /// Voting errors.
#[derive(Debug)] #[derive(Debug)]
@ -59,6 +65,8 @@ pub enum EngineError {
UnexpectedMessage, UnexpectedMessage,
/// Seal field has an unexpected size. /// Seal field has an unexpected size.
BadSealFieldSize(OutOfBounds<usize>), BadSealFieldSize(OutOfBounds<usize>),
/// Validation proof insufficient.
InsufficientProof(String),
} }
impl fmt::Display for EngineError { impl fmt::Display for EngineError {
@ -70,6 +78,7 @@ impl fmt::Display for EngineError {
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
UnexpectedMessage => "This Engine should not be fed messages.".into(), UnexpectedMessage => "This Engine should not be fed messages.".into(),
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob), BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
}; };
f.write_fmt(format_args!("Engine error ({})", msg)) f.write_fmt(format_args!("Engine error ({})", msg))
@ -87,6 +96,31 @@ pub enum Seal {
None, None,
} }
/// Type alias for a function we can make calls through synchronously.
pub type Call<'a> = Fn(Address, Bytes) -> Result<Bytes, String> + 'a;
/// Results of a query of whether an epoch change occurred at the given block.
#[derive(Debug, Clone, PartialEq)]
pub enum EpochChange {
/// Cannot determine until more data is passed.
Unsure(Unsure),
/// No epoch change.
No,
/// Validation proof required, and the new epoch number and expected proof.
Yes(u64, Bytes),
}
/// More data required to determine if an epoch change occurred at a given block.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Unsure {
/// Needs the body.
NeedsBody,
/// Needs the receipts.
NeedsReceipts,
/// Needs both body and receipts.
NeedsBoth,
}
/// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based.
/// Provides hooks into each of the major parts of block import. /// Provides hooks into each of the major parts of block import.
pub trait Engine : Sync + Send { pub trait Engine : Sync + Send {
@ -152,6 +186,9 @@ pub trait Engine : Sync + Send {
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
/// Phase 4 verification. Verify block header against potentially external data.
fn verify_block_external(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
/// Additional verification for transactions in blocks. /// Additional verification for transactions in blocks.
// TODO: Add flags for which bits of the transaction to check. // TODO: Add flags for which bits of the transaction to check.
// TODO: consider including State in the params. // TODO: consider including State in the params.
@ -177,6 +214,40 @@ pub trait Engine : Sync + Send {
self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None)) self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None))
} }
/// Generate epoch change proof.
///
/// This will be used to generate proofs of epoch change as well as verify them.
/// Must be called on blocks that have already passed basic verification.
///
/// Return the "epoch proof" generated.
/// This must be usable to generate a `EpochVerifier` for verifying all blocks
/// from the supplied header up to the next one where proof is required.
///
/// For example, for PoA chains the proof will be a validator set,
/// and the corresponding `EpochVerifier` can be used to correctly validate
/// all blocks produced under that `ValidatorSet`
fn epoch_proof(&self, _header: &Header, _caller: &Call)
-> Result<Vec<u8>, Error>
{
Ok(Vec::new())
}
/// Whether an epoch change occurred at the given header.
/// Should not interact with state.
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[Receipt]>)
-> EpochChange
{
EpochChange::No
}
/// Create an epoch verifier from validation proof.
///
/// The proof should be one generated by `epoch_proof`.
/// See docs of `epoch_proof` for description.
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<EpochVerifier>, Error> {
Ok(Box::new(self::epoch_verifier::NoOp))
}
/// Populate a header's fields based on its parent's header. /// Populate a header's fields based on its parent's header.
/// Usually implements the chain scoring rule based on weight. /// Usually implements the chain scoring rule based on weight.
/// The gas floor target must not be lower than the engine's minimum gas limit. /// The gas floor target must not be lower than the engine's minimum gas limit.
@ -217,4 +288,10 @@ pub trait Engine : Sync + Send {
/// Stops any services that the may hold the Engine and makes it safe to drop. /// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {} fn stop(&self) {}
/// Create a factory for building snapshot chunks and restoring from them.
/// Returning `None` indicates that this engine doesn't support snapshot creation.
fn snapshot_components(&self) -> Option<Box<SnapshotComponents>> {
None
}
} }

View File

@ -60,4 +60,8 @@ impl Engine for NullEngine {
fn schedule(&self, _block_number: BlockNumber) -> Schedule { fn schedule(&self, _block_number: BlockNumber) -> Schedule {
Schedule::new_homestead() Schedule::new_homestead()
} }
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PowSnapshot(10000)))
}
} }

View File

@ -19,119 +19,96 @@
use std::sync::Weak; use std::sync::Weak;
use util::*; use util::*;
use futures::Future;
use native_contracts::ValidatorReport as Provider;
use client::{Client, BlockChainClient}; use client::{Client, BlockChainClient};
use engines::Call;
use header::Header;
use super::ValidatorSet; use super::ValidatorSet;
use super::safe_contract::ValidatorSafeContract; use super::safe_contract::ValidatorSafeContract;
/// The validator contract should have the following interface: /// A validator contract with reporting.
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorContract { pub struct ValidatorContract {
validators: ValidatorSafeContract, validators: ValidatorSafeContract,
provider: RwLock<Option<provider::Contract>>, provider: Provider,
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
} }
impl ValidatorContract { impl ValidatorContract {
pub fn new(contract_address: Address) -> Self { pub fn new(contract_address: Address) -> Self {
ValidatorContract { ValidatorContract {
validators: ValidatorSafeContract::new(contract_address), validators: ValidatorSafeContract::new(contract_address),
provider: RwLock::new(None), provider: Provider::new(contract_address),
client: RwLock::new(None),
} }
} }
} }
impl ValidatorContract {
// could be `impl Trait`.
// note: dispatches transactions to network as well as execute.
// TODO [keorn]: Make more general.
fn transact(&self) -> Box<Call> {
let client = self.client.read().clone();
Box::new(move |a, d| client.as_ref()
.and_then(Weak::upgrade)
.ok_or("No client!".into())
.and_then(|c| c.transact_contract(a, d).map_err(|e| format!("Transaction import error: {}", e)))
.map(|_| Default::default()))
}
}
impl ValidatorSet for ValidatorContract { impl ValidatorSet for ValidatorContract {
fn contains(&self, bh: &H256, address: &Address) -> bool { fn default_caller(&self, id: ::ids::BlockId) -> Box<Call> {
self.validators.contains(bh, address) self.validators.default_caller(id)
} }
fn get(&self, bh: &H256, nonce: usize) -> Address { fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
self.validators.get(bh, nonce) -> ::engines::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
} }
fn count(&self, bh: &H256) -> usize { fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
self.validators.count(bh) self.validators.epoch_proof(header, caller)
}
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
self.validators.epoch_set(header, proof)
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
self.validators.contains_with_caller(bh, address, caller)
}
fn get_with_caller(&self, bh: &H256, nonce: usize, caller: &Call) -> Address {
self.validators.get_with_caller(bh, nonce, caller)
}
fn count_with_caller(&self, bh: &H256, caller: &Call) -> usize {
self.validators.count_with_caller(bh, caller)
} }
fn report_malicious(&self, address: &Address) { fn report_malicious(&self, address: &Address) {
if let Some(ref provider) = *self.provider.read() { match self.provider.report_malicious(&*self.transact(), *address).wait() {
match provider.report_malicious(address) {
Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address), Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
} }
} else {
warn!(target: "engine", "Malicious behaviour could not be reported: no provider contract.")
}
} }
fn report_benign(&self, address: &Address) { fn report_benign(&self, address: &Address) {
if let Some(ref provider) = *self.provider.read() { match self.provider.report_benign(&*self.transact(), *address).wait() {
match provider.report_benign(address) {
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
} }
} else {
warn!(target: "engine", "Benign misbehaviour could not be reported: no provider contract.")
}
} }
fn register_contract(&self, client: Weak<Client>) { fn register_contract(&self, client: Weak<Client>) {
self.validators.register_contract(client.clone()); self.validators.register_contract(client.clone());
let transact = move |a, d| client *self.client.write() = Some(client);
.upgrade()
.ok_or("No client!".into())
.and_then(|c| c.transact_contract(a, d).map_err(|e| format!("Transaction import error: {}", e)))
.map(|_| Default::default());
*self.provider.write() = Some(provider::Contract::new(self.validators.address, transact));
}
}
mod provider {
// Autogenerated from JSON contract definition using Rust contract convertor.
#![allow(unused_imports)]
use std::string::String;
use std::result::Result;
use std::fmt;
use {util, ethabi};
use util::{Uint};
pub struct Contract {
contract: ethabi::Contract,
address: util::Address,
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
}
impl Contract {
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
Contract {
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportMalicious\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportBenign\",\"outputs\":[],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
address: address,
do_call: Box::new(do_call),
}
}
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
/// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"}`
#[allow(dead_code)]
pub fn report_malicious(&self, validator: &util::Address) -> Result<(), String> {
let call = self.contract.function("reportMalicious".into()).map_err(Self::as_string)?;
let data = call.encode_call(
vec![ethabi::Token::Address(validator.clone().0)]
).map_err(Self::as_string)?;
call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
Ok(())
}
/// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}`
#[allow(dead_code)]
pub fn report_benign(&self, validator: &util::Address) -> Result<(), String> {
let call = self.contract.function("reportBenign".into()).map_err(Self::as_string)?;
let data = call.encode_call(
vec![ethabi::Token::Address(validator.clone().0)]
).map_err(Self::as_string)?;
call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
Ok(())
}
} }
} }
@ -180,7 +157,7 @@ mod tests {
header.set_parent_hash(client.chain_info().best_block_hash); header.set_parent_hash(client.chain_info().best_block_hash);
// `reportBenign` when the designated proposer releases block from the future (bad clock). // `reportBenign` when the designated proposer releases block from the future (bad clock).
assert!(client.engine().verify_block_family(&header, &header, None).is_err()); assert!(client.engine().verify_block_external(&header, None).is_err());
// Seal a block. // Seal a block.
client.engine().step(); client.engine().step();
assert_eq!(client.chain_info().best_block_number, 1); assert_eq!(client.chain_info().best_block_number, 1);

View File

@ -22,14 +22,19 @@ mod contract;
mod multi; mod multi;
use std::sync::Weak; use std::sync::Weak;
use ids::BlockId;
use util::{Address, H256}; use util::{Address, H256};
use ethjson::spec::ValidatorSet as ValidatorSpec; use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client; use client::Client;
use self::simple_list::SimpleList; use header::Header;
pub use self::simple_list::SimpleList;
use self::contract::ValidatorContract; use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract; use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi; use self::multi::Multi;
use super::Call;
/// Creates a validator set from spec. /// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> { pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
match spec { match spec {
@ -42,13 +47,69 @@ pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
} }
} }
/// A validator set.
pub trait ValidatorSet: Send + Sync { pub trait ValidatorSet: Send + Sync {
/// Checks if a given address is a validator. /// Get the default "Call" helper, for use in general operation.
fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool; // TODO [keorn]: this is a hack intended to migrate off of
// a strict dependency on state always being available.
fn default_caller(&self, block_id: BlockId) -> Box<Call>;
/// Checks if a given address is a validator,
/// using underlying, default call mechanism.
fn contains(&self, parent: &H256, address: &Address) -> bool {
let default = self.default_caller(BlockId::Hash(*parent));
self.contains_with_caller(parent, address, &*default)
}
/// Draws an validator nonce modulo number of validators. /// Draws an validator nonce modulo number of validators.
fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address; fn get(&self, parent: &H256, nonce: usize) -> Address {
let default = self.default_caller(BlockId::Hash(*parent));
self.get_with_caller(parent, nonce, &*default)
}
/// Returns the current number of validators. /// Returns the current number of validators.
fn count(&self, parent_block_hash: &H256) -> usize; fn count(&self, parent: &H256) -> usize {
let default = self.default_caller(BlockId::Hash(*parent));
self.count_with_caller(parent, &*default)
}
/// Whether this block is the last one in its epoch.
/// Usually indicates that the validator set changed at the given block.
///
/// Should not inspect state! This is used in situations where
/// state is not generally available.
///
/// Return `Yes` or `No` indicating whether it changed at the given header,
/// or `Unsure` indicating a need for more information.
///
/// If block or receipts are provided, do not return `Unsure` indicating
/// need for them.
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange;
/// Generate epoch proof.
/// Must interact with state only through the given caller!
/// Otherwise, generated proofs may be wrong.
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String>;
/// Recover the validator set for all
///
/// May fail if the given header doesn't kick off an epoch or
/// the proof is invalid.
///
/// Returns the epoch number and proof.
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error>;
/// Checks if a given address is a validator, with the given function
/// for executing synchronous calls to contracts.
fn contains_with_caller(&self, parent_block_hash: &H256, address: &Address, caller: &Call) -> bool;
/// Draws an validator nonce modulo number of validators.
///
fn get_with_caller(&self, parent_block_hash: &H256, nonce: usize, caller: &Call) -> Address;
/// Returns the current number of validators.
fn count_with_caller(&self, parent_block_hash: &H256, caller: &Call) -> usize;
/// Notifies about malicious behaviour. /// Notifies about malicious behaviour.
fn report_malicious(&self, _validator: &Address) {} fn report_malicious(&self, _validator: &Address) {}
/// Notifies about benign misbehaviour. /// Notifies about benign misbehaviour.

View File

@ -18,13 +18,14 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Weak; use std::sync::Weak;
use engines::{Call, EpochChange};
use util::{H256, Address, RwLock}; use util::{H256, Address, RwLock};
use ids::BlockId; use ids::BlockId;
use header::BlockNumber; use header::{BlockNumber, Header};
use client::{Client, BlockChainClient}; use client::{Client, BlockChainClient};
use super::ValidatorSet; use super::ValidatorSet;
type BlockNumberLookup = Box<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>; type BlockNumberLookup = Box<Fn(BlockId) -> Result<BlockNumber, String> + Send + Sync + 'static>;
pub struct Multi { pub struct Multi {
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>, sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
@ -40,42 +41,73 @@ impl Multi {
} }
} }
fn correct_set(&self, bh: &H256) -> Option<&Box<ValidatorSet>> { fn correct_set(&self, id: BlockId) -> Option<&ValidatorSet> {
match self match self.block_number.read()(id).map(|parent_block| self.correct_set_by_number(parent_block)) {
.block_number Ok((_, set)) => Some(set),
.read()(bh)
.map(|parent_block| self
.sets
.iter()
.rev()
.find(|&(block, _)| *block <= parent_block + 1)
.expect("constructor validation ensures that there is at least one validator set for block 0;
block 0 is less than any uint;
qed")
) {
Ok((block, set)) => {
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
Some(set)
},
Err(e) => { Err(e) => {
debug!(target: "engine", "ValidatorSet could not be recovered: {}", e); debug!(target: "engine", "ValidatorSet could not be recovered: {}", e);
None None
}, },
} }
} }
// get correct set by block number, along with block number at which
// this set was activated.
fn correct_set_by_number(&self, parent_block: BlockNumber) -> (BlockNumber, &ValidatorSet) {
let (block, set) = self.sets.iter()
.rev()
.find(|&(block, _)| *block <= parent_block + 1)
.expect("constructor validation ensures that there is at least one validator set for block 0;
block 0 is less than any uint;
qed");
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
(*block, &**set)
}
} }
impl ValidatorSet for Multi { impl ValidatorSet for Multi {
fn contains(&self, bh: &H256, address: &Address) -> bool { fn default_caller(&self, block_id: BlockId) -> Box<Call> {
self.correct_set(bh).map_or(false, |set| set.contains(bh, address)) self.correct_set(block_id).map(|set| set.default_caller(block_id))
.unwrap_or(Box::new(|_, _| Err("No validator set for given ID.".into())))
} }
fn get(&self, bh: &H256, nonce: usize) -> Address { fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce)) -> EpochChange
{
let (set_block, set) = self.correct_set_by_number(header.number());
match set.is_epoch_end(header, block, receipts) {
EpochChange::Yes(num, proof) => EpochChange::Yes(set_block + num, proof),
other => other,
}
} }
fn count(&self, bh: &H256) -> usize { fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh)) self.correct_set_by_number(header.number()).1.epoch_proof(header, caller)
}
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
// "multi" epoch is the inner set's epoch plus the transition block to that set.
// ensures epoch increases monotonically.
let (set_block, set) = self.correct_set_by_number(header.number());
let (inner_epoch, list) = set.epoch_set(header, proof)?;
Ok((set_block + inner_epoch, list))
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
self.correct_set(BlockId::Hash(*bh))
.map_or(false, |set| set.contains_with_caller(bh, address, caller))
}
fn get_with_caller(&self, bh: &H256, nonce: usize, caller: &Call) -> Address {
self.correct_set(BlockId::Hash(*bh))
.map_or_else(Default::default, |set| set.get_with_caller(bh, nonce, caller))
}
fn count_with_caller(&self, bh: &H256, caller: &Call) -> usize {
self.correct_set(BlockId::Hash(*bh))
.map_or_else(usize::max_value, |set| set.count_with_caller(bh, caller))
} }
fn report_malicious(&self, validator: &Address) { fn report_malicious(&self, validator: &Address) {
@ -94,10 +126,10 @@ impl ValidatorSet for Multi {
for set in self.sets.values() { for set in self.sets.values() {
set.register_contract(client.clone()); set.register_contract(client.clone());
} }
*self.block_number.write() = Box::new(move |hash| client *self.block_number.write() = Box::new(move |id| client
.upgrade() .upgrade()
.ok_or("No client!".into()) .ok_or("No client!".into())
.and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into()))); .and_then(|c| c.block_number(id).ok_or("Unknown block".into())));
} }
} }

View File

@ -17,24 +17,45 @@
/// Validator set maintained in a contract, updated using `getValidators` method. /// Validator set maintained in a contract, updated using `getValidators` method.
use std::sync::Weak; use std::sync::Weak;
use ethabi; use futures::Future;
use native_contracts::ValidatorSet as Provider;
use util::*; use util::*;
use util::cache::MemoryLruCache; use util::cache::MemoryLruCache;
use types::ids::BlockId;
use basic_types::LogBloom;
use client::{Client, BlockChainClient}; use client::{Client, BlockChainClient};
use engines::Call;
use header::Header;
use ids::BlockId;
use log_entry::LogEntry;
use super::ValidatorSet; use super::ValidatorSet;
use super::simple_list::SimpleList; use super::simple_list::SimpleList;
const MEMOIZE_CAPACITY: usize = 500; const MEMOIZE_CAPACITY: usize = 500;
const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]";
const GET_VALIDATORS: &'static str = "getValidators"; // TODO: ethabi should be able to generate this.
const EVENT_NAME: &'static [u8] = &*b"ValidatorsChanged(bytes32,uint256,address[])";
lazy_static! {
static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3();
}
/// The validator contract should have the following interface: /// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorSafeContract { pub struct ValidatorSafeContract {
pub address: Address, pub address: Address,
validators: RwLock<MemoryLruCache<H256, SimpleList>>, validators: RwLock<MemoryLruCache<H256, SimpleList>>,
provider: RwLock<Option<provider::Contract>>, provider: Provider,
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
}
fn encode_proof(nonce: U256, validators: &[Address]) -> Bytes {
use rlp::RlpStream;
let mut stream = RlpStream::new_list(2);
stream.append(&nonce).append_list(validators);
stream.drain().to_vec()
} }
impl ValidatorSafeContract { impl ValidatorSafeContract {
@ -42,14 +63,14 @@ impl ValidatorSafeContract {
ValidatorSafeContract { ValidatorSafeContract {
address: contract_address, address: contract_address,
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
provider: RwLock::new(None), provider: Provider::new(contract_address),
client: RwLock::new(None),
} }
} }
/// Queries the state and gets the set of validators. /// Queries the state and gets the set of validators.
fn get_list(&self, block_hash: H256) -> Option<SimpleList> { fn get_list(&self, caller: &Call) -> Option<SimpleList> {
if let Some(ref provider) = *self.provider.read() { match self.provider.get_validators(caller).wait() {
match provider.get_validators(BlockId::Hash(block_hash)) {
Ok(new) => { Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new); debug!(target: "engine", "Set of validators obtained: {:?}", new);
Some(SimpleList::new(new)) Some(SimpleList::new(new))
@ -59,22 +80,151 @@ impl ValidatorSafeContract {
None None
}, },
} }
} else { }
warn!(target: "engine", "Set of validators could not be updated: no provider contract.");
/// Queries for the current validator set transition nonce.
fn get_nonce(&self, caller: &Call) -> Option<::util::U256> {
match self.provider.transition_nonce(caller).wait() {
Ok(nonce) => Some(nonce),
Err(s) => {
debug!(target: "engine", "Unable to fetch transition nonce: {}", s);
None None
} }
} }
} }
// Whether the header matches the expected bloom.
//
// The expected log should have 3 topics:
// 1. ETHABI-encoded log name.
// 2. the block's parent hash.
// 3. the "nonce": n for the nth transition in history.
//
// We can only search for the first 2, since we don't have the third
// just yet.
//
// The parent hash is included to prevent
// malicious actors from brute forcing other logs that would
// produce the same bloom.
//
// The log data is an array of all new validator addresses.
fn expected_bloom(&self, header: &Header) -> LogBloom {
LogEntry {
address: self.address,
topics: vec![*EVENT_NAME_HASH, *header.parent_hash()],
data: Vec::new(), // irrelevant for bloom.
}.bloom()
}
}
impl ValidatorSet for ValidatorSafeContract { impl ValidatorSet for ValidatorSafeContract {
fn contains(&self, block_hash: &H256, address: &Address) -> bool { fn default_caller(&self, id: BlockId) -> Box<Call> {
let client = self.client.read().clone();
Box::new(move |addr, data| client.as_ref()
.and_then(Weak::upgrade)
.ok_or("No client!".into())
.and_then(|c| c.call_contract(id, addr, data)))
}
fn is_epoch_end(&self, header: &Header, _block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
let bloom = self.expected_bloom(header);
let header_bloom = header.log_bloom();
if &bloom & header_bloom != bloom { return ::engines::EpochChange::No }
match receipts {
None => ::engines::EpochChange::Unsure(::engines::Unsure::NeedsReceipts),
Some(receipts) => {
let check_log = |log: &LogEntry| {
log.address == self.address &&
log.topics.len() == 3 &&
log.topics[0] == *EVENT_NAME_HASH &&
log.topics[1] == *header.parent_hash()
// don't have anything to compare nonce to yet.
};
let event = Provider::contract(&self.provider)
.event("ValidatorsChanged".into())
.expect("Contract known ahead of time to have `ValidatorsChanged` event; qed");
// iterate in reverse because only the _last_ change in a given
// block actually has any effect.
// the contract should only increment the nonce once.
let mut decoded_events = receipts.iter()
.rev()
.filter(|r| &bloom & &r.log_bloom == bloom)
.flat_map(|r| r.logs.iter())
.filter(move |l| check_log(l))
.filter_map(|log| {
let topics = log.topics.iter().map(|x| x.0.clone()).collect();
match event.decode_log(topics, log.data.clone()) {
Ok(decoded) => Some(decoded),
Err(_) => None,
}
});
match decoded_events.next() {
None => ::engines::EpochChange::No,
Some(matched_event) => {
// decode log manually until the native contract generator is
// good enough to do it for us.
let &(_, _, ref nonce_token) = &matched_event.params[1];
let &(_, _, ref validators_token) = &matched_event.params[2];
let nonce: Option<U256> = nonce_token.clone().to_uint()
.map(H256).map(Into::into);
let validators = validators_token.clone().to_array()
.and_then(|a| a.into_iter()
.map(|x| x.to_address().map(H160))
.collect::<Option<Vec<_>>>()
);
match (nonce, validators) {
(Some(nonce), Some(validators)) => {
let proof = encode_proof(nonce, &validators);
let new_epoch = nonce.low_u64();
::engines::EpochChange::Yes(new_epoch, proof)
}
_ => {
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
::engines::EpochChange::No
}
}
}
}
}
}
}
// the proof we generate is an RLP list containing two parts.
// (nonce, validators)
fn epoch_proof(&self, _header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
match (self.get_nonce(caller), self.get_list(caller)) {
(Some(nonce), Some(list)) => Ok(encode_proof(nonce, &list.into_inner())),
_ => Err("Caller insufficient to generate validator proof.".into()),
}
}
fn epoch_set(&self, _header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
use rlp::UntrustedRlp;
let rlp = UntrustedRlp::new(proof);
let nonce: u64 = rlp.val_at(0)?;
let validators: Vec<Address> = rlp.list_at(1)?;
Ok((nonce, SimpleList::new(validators)))
}
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
let mut guard = self.validators.write(); let mut guard = self.validators.write();
let maybe_existing = guard let maybe_existing = guard
.get_mut(block_hash) .get_mut(block_hash)
.map(|list| list.contains(block_hash, address)); .map(|list| list.contains(block_hash, address));
maybe_existing maybe_existing
.unwrap_or_else(|| self .unwrap_or_else(|| self
.get_list(block_hash.clone()) .get_list(caller)
.map_or(false, |list| { .map_or(false, |list| {
let contains = list.contains(block_hash, address); let contains = list.contains(block_hash, address);
guard.insert(block_hash.clone(), list); guard.insert(block_hash.clone(), list);
@ -82,14 +232,14 @@ impl ValidatorSet for ValidatorSafeContract {
})) }))
} }
fn get(&self, block_hash: &H256, nonce: usize) -> Address { fn get_with_caller(&self, block_hash: &H256, nonce: usize, caller: &Call) -> Address {
let mut guard = self.validators.write(); let mut guard = self.validators.write();
let maybe_existing = guard let maybe_existing = guard
.get_mut(block_hash) .get_mut(block_hash)
.map(|list| list.get(block_hash, nonce)); .map(|list| list.get(block_hash, nonce));
maybe_existing maybe_existing
.unwrap_or_else(|| self .unwrap_or_else(|| self
.get_list(block_hash.clone()) .get_list(caller)
.map_or_else(Default::default, |list| { .map_or_else(Default::default, |list| {
let address = list.get(block_hash, nonce); let address = list.get(block_hash, nonce);
guard.insert(block_hash.clone(), list); guard.insert(block_hash.clone(), list);
@ -97,14 +247,14 @@ impl ValidatorSet for ValidatorSafeContract {
})) }))
} }
fn count(&self, block_hash: &H256) -> usize { fn count_with_caller(&self, block_hash: &H256, caller: &Call) -> usize {
let mut guard = self.validators.write(); let mut guard = self.validators.write();
let maybe_existing = guard let maybe_existing = guard
.get_mut(block_hash) .get_mut(block_hash)
.map(|list| list.count(block_hash)); .map(|list| list.count(block_hash));
maybe_existing maybe_existing
.unwrap_or_else(|| self .unwrap_or_else(|| self
.get_list(block_hash.clone()) .get_list(caller)
.map_or_else(usize::max_value, |list| { .map_or_else(usize::max_value, |list| {
let address = list.count(block_hash); let address = list.count(block_hash);
guard.insert(block_hash.clone(), list); guard.insert(block_hash.clone(), list);
@ -114,55 +264,7 @@ impl ValidatorSet for ValidatorSafeContract {
fn register_contract(&self, client: Weak<Client>) { fn register_contract(&self, client: Weak<Client>) {
trace!(target: "engine", "Setting up contract caller."); trace!(target: "engine", "Setting up contract caller.");
let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed")); *self.client.write() = Some(client);
let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed");
let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed");
let contract_address = self.address.clone();
let do_call = move |id| client
.upgrade()
.ok_or("No client!".into())
.and_then(|c| c.call_contract(id, contract_address.clone(), data.clone()))
.map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed"));
*self.provider.write() = Some(provider::Contract::new(do_call));
}
}
mod provider {
use std::string::String;
use std::result::Result;
use {util, ethabi};
use types::ids::BlockId;
pub struct Contract {
do_call: Box<Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static>,
}
impl Contract {
pub fn new<F>(do_call: F) -> Self where F: Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static {
Contract {
do_call: Box::new(do_call),
}
}
/// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
pub fn get_validators(&self, id: BlockId) -> Result<Vec<util::Address>, String> {
Ok((self.do_call)(id)?
.into_iter()
.rev()
.collect::<Vec<_>>()
.pop()
.expect("get_validators returns one argument; qed")
.to_array()
.and_then(|v| v
.into_iter()
.map(|a| a.to_address())
.collect::<Option<Vec<[u8; 20]>>>())
.expect("get_validators returns a list of addresses; qed")
.into_iter()
.map(util::Address::from)
.collect::<Vec<_>>()
)
}
} }
} }
@ -178,7 +280,7 @@ mod tests {
use miner::MinerService; use miner::MinerService;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use super::super::ValidatorSet; use super::super::ValidatorSet;
use super::ValidatorSafeContract; use super::{ValidatorSafeContract, EVENT_NAME_HASH};
#[test] #[test]
fn fetches_validators() { fn fetches_validators() {
@ -256,4 +358,35 @@ mod tests {
sync_client.flush_queue(); sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3); assert_eq!(sync_client.chain_info().best_block_number, 3);
} }
#[test]
fn detects_bloom() {
use header::Header;
use engines::{EpochChange, Unsure};
use log_entry::LogEntry;
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
let engine = client.engine().clone();
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
let last_hash = client.best_block_header().hash();
let mut new_header = Header::default();
new_header.set_parent_hash(last_hash);
// first, try without the parent hash.
let mut event = LogEntry {
address: validator_contract,
topics: vec![*EVENT_NAME_HASH],
data: Vec::new(),
};
new_header.set_log_bloom(event.bloom());
assert_eq!(engine.is_epoch_end(&new_header, None, None), EpochChange::No);
// with the last hash, it should need the receipts.
event.topics.push(last_hash);
new_header.set_log_bloom(event.bloom());
assert_eq!(engine.is_epoch_end(&new_header, None, None),
EpochChange::Unsure(Unsure::NeedsReceipts));
}
} }

View File

@ -17,40 +17,67 @@
/// Preconfigured validator list. /// Preconfigured validator list.
use util::{H256, Address, HeapSizeOf}; use util::{H256, Address, HeapSizeOf};
use engines::Call;
use header::Header;
use super::ValidatorSet; use super::ValidatorSet;
#[derive(Debug, PartialEq, Eq, Default)] /// Validator set containing a known set of addresses.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct SimpleList { pub struct SimpleList {
validators: Vec<Address>, validators: Vec<Address>,
validator_n: usize,
} }
impl SimpleList { impl SimpleList {
/// Create a new `SimpleList`.
pub fn new(validators: Vec<Address>) -> Self { pub fn new(validators: Vec<Address>) -> Self {
SimpleList { SimpleList {
validator_n: validators.len(),
validators: validators, validators: validators,
} }
} }
/// Convert into inner representation.
pub fn into_inner(self) -> Vec<Address> {
self.validators
}
} }
impl HeapSizeOf for SimpleList { impl HeapSizeOf for SimpleList {
fn heap_size_of_children(&self) -> usize { fn heap_size_of_children(&self) -> usize {
self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children() self.validators.heap_size_of_children()
} }
} }
impl ValidatorSet for SimpleList { impl ValidatorSet for SimpleList {
fn contains(&self, _bh: &H256, address: &Address) -> bool { fn default_caller(&self, _block_id: ::ids::BlockId) -> Box<Call> {
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
::engines::EpochChange::No
}
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
Ok((0, self.clone()))
}
fn contains_with_caller(&self, _bh: &H256, address: &Address, _: &Call) -> bool {
self.validators.contains(address) self.validators.contains(address)
} }
fn get(&self, _bh: &H256, nonce: usize) -> Address { fn get_with_caller(&self, _bh: &H256, nonce: usize, _: &Call) -> Address {
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone() let validator_n = self.validators.len();
self.validators.get(nonce % validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
} }
fn count(&self, _bh: &H256) -> usize { fn count_with_caller(&self, _bh: &H256, _: &Call) -> usize {
self.validator_n self.validators.len()
} }
} }

View File

@ -32,6 +32,10 @@ use rlp::{self, UntrustedRlp};
/// Parity tries to round block.gas_limit to multiple of this constant /// Parity tries to round block.gas_limit to multiple of this constant
pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]); pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
/// Number of blocks in an ethash snapshot.
// make dependent on difficulty incrment divisor?
const SNAPSHOT_BLOCKS: u64 = 30000;
/// Ethash params. /// Ethash params.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct EthashParams { pub struct EthashParams {
@ -139,17 +143,33 @@ pub struct Ethash {
impl Ethash { impl Ethash {
/// Create a new instance of Ethash engine /// Create a new instance of Ethash engine
pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap<Address, Builtin>) -> Self { pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Self> {
Ethash { Arc::new(Ethash {
params: params, params: params,
ethash_params: ethash_params, ethash_params: ethash_params,
builtins: builtins, builtins: builtins,
pow: EthashManager::new(), pow: EthashManager::new(),
} })
} }
} }
impl Engine for Ethash { // TODO [rphmeier]
//
// for now, this is different than Ethash's own epochs, and signal
// "consensus epochs".
// in this sense, `Ethash` is epochless: the same `EpochVerifier` can be used
// for any block in the chain.
// in the future, we might move the Ethash epoch
// caching onto this mechanism as well.
impl ::engines::EpochVerifier for Arc<Ethash> {
fn epoch_number(&self) -> u64 { 0 }
fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
fn verify_heavy(&self, header: &Header) -> Result<(), Error> {
self.verify_block_unordered(header, None)
}
}
impl Engine for Arc<Ethash> {
fn name(&self) -> &str { "Ethash" } fn name(&self) -> &str { "Ethash" }
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
// Two fields - mix // Two fields - mix
@ -379,6 +399,14 @@ impl Engine for Ethash {
t.verify_basic(check_low_s, network_id, false)?; t.verify_basic(check_low_s, network_id, false)?;
Ok(()) Ok(())
} }
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<::engines::EpochVerifier>, Error> {
Ok(Box::new(self.clone()))
}
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PowSnapshot(SNAPSHOT_BLOCKS)))
}
} }
// Try to round gas_limit a bit so that: // Try to round gas_limit a bit so that:

View File

@ -238,7 +238,7 @@ impl Migration for OverlayRecentV7 {
} }
let mut count = 0; let mut count = 0;
for (key, value) in source.iter(None) { for (key, value) in source.iter(None).into_iter().flat_map(|inner| inner) {
count += 1; count += 1;
if count == 100_000 { if count == 100_000 {
count = 0; count = 0;

View File

@ -102,7 +102,7 @@ impl Migration for ToV10 {
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> { fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
let mut batch = Batch::new(config, col); let mut batch = Batch::new(config, col);
for (key, value) in source.iter(col) { for (key, value) in source.iter(col).into_iter().flat_map(|inner| inner) {
self.progress.tick(); self.progress.tick();
batch.insert(key.to_vec(), value.to_vec(), dest)?; batch.insert(key.to_vec(), value.to_vec(), dest)?;
} }

View File

@ -59,7 +59,7 @@ impl Migration for ToV9 {
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> { fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
let mut batch = Batch::new(config, self.column); let mut batch = Batch::new(config, self.column);
for (key, value) in source.iter(col) { for (key, value) in source.iter(col).into_iter().flat_map(|inner| inner) {
self.progress.tick(); self.progress.tick();
match self.extract { match self.extract {
Extract::Header => { Extract::Header => {

View File

@ -0,0 +1,352 @@
// 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/>.
//! Secondary chunk creation and restoration, implementations for different consensus
//! engines.
use std::collections::VecDeque;
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use blockchain::{BlockChain, BlockProvider};
use engines::Engine;
use snapshot::{Error, ManifestData};
use snapshot::block::AbridgedBlock;
use util::{Bytes, H256};
use util::kvdb::KeyValueDB;
use rand::OsRng;
use rlp::{RlpStream, UntrustedRlp};
/// A sink for produced chunks.
pub type ChunkSink<'a> = FnMut(&[u8]) -> io::Result<()> + 'a;
/// Components necessary for snapshot creation and restoration.
pub trait SnapshotComponents: Send {
/// Create secondary snapshot chunks; these corroborate the state data
/// in the state chunks.
///
/// Chunks shouldn't exceed the given preferred size, and should be fed
/// uncompressed into the sink.
///
/// This will vary by consensus engine, so it's exposed as a trait.
fn chunk_all(
&mut self,
chain: &BlockChain,
block_at: H256,
chunk_sink: &mut ChunkSink,
preferred_size: usize,
) -> Result<(), Error>;
/// Create a rebuilder, which will have chunks fed into it in aribtrary
/// order and then be finalized.
///
/// The manifest, a database, and fresh `BlockChain` are supplied.
// TODO: supply anything for state?
fn rebuilder(
&self,
chain: BlockChain,
db: Arc<KeyValueDB>,
manifest: &ManifestData,
) -> Result<Box<Rebuilder>, ::error::Error>;
}
/// Restore from secondary snapshot chunks.
pub trait Rebuilder: Send {
/// Feed a chunk, potentially out of order.
///
/// Check `abort_flag` periodically while doing heavy work. If set to `false`, should bail with
/// `Error::RestorationAborted`.
fn feed(
&mut self,
chunk: &[u8],
engine: &Engine,
abort_flag: &AtomicBool,
) -> Result<(), ::error::Error>;
/// Finalize the restoration. Will be done after all chunks have been
/// fed successfully.
/// This will apply the necessary "glue" between chunks.
fn finalize(&mut self) -> Result<(), Error>;
}
/// Snapshot creation and restoration for PoW chains.
/// This includes blocks from the head of the chain as a
/// loose assurance that the chain is valid.
///
/// The field is the number of blocks from the head of the chain
/// to include in the snapshot.
#[derive(Clone, Copy, PartialEq)]
pub struct PowSnapshot(pub u64);
impl SnapshotComponents for PowSnapshot {
fn chunk_all(
&mut self,
chain: &BlockChain,
block_at: H256,
chunk_sink: &mut ChunkSink,
preferred_size: usize,
) -> Result<(), Error> {
PowWorker {
chain: chain,
rlps: VecDeque::new(),
current_hash: block_at,
writer: chunk_sink,
preferred_size: preferred_size,
}.chunk_all(self.0)
}
fn rebuilder(
&self,
chain: BlockChain,
db: Arc<KeyValueDB>,
manifest: &ManifestData,
) -> Result<Box<Rebuilder>, ::error::Error> {
PowRebuilder::new(chain, db, manifest, self.0).map(|r| Box::new(r) as Box<_>)
}
}
/// Used to build block chunks.
struct PowWorker<'a> {
chain: &'a BlockChain,
// block, receipt rlp pairs.
rlps: VecDeque<Bytes>,
current_hash: H256,
writer: &'a mut ChunkSink<'a>,
preferred_size: usize,
}
impl<'a> PowWorker<'a> {
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
// Loops until we reach the first desired block, and writes out the remainder.
fn chunk_all(&mut self, snapshot_blocks: u64) -> Result<(), Error> {
let mut loaded_size = 0;
let mut last = self.current_hash;
let genesis_hash = self.chain.genesis_hash();
for _ in 0..snapshot_blocks {
if self.current_hash == genesis_hash { break }
let (block, receipts) = self.chain.block(&self.current_hash)
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
.ok_or(Error::BlockNotFound(self.current_hash))?;
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
let pair = {
let mut pair_stream = RlpStream::new_list(2);
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
pair_stream.out()
};
let new_loaded_size = loaded_size + pair.len();
// cut off the chunk if too large.
if new_loaded_size > self.preferred_size && !self.rlps.is_empty() {
self.write_chunk(last)?;
loaded_size = pair.len();
} else {
loaded_size = new_loaded_size;
}
self.rlps.push_front(pair);
last = self.current_hash;
self.current_hash = block.header_view().parent_hash();
}
if loaded_size != 0 {
self.write_chunk(last)?;
}
Ok(())
}
// write out the data in the buffers to a chunk on disk
//
// we preface each chunk with the parent of the first block's details,
// obtained from the details of the last block written.
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
let (last_header, last_details) = self.chain.block_header(&last)
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
.ok_or(Error::BlockNotFound(last))?;
let parent_number = last_header.number() - 1;
let parent_hash = last_header.parent_hash();
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
let num_entries = self.rlps.len();
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
for pair in self.rlps.drain(..) {
rlp_stream.append_raw(&pair, 1);
}
let raw_data = rlp_stream.out();
(self.writer)(&raw_data)?;
Ok(())
}
}
/// Rebuilder for proof-of-work chains.
/// Does basic verification for all blocks, but `PoW` verification for some.
/// Blocks must be fed in-order.
///
/// The first block in every chunk is disconnected from the last block in the
/// chunk before it, as chunks may be submitted out-of-order.
///
/// After all chunks have been submitted, we "glue" the chunks together.
pub struct PowRebuilder {
chain: BlockChain,
db: Arc<KeyValueDB>,
rng: OsRng,
disconnected: Vec<(u64, H256)>,
best_number: u64,
best_hash: H256,
best_root: H256,
fed_blocks: u64,
snapshot_blocks: u64,
}
impl PowRebuilder {
/// Create a new PowRebuilder.
fn new(chain: BlockChain, db: Arc<KeyValueDB>, manifest: &ManifestData, snapshot_blocks: u64) -> Result<Self, ::error::Error> {
Ok(PowRebuilder {
chain: chain,
db: db,
rng: OsRng::new()?,
disconnected: Vec::new(),
best_number: manifest.block_number,
best_hash: manifest.block_hash,
best_root: manifest.state_root,
fed_blocks: 0,
snapshot_blocks: snapshot_blocks,
})
}
}
impl Rebuilder for PowRebuilder {
/// Feed the rebuilder an uncompressed block chunk.
/// Returns the number of blocks fed or any errors.
fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<(), ::error::Error> {
use basic_types::Seal::With;
use views::BlockView;
use snapshot::verify_old_block;
use util::U256;
use util::triehash::ordered_trie_root;
let rlp = UntrustedRlp::new(chunk);
let item_count = rlp.item_count()?;
let num_blocks = (item_count - 3) as u64;
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
if self.fed_blocks + num_blocks > self.snapshot_blocks {
return Err(Error::TooManyBlocks(self.snapshot_blocks, self.fed_blocks).into())
}
// todo: assert here that these values are consistent with chunks being in order.
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
let mut parent_hash = rlp.val_at::<H256>(1)?;
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
for idx in 3..item_count {
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
let pair = rlp.at(idx)?;
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
let receipts_root = ordered_trie_root(
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
);
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
let block_bytes = block.rlp_bytes(With);
let is_best = cur_number == self.best_number;
if is_best {
if block.header.hash() != self.best_hash {
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
}
if block.header.state_root() != &self.best_root {
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
}
}
verify_old_block(
&mut self.rng,
&block.header,
engine,
&self.chain,
Some(&block_bytes),
is_best
)?;
let mut batch = self.db.transaction();
// special-case the first block in each chunk.
if idx == 3 {
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
self.disconnected.push((cur_number, block.header.hash()));
}
} else {
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
}
self.db.write_buffered(batch);
self.chain.commit();
parent_hash = BlockView::new(&block_bytes).hash();
cur_number += 1;
}
self.fed_blocks += num_blocks;
Ok(())
}
/// Glue together any disconnected chunks and check that the chain is complete.
fn finalize(&mut self) -> Result<(), Error> {
let mut batch = self.db.transaction();
for (first_num, first_hash) in self.disconnected.drain(..) {
let parent_num = first_num - 1;
// check if the parent is even in the chain.
// since we don't restore every single block in the chain,
// the first block of the first chunks has nothing to connect to.
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
// if so, add the child to it.
self.chain.add_child(&mut batch, parent_hash, first_hash);
}
}
self.db.write_buffered(batch);
Ok(())
}
}

View File

@ -57,6 +57,8 @@ pub enum Error {
VersionNotSupported(u64), VersionNotSupported(u64),
/// Max chunk size is to small to fit basic account data. /// Max chunk size is to small to fit basic account data.
ChunkTooSmall, ChunkTooSmall,
/// Snapshots not supported by the consensus engine.
SnapshotsUnsupported,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -79,6 +81,7 @@ impl fmt::Display for Error {
Error::Trie(ref err) => err.fmt(f), Error::Trie(ref err) => err.fmt(f),
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver), Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
Error::ChunkTooSmall => write!(f, "Chunk size is too small."), Error::ChunkTooSmall => write!(f, "Chunk size is too small."),
Error::SnapshotsUnsupported => write!(f, "Snapshots unsupported by consensus engine."),
} }
} }
} }

View File

@ -17,9 +17,9 @@
//! Snapshot creation, restoration, and network service. //! Snapshot creation, restoration, and network service.
//! //!
//! Documentation of the format can be found at //! Documentation of the format can be found at
//! https://github.com/paritytech/parity/wiki/%22PV64%22-Snapshot-Format //! https://github.com/paritytech/parity/wiki/Warp-Sync-Snapshot-Format
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@ -28,7 +28,6 @@ use blockchain::{BlockChain, BlockProvider};
use engines::Engine; use engines::Engine;
use header::Header; use header::Header;
use ids::BlockId; use ids::BlockId;
use views::BlockView;
use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint}; use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint};
use util::Mutex; use util::Mutex;
@ -40,7 +39,6 @@ use util::sha3::SHA3_NULL_RLP;
use rlp::{RlpStream, UntrustedRlp}; use rlp::{RlpStream, UntrustedRlp};
use bloom_journal::Bloom; use bloom_journal::Bloom;
use self::block::AbridgedBlock;
use self::io::SnapshotWriter; use self::io::SnapshotWriter;
use super::state_db::StateDB; use super::state_db::StateDB;
@ -51,6 +49,7 @@ use rand::{Rng, OsRng};
pub use self::error::Error; pub use self::error::Error;
pub use self::consensus::*;
pub use self::service::{Service, DatabaseRestore}; pub use self::service::{Service, DatabaseRestore};
pub use self::traits::SnapshotService; pub use self::traits::SnapshotService;
pub use self::watcher::Watcher; pub use self::watcher::Watcher;
@ -63,6 +62,7 @@ pub mod service;
mod account; mod account;
mod block; mod block;
mod consensus;
mod error; mod error;
mod watcher; mod watcher;
@ -83,9 +83,6 @@ mod traits {
// Try to have chunks be around 4MB (before compression) // Try to have chunks be around 4MB (before compression)
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024; const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
// How many blocks to include in a snapshot, starting from the head of the chain.
const SNAPSHOT_BLOCKS: u64 = 30000;
/// A progress indicator for snapshots. /// A progress indicator for snapshots.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Progress { pub struct Progress {
@ -122,6 +119,7 @@ impl Progress {
} }
/// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer. /// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer.
pub fn take_snapshot<W: SnapshotWriter + Send>( pub fn take_snapshot<W: SnapshotWriter + Send>(
engine: &Engine,
chain: &BlockChain, chain: &BlockChain,
block_at: H256, block_at: H256,
state_db: &HashDB, state_db: &HashDB,
@ -136,9 +134,11 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
info!("Taking snapshot starting at block {}", number); info!("Taking snapshot starting at block {}", number);
let writer = Mutex::new(writer); let writer = Mutex::new(writer);
let chunker = engine.snapshot_components().ok_or(Error::SnapshotsUnsupported)?;
let (state_hashes, block_hashes) = scope(|scope| { let (state_hashes, block_hashes) = scope(|scope| {
let block_guard = scope.spawn(|| chunk_blocks(chain, block_at, &writer, p)); let writer = &writer;
let state_res = chunk_state(state_db, state_root, &writer, p); let block_guard = scope.spawn(move || chunk_secondary(chunker, chain, block_at, writer, p));
let state_res = chunk_state(state_db, state_root, writer, p);
state_res.and_then(|state_hashes| { state_res.and_then(|state_hashes| {
block_guard.join().map(|block_hashes| (state_hashes, block_hashes)) block_guard.join().map(|block_hashes| (state_hashes, block_hashes))
@ -163,128 +163,41 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
Ok(()) Ok(())
} }
/// Used to build block chunks. /// Create and write out all secondary chunks to disk, returning a vector of all
struct BlockChunker<'a> { /// the hashes of secondary chunks created.
chain: &'a BlockChain,
// block, receipt rlp pairs.
rlps: VecDeque<Bytes>,
current_hash: H256,
hashes: Vec<H256>,
snappy_buffer: Vec<u8>,
writer: &'a Mutex<SnapshotWriter + 'a>,
progress: &'a Progress,
}
impl<'a> BlockChunker<'a> {
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
// Loops until we reach the first desired block, and writes out the remainder.
fn chunk_all(&mut self) -> Result<(), Error> {
let mut loaded_size = 0;
let mut last = self.current_hash;
let genesis_hash = self.chain.genesis_hash();
for _ in 0..SNAPSHOT_BLOCKS {
if self.current_hash == genesis_hash { break }
let (block, receipts) = self.chain.block(&self.current_hash)
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
.ok_or(Error::BlockNotFound(self.current_hash))?;
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
let pair = {
let mut pair_stream = RlpStream::new_list(2);
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
pair_stream.out()
};
let new_loaded_size = loaded_size + pair.len();
// cut off the chunk if too large.
if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() {
self.write_chunk(last)?;
loaded_size = pair.len();
} else {
loaded_size = new_loaded_size;
}
self.rlps.push_front(pair);
last = self.current_hash;
self.current_hash = block.header_view().parent_hash();
}
if loaded_size != 0 {
self.write_chunk(last)?;
}
Ok(())
}
// write out the data in the buffers to a chunk on disk
//
// we preface each chunk with the parent of the first block's details,
// obtained from the details of the last block written.
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
let (last_header, last_details) = self.chain.block_header(&last)
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
.ok_or(Error::BlockNotFound(last))?;
let parent_number = last_header.number() - 1;
let parent_hash = last_header.parent_hash();
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
let num_entries = self.rlps.len();
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
for pair in self.rlps.drain(..) {
rlp_stream.append_raw(&pair, 1);
}
let raw_data = rlp_stream.out();
let size = snappy::compress_into(&raw_data, &mut self.snappy_buffer);
let compressed = &self.snappy_buffer[..size];
let hash = compressed.sha3();
self.writer.lock().write_block_chunk(hash, compressed)?;
trace!(target: "snapshot", "wrote block chunk. hash: {}, size: {}, uncompressed size: {}", hash.hex(), size, raw_data.len());
self.progress.size.fetch_add(size, Ordering::SeqCst);
self.progress.blocks.fetch_add(num_entries, Ordering::SeqCst);
self.hashes.push(hash);
Ok(())
}
}
/// Create and write out all block chunks to disk, returning a vector of all
/// the hashes of block chunks created.
/// ///
/// The path parameter is the directory to store the block chunks in. /// Secondary chunks are engine-specific, but they intend to corroborate the state data
/// This function assumes the directory exists already. /// in the state chunks.
/// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis. /// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis.
pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> { pub fn chunk_secondary<'a>(mut chunker: Box<SnapshotComponents>, chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> {
let mut chunker = BlockChunker { let mut chunk_hashes = Vec::new();
chain: chain, let mut snappy_buffer = vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)];
rlps: VecDeque::new(),
current_hash: start_hash, {
hashes: Vec::new(), let mut chunk_sink = |raw_data: &[u8]| {
snappy_buffer: vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)], let compressed_size = snappy::compress_into(raw_data, &mut snappy_buffer);
writer: writer, let compressed = &snappy_buffer[..compressed_size];
progress: progress, let hash = compressed.sha3();
let size = compressed.len();
writer.lock().write_block_chunk(hash, compressed)?;
trace!(target: "snapshot", "wrote secondary chunk. hash: {}, size: {}, uncompressed size: {}",
hash.hex(), size, raw_data.len());
progress.size.fetch_add(size, Ordering::SeqCst);
chunk_hashes.push(hash);
Ok(())
}; };
chunker.chunk_all()?; chunker.chunk_all(
chain,
start_hash,
&mut chunk_sink,
PREFERRED_CHUNK_SIZE,
)?;
}
Ok(chunker.hashes) Ok(chunk_hashes)
} }
/// State trie chunker. /// State trie chunker.
@ -564,158 +477,15 @@ const POW_VERIFY_RATE: f32 = 0.02;
/// the fullest verification possible. If not, it will take a random sample to determine whether it will /// the fullest verification possible. If not, it will take a random sample to determine whether it will
/// do heavy or light verification. /// do heavy or light verification.
pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> { pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> {
engine.verify_block_basic(header, body)?;
if always || rng.gen::<f32>() <= POW_VERIFY_RATE { if always || rng.gen::<f32>() <= POW_VERIFY_RATE {
engine.verify_block_unordered(header, body)?;
match chain.block_header(header.parent_hash()) { match chain.block_header(header.parent_hash()) {
Some(parent) => engine.verify_block_family(header, &parent, body), Some(parent) => engine.verify_block_family(header, &parent, body),
None => engine.verify_block_seal(header), None => Ok(()),
} }
} else { } else {
engine.verify_block_basic(header, body)
}
}
/// Rebuilds the blockchain from chunks.
///
/// Does basic verification for all blocks, but `PoW` verification for some.
/// Blocks must be fed in-order.
///
/// The first block in every chunk is disconnected from the last block in the
/// chunk before it, as chunks may be submitted out-of-order.
///
/// After all chunks have been submitted, we "glue" the chunks together.
pub struct BlockRebuilder {
chain: BlockChain,
db: Arc<Database>,
rng: OsRng,
disconnected: Vec<(u64, H256)>,
best_number: u64,
best_hash: H256,
best_root: H256,
fed_blocks: u64,
}
impl BlockRebuilder {
/// Create a new BlockRebuilder.
pub fn new(chain: BlockChain, db: Arc<Database>, manifest: &ManifestData) -> Result<Self, ::error::Error> {
Ok(BlockRebuilder {
chain: chain,
db: db,
rng: OsRng::new()?,
disconnected: Vec::new(),
best_number: manifest.block_number,
best_hash: manifest.block_hash,
best_root: manifest.state_root,
fed_blocks: 0,
})
}
/// Feed the rebuilder an uncompressed block chunk.
/// Returns the number of blocks fed or any errors.
pub fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<u64, ::error::Error> {
use basic_types::Seal::With;
use util::U256;
use util::triehash::ordered_trie_root;
let rlp = UntrustedRlp::new(chunk);
let item_count = rlp.item_count()?;
let num_blocks = (item_count - 3) as u64;
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS {
return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into())
}
// todo: assert here that these values are consistent with chunks being in order.
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
let mut parent_hash = rlp.val_at::<H256>(1)?;
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
for idx in 3..item_count {
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
let pair = rlp.at(idx)?;
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
let receipts_root = ordered_trie_root(
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
);
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
let block_bytes = block.rlp_bytes(With);
let is_best = cur_number == self.best_number;
if is_best {
if block.header.hash() != self.best_hash {
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
}
if block.header.state_root() != &self.best_root {
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
}
}
verify_old_block(
&mut self.rng,
&block.header,
engine,
&self.chain,
Some(&block_bytes),
is_best
)?;
let mut batch = self.db.transaction();
// special-case the first block in each chunk.
if idx == 3 {
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
self.disconnected.push((cur_number, block.header.hash()));
}
} else {
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
}
self.db.write_buffered(batch);
self.chain.commit();
parent_hash = BlockView::new(&block_bytes).hash();
cur_number += 1;
}
self.fed_blocks += num_blocks;
Ok(num_blocks)
}
/// Glue together any disconnected chunks and check that the chain is complete.
pub fn finalize(self, canonical: HashMap<u64, H256>) -> Result<(), Error> {
let mut batch = self.db.transaction();
for (first_num, first_hash) in self.disconnected {
let parent_num = first_num - 1;
// check if the parent is even in the chain.
// since we don't restore every single block in the chain,
// the first block of the first chunks has nothing to connect to.
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
// if so, add the child to it.
self.chain.add_child(&mut batch, parent_hash, first_hash);
}
}
self.db.write_buffered(batch);
let best_number = self.best_number;
for num in (0..self.fed_blocks).map(|x| best_number - x) {
let hash = self.chain.block_hash(num).ok_or(Error::IncompleteChain)?;
if let Some(canon_hash) = canonical.get(&num).cloned() {
if canon_hash != hash {
return Err(Error::WrongBlockHash(num, canon_hash, hash));
}
}
}
Ok(()) Ok(())
} }
} }

View File

@ -16,14 +16,14 @@
//! Snapshot network service implementation. //! Snapshot network service implementation.
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use super::{ManifestData, StateRebuilder, BlockRebuilder, RestorationStatus, SnapshotService}; use super::{ManifestData, StateRebuilder, Rebuilder, RestorationStatus, SnapshotService};
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter}; use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
use blockchain::BlockChain; use blockchain::BlockChain;
@ -69,12 +69,11 @@ struct Restoration {
state_chunks_left: HashSet<H256>, state_chunks_left: HashSet<H256>,
block_chunks_left: HashSet<H256>, block_chunks_left: HashSet<H256>,
state: StateRebuilder, state: StateRebuilder,
blocks: BlockRebuilder, secondary: Box<Rebuilder>,
writer: Option<LooseWriter>, writer: Option<LooseWriter>,
snappy_buffer: Bytes, snappy_buffer: Bytes,
final_state_root: H256, final_state_root: H256,
guard: Guard, guard: Guard,
canonical_hashes: HashMap<u64, H256>,
db: Arc<Database>, db: Arc<Database>,
} }
@ -86,6 +85,7 @@ struct RestorationParams<'a> {
writer: Option<LooseWriter>, // writer for recovered snapshot. writer: Option<LooseWriter>, // writer for recovered snapshot.
genesis: &'a [u8], // genesis block of the chain. genesis: &'a [u8], // genesis block of the chain.
guard: Guard, // guard for the restoration directory. guard: Guard, // guard for the restoration directory.
engine: &'a Engine,
} }
impl Restoration { impl Restoration {
@ -100,7 +100,10 @@ impl Restoration {
.map_err(UtilError::SimpleString)?); .map_err(UtilError::SimpleString)?);
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
let blocks = BlockRebuilder::new(chain, raw_db.clone(), &manifest)?; let components = params.engine.snapshot_components()
.ok_or_else(|| ::snapshot::Error::SnapshotsUnsupported)?;
let secondary = components.rebuilder(chain, raw_db.clone(), &manifest)?;
let root = manifest.state_root.clone(); let root = manifest.state_root.clone();
Ok(Restoration { Ok(Restoration {
@ -108,12 +111,11 @@ impl Restoration {
state_chunks_left: state_chunks, state_chunks_left: state_chunks,
block_chunks_left: block_chunks, block_chunks_left: block_chunks,
state: StateRebuilder::new(raw_db.clone(), params.pruning), state: StateRebuilder::new(raw_db.clone(), params.pruning),
blocks: blocks, secondary: secondary,
writer: params.writer, writer: params.writer,
snappy_buffer: Vec::new(), snappy_buffer: Vec::new(),
final_state_root: root, final_state_root: root,
guard: params.guard, guard: params.guard,
canonical_hashes: HashMap::new(),
db: raw_db, db: raw_db,
}) })
} }
@ -138,7 +140,7 @@ impl Restoration {
if self.block_chunks_left.remove(&hash) { if self.block_chunks_left.remove(&hash) {
let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?; let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?;
self.blocks.feed(&self.snappy_buffer[..len], engine, flag)?; self.secondary.feed(&self.snappy_buffer[..len], engine, flag)?;
if let Some(ref mut writer) = self.writer.as_mut() { if let Some(ref mut writer) = self.writer.as_mut() {
writer.write_block_chunk(hash, chunk)?; writer.write_block_chunk(hash, chunk)?;
} }
@ -147,13 +149,8 @@ impl Restoration {
Ok(()) Ok(())
} }
// note canonical hashes.
fn note_canonical(&mut self, hashes: &[(u64, H256)]) {
self.canonical_hashes.extend(hashes.iter().cloned());
}
// finish up restoration. // finish up restoration.
fn finalize(self) -> Result<(), Error> { fn finalize(mut self) -> Result<(), Error> {
use util::trie::TrieError; use util::trie::TrieError;
if !self.is_done() { return Ok(()) } if !self.is_done() { return Ok(()) }
@ -169,7 +166,7 @@ impl Restoration {
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?; self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
// connect out-of-order chunks and verify chain integrity. // connect out-of-order chunks and verify chain integrity.
self.blocks.finalize(self.canonical_hashes)?; self.secondary.finalize()?;
if let Some(writer) = self.writer { if let Some(writer) = self.writer {
writer.finish(self.manifest)?; writer.finish(self.manifest)?;
@ -425,6 +422,7 @@ impl Service {
writer: writer, writer: writer,
genesis: &self.genesis_block, genesis: &self.genesis_block,
guard: Guard::new(rest_dir), guard: Guard::new(rest_dir),
engine: &*self.engine,
}; };
let state_chunks = params.manifest.state_hashes.len(); let state_chunks = params.manifest.state_hashes.len();
@ -593,14 +591,6 @@ impl SnapshotService for Service {
trace!("Error sending snapshot service message: {:?}", e); trace!("Error sending snapshot service message: {:?}", e);
} }
} }
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]) {
let mut rest = self.restoration.lock();
if let Some(ref mut rest) = rest.as_mut() {
rest.note_canonical(canonical);
}
}
} }
impl Drop for Service { impl Drop for Service {

View File

@ -48,10 +48,6 @@ pub trait SnapshotService : Sync + Send {
/// Feed a raw block chunk to the service to be processed asynchronously. /// Feed a raw block chunk to the service to be processed asynchronously.
/// no-op if currently restoring. /// no-op if currently restoring.
fn restore_block_chunk(&self, hash: H256, chunk: Bytes); fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
/// Give the restoration in-progress some canonical block hashes for
/// extra verification (performed at the end)
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]);
} }
impl IpcConfig for SnapshotService { } impl IpcConfig for SnapshotService { }

View File

@ -21,33 +21,32 @@ use error::Error;
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer}; use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
use blockchain::BlockChain; use blockchain::BlockChain;
use snapshot::{chunk_blocks, BlockRebuilder, Error as SnapshotError, Progress}; use snapshot::{chunk_secondary, Error as SnapshotError, Progress, SnapshotComponents};
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
use util::{Mutex, snappy}; use util::{Mutex, snappy};
use util::kvdb::{Database, DatabaseConfig}; use util::kvdb::{self, KeyValueDB, DBTransaction};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
const SNAPSHOT_MODE: ::snapshot::PowSnapshot = ::snapshot::PowSnapshot(30000);
fn chunk_and_restore(amount: u64) { fn chunk_and_restore(amount: u64) {
let mut canon_chain = ChainGenerator::default(); let mut canon_chain = ChainGenerator::default();
let mut finalizer = BlockFinalizer::default(); let mut finalizer = BlockFinalizer::default();
let genesis = canon_chain.generate(&mut finalizer).unwrap(); let genesis = canon_chain.generate(&mut finalizer).unwrap();
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
let engine = Arc::new(::engines::NullEngine::default()); let engine = Arc::new(::engines::NullEngine::default());
let orig_path = RandomTempPath::create_dir();
let new_path = RandomTempPath::create_dir(); let new_path = RandomTempPath::create_dir();
let mut snapshot_path = new_path.as_path().to_owned(); let mut snapshot_path = new_path.as_path().to_owned();
snapshot_path.push("SNAP"); snapshot_path.push("SNAP");
let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); let old_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
let bc = BlockChain::new(Default::default(), &genesis, old_db.clone()); let bc = BlockChain::new(Default::default(), &genesis, old_db.clone());
// build the blockchain. // build the blockchain.
let mut batch = old_db.transaction(); let mut batch = DBTransaction::new();
for _ in 0..amount { for _ in 0..amount {
let block = canon_chain.generate(&mut finalizer).unwrap(); let block = canon_chain.generate(&mut finalizer).unwrap();
bc.insert_block(&mut batch, &block, vec![]); bc.insert_block(&mut batch, &block, vec![]);
@ -56,12 +55,18 @@ fn chunk_and_restore(amount: u64) {
old_db.write(batch).unwrap(); old_db.write(batch).unwrap();
let best_hash = bc.best_block_hash(); let best_hash = bc.best_block_hash();
// snapshot it. // snapshot it.
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap()); let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap(); let block_hashes = chunk_secondary(
Box::new(SNAPSHOT_MODE),
&bc,
best_hash,
&writer,
&Progress::default()
).unwrap();
let manifest = ::snapshot::ManifestData { let manifest = ::snapshot::ManifestData {
version: 2, version: 2,
state_hashes: Vec::new(), state_hashes: Vec::new(),
@ -74,9 +79,10 @@ fn chunk_and_restore(amount: u64) {
writer.into_inner().finish(manifest.clone()).unwrap(); writer.into_inner().finish(manifest.clone()).unwrap();
// restore it. // restore it.
let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); let new_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let mut rebuilder = SNAPSHOT_MODE.rebuilder(new_chain, new_db.clone(), &manifest).unwrap();
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
let flag = AtomicBool::new(true); let flag = AtomicBool::new(true);
for chunk_hash in &reader.manifest().block_hashes { for chunk_hash in &reader.manifest().block_hashes {
@ -85,7 +91,8 @@ fn chunk_and_restore(amount: u64) {
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap(); rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
} }
rebuilder.finalize(HashMap::new()).unwrap(); rebuilder.finalize().unwrap();
drop(rebuilder);
// and test it. // and test it.
let new_chain = BlockChain::new(Default::default(), &genesis, new_db); let new_chain = BlockChain::new(Default::default(), &genesis, new_db);
@ -118,10 +125,8 @@ fn checks_flag() {
}; };
let chunk = stream.out(); let chunk = stream.out();
let path = RandomTempPath::create_dir();
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap());
let engine = Arc::new(::engines::NullEngine::default()); let engine = Arc::new(::engines::NullEngine::default());
let chain = BlockChain::new(Default::default(), &genesis, db.clone()); let chain = BlockChain::new(Default::default(), &genesis, db.clone());
@ -134,7 +139,7 @@ fn checks_flag() {
block_hash: H256::default(), block_hash: H256::default(),
}; };
let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap(); let mut rebuilder = SNAPSHOT_MODE.rebuilder(chain, db.clone(), &manifest).unwrap();
match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) { match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) {
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {} Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}

View File

@ -350,7 +350,7 @@ fn transaction_proof() {
data: Vec::new(), data: Vec::new(),
}.fake_sign(address); }.fake_sign(address);
let proof = client.prove_transaction(transaction.clone(), BlockId::Latest).unwrap(); let proof = client.prove_transaction(transaction.clone(), BlockId::Latest).unwrap().1;
let backend = state::backend::ProofCheck::new(&proof); let backend = state::backend::ProofCheck::new(&proof);
let mut factories = ::factory::Factories::default(); let mut factories = ::factory::Factories::default();

View File

@ -34,4 +34,8 @@ impl Verifier for CanonVerifier {
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error> { fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error> {
verification::verify_block_final(expected, got) verification::verify_block_final(expected, got)
} }
fn verify_block_external(&self, header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> {
engine.verify_block_external(header, Some(bytes))
}
} }

View File

@ -34,4 +34,8 @@ impl Verifier for NoopVerifier {
fn verify_block_final(&self, _expected: &Header, _got: &Header) -> Result<(), Error> { fn verify_block_final(&self, _expected: &Header, _got: &Header) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn verify_block_external(&self, _header: &Header, _bytes: &[u8], _engine: &Engine) -> Result<(), Error> {
Ok(())
}
} }

View File

@ -27,4 +27,6 @@ pub trait Verifier: Send + Sync {
fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>; fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>;
/// Do a final verification check for an enacted header vs its expected counterpart. /// Do a final verification check for an enacted header vs its expected counterpart.
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>; fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>;
/// Verify a block, inspecing external state.
fn verify_block_external(&self, header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error>;
} }

View File

@ -17,7 +17,7 @@
const DEFAULT_LOCALE = 'en'; const DEFAULT_LOCALE = 'en';
const DEFAULT_LOCALES = process.env.NODE_ENV === 'production' const DEFAULT_LOCALES = process.env.NODE_ENV === 'production'
? ['en'] ? ['en']
: ['en', 'de', 'nl', 'zh']; : ['en', 'de', 'nl', 'zh', 'zh-Hant-TW'];
const LS_STORE_KEY = '_parity::locale'; const LS_STORE_KEY = '_parity::locale';
export { export {

View File

@ -18,5 +18,6 @@ export default {
de: 'Deutsch', de: 'Deutsch',
en: 'English', en: 'English',
nl: 'Nederlands', nl: 'Nederlands',
zh: '简体中文' zh: '简体中文',
'zh-Hant-TW': '繁體中文'
}; };

View File

@ -29,6 +29,7 @@ import deMessages from './de';
import enMessages from './en'; import enMessages from './en';
import nlMessages from './nl'; import nlMessages from './nl';
import zhMessages from './zh'; import zhMessages from './zh';
import zhHantTWMessages from './zh-Hant-TW';
let instance = null; let instance = null;
@ -37,7 +38,8 @@ const MESSAGES = {
de: Object.assign(flatten(deMessages), LANGUAGES), de: Object.assign(flatten(deMessages), LANGUAGES),
en: Object.assign(flatten(enMessages), LANGUAGES), en: Object.assign(flatten(enMessages), LANGUAGES),
nl: Object.assign(flatten(nlMessages), LANGUAGES), nl: Object.assign(flatten(nlMessages), LANGUAGES),
zh: Object.assign(flatten(zhMessages), LANGUAGES) zh: Object.assign(flatten(zhMessages), LANGUAGES),
'zh-Hant-TW': Object.assign(flatten(zhHantTWMessages), LANGUAGES)
}; };
addLocaleData([...de, ...en, ...nl, ...zh]); addLocaleData([...de, ...en, ...nl, ...zh]);

View File

@ -0,0 +1,39 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
delete: `刪除`, // delete
edit: `編輯`, // edit
faucet: `Kovan測試網路以太幣`, // Kovan ETH
password: `密碼`, // password
shapeshift: `shapeshift`,
transfer: `轉帳`, // transfer
verify: `確認`// verify
},
hardware: {
confirmDelete: `你確定從你的帳戶列表中移除下面的硬體地址嗎?`
}, // Are you sure you want to remove the following hardware address from your account list?
header: {
outgoingTransactions: `{count}筆正在發生的轉帳`, // {count} outgoing transactions
uuid: `uuid: {uuid}`
},
title: `帳戶管理`, // Account Management
transactions: {
poweredBy: `Transaction list powered by {etherscan}提供的交易列表`,
title: `交易`// transactions
}
};

View File

@ -0,0 +1,36 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
newAccount: `帳戶`, // account
newWallet: `錢包`, // wallet
vaults: `保險庫`// vaults
},
summary: {
minedBlock: `在第#{blockNumber}個區塊被挖出`// Mined at block #{blockNumber}
},
title: `帳戶總覽`, // Accounts Overview
tooltip: {
actions: `與當前視窗有關的操作可以在工具欄中快速被找到,不論是執行操作還是建立新項`,
// actions relating to the current view are available on the toolbar for quick access, be it for performing actions or creating a new item
overview: `你的帳戶很容易使用,使你可以編輯元資訊、轉帳、檢視交易和向帳戶充值`
// your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account
},
tooltips: {
owner: `{name}持有者`// {name} (owner)
}
};

View File

@ -0,0 +1,41 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
add: `儲存地址`, // Save Address
close: `取消` // Cancel
},
header: `如果想在地址簿中新增一條新的記錄,你需要擁有帳戶的網路地址並提供一個的描述(可選)。一旦新增,記錄就可以體現在你的地址簿中。`,
// To add a new entry to your addressbook, you need the network
// address of the account and can supply an optional description.
// Once added it will reflect in your address book.
input: {
address: {
hint: `記錄的網路地址`, // the network address for the entry
label: `網路地址` // network address
},
description: {
hint: `記錄的詳細描述`, // an expanded description for the entry
label: `(可選)地址描述` // (optional) address description
},
name: {
hint: `記錄的名字`, // a descriptive name for the entry
label: `地址名` // address name
}
},
label: `新增已儲存的地址` // add saved address
};

View File

@ -0,0 +1,60 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
abi: {
hint: `合約的ABI`, // the abi for the contract
label: `合約ABI` // contract abi
},
abiType: {
custom: {
description: `通過自定義ABI創造的合約`, // Contract created from custom ABI
label: `自定義合約` // Custom Contract
},
multisigWallet: {
description: `以太坊多重簽名合約{link}`, // Ethereum Multisig contract {link}
label: `多重簽名錢包`, // Multisig Wallet
link: `參考合約程式碼` // see contract code
},
token: {
description: `一個標準的{erc20}代幣`, // A standard {erc20} token
erc20: `ERC 20`, // ERC 20
label: `代幣` // Token
}
},
address: {
hint: `合約的網路地址`, // the network address for the contract
label: `網路地址` // network address
},
button: {
add: `新增合約`, // Add Contract
cancel: `取消`, // Cancel
next: `下一步`, // Next
prev: `上一步` // Back
},
description: {
hint: `記錄的詳細描述`, // an expanded description for the entry
label: `(可選)合約描述` // (optional) contract description
},
name: {
hint: `合約的描述性名稱`, // a descriptive name for the contract
label: `合約名` // contract name
},
title: {
details: `輸入合約細節`, // enter contract details
type: `選擇合約種類` // choose a contract type
}
};

View File

@ -0,0 +1,28 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
edit: `編輯`, // edit
forget: `忘記`, // forget
save: `儲存`// save
},
delete: {
confirmInfo: `你確定你想把下面的地址從你的地址簿中移除嗎?`, // Are you sure you want to remove the following address from your addressbook?
title: `確認移除`// confirm removal
},
title: `地址資訊`// Address Information
};

View File

@ -0,0 +1,26 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
fromEmail: `使用郵箱{email}進行確認`, // Verified using email {email}
fromRegistry: `{name}(來自注冊)`, // {name} (from registry)
labels: {
accounts: `帳戶`, // accounts
contacts: `合約`, // contacts
contracts: `合約`// contracts
},
noAccount: `查不到這個帳戶`// No account matches this query...
};

View File

@ -0,0 +1,25 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
add: `地址` // address
},
errors: {
invalidFile: `提供的檔案是無效的`// The provided file is invalid...
},
title: `儲存的地址`// Saved Addresses
};

View File

@ -0,0 +1,30 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
frame: {
error: `錯誤:這個應用不能也不應該載入到內建框架中`// ERROR: This application cannot and should not be loaded in an embedded iFrame
},
status: {
consensus: {
capable: `可行`, // Capable
capableUntil: `到第 #{blockNumber} 區塊前可行`, // Capable until #{blockNumber}
incapableSince: `自第 #{blockNumber} 區塊後不可行`, // Incapable since #{blockNumber}
unknown: `未知能力`// Unknown capability
},
upgrade: `升級`// Upgrade
}
};

View File

@ -0,0 +1,30 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
connectingAPI: `正在連線至Parity Secure API`, // Connecting to the Parity Secure API.
connectingNode: `正在連線Parity節點。如果彈出任何資訊請確認你的Parity節點正在執行並連線至網際網路。`,
// Connecting to the Parity Node. If this informational message persists,
// please ensure that your Parity node is running and reachable on the network.
invalidToken: `無效的簽名令牌`, // invalid signer token
noConnection: `無法連線至Parity Secure API。請升級的你的安全令牌或者生成一個新的執行{newToken}並貼上生成的令牌到下方。`,
// Unable to make a connection to the Parity Secure API. To update your secure
// token or to generate a new one, run {newToken} and paste the generated token
// into the space below.
token: {
hint: `一個Parity生成的令牌`, // a generated token from Parity
label: `安全令牌` // secure token
}
};

View File

@ -0,0 +1,41 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
close: `關閉`, // close
details: `詳細資料`, // details
edit: `編輯`, // edit
execute: `執行`, // execute
forget: `forget` // forget
},
details: {
title: `合約細節` // contract details
},
events: {
eventPending: `待定中`, // pending
noEvents: `此合約沒有傳送過任何事件`, // No events has been sent from this contract.
title: `事件` // events
},
minedBlock: `挖到了第{blockNumber}個區塊`, // Mined at block #{blockNumber}
queries: {
buttons: {
query: `查詢` // Query
},
title: `查詢` // queries
},
title: `合約資訊` // Contract Information
};

View File

@ -0,0 +1,28 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
deploy: `部署`, // deploy
develop: `開發`, // develop
watch: `觀察` // watch
},
sortOrder: {
date: `日期`, // date
minedBlock: `挖到的區塊` // mined block
},
title: `合約` // Contracts
};

View File

@ -0,0 +1,165 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
accountDetails: {
address: {
hint: `帳戶地址`,
label: `地址`
},
name: {
hint: `描述帳戶的名字`,
label: `帳戶名`
},
phrase: {
hint: `帳戶恢復詞`,
label: `帳戶恢復詞(安全儲存,別人擁有它就可以完全控制你的帳戶)`
}
},
accountDetailsGeth: {
imported: `你已經從Geth keystore匯入了{number}個地址`
},
button: {
back: `返回`,
cancel: `取消`,
close: `關閉`,
create: `建立`,
done: `Done`,
import: `匯入`,
next: `下一步`,
print: `列印恢復詞`
},
creationType: {
fromGeth: {
description: `Import accounts from the Geth keystore with the original password`,
label: `從Geth keystore匯入帳戶`
},
fromJSON: {
description: `Import an industry-standard JSON keyfile with the original password`,
label: `從JSON檔案匯入帳戶`
},
fromNew: {
description: `Selecting your identity icon and specifying the password`,
label: `手動建立新帳戶`
},
fromPhrase: {
description: `Recover using a previously stored recovery phrase and new password`,
label: `通過恢復詞恢復帳戶`
},
fromPresale: {
description: `Import an Ethereum presale wallet file with the original password`,
label: `從以太坊預售錢包匯入帳戶`
},
fromRaw: {
description: `Enter a previously created raw private key with a new password`,
label: `匯入私鑰`
},
info: `Please select the type of account you want to create. Either create an account via name & password, or import it from a variety of existing sources. From here the wizard will guide you through the process of completing your account creation.`
},
newAccount: {
hint: {
hint: `(可選)幫助你記住密碼的提示`,
label: `密碼提示`
},
name: {
hint: `描述帳戶的名字`,
label: `帳戶名`
},
password: {
hint: `足夠強的密碼`,
label: `密碼`
},
password2: {
hint: `確認你的密碼`,
label: `再次輸入密碼`
}
},
newGeth: {
available: `There are currently {count} importable keys available from the Geth keystore which are not already available on your Parity instance. Select the accounts you wish to import and move to the next step to complete the import.`,
noKeys: `現在Geth keystore中沒有可匯入的私鑰`
},
newImport: {
file: {
hint: `要匯入的錢包檔案`,
label: `錢包檔案`
},
hint: {
hint: `(可選)幫助你記住密碼的提示`,
label: `密碼提示`
},
name: {
hint: `描述帳戶的名字`,
label: `帳戶名`
},
password: {
hint: `輸入密碼,解鎖錢包`,
label: `密碼`
}
},
rawKey: {
hint: {
hint: `(可選)幫助你記住密碼的提示`,
label: `密碼提示`
},
name: {
hint: `描述帳戶的名字`,
label: `帳戶名`
},
password: {
hint: `足夠強的密碼`,
label: `密碼`
},
password2: {
hint: `確認密碼`,
label: `再次輸入密碼`
},
private: {
hint: `原始的十六進位制編碼私鑰`,
label: `私鑰`
}
},
recoveryPhrase: {
hint: {
hint: `(可選)幫助你記住密碼的提示`,
label: `密碼提示`
},
name: {
hint: `描述帳戶的名字`,
label: `帳戶名`
},
password: {
hint: `足夠強的密碼`,
label: `密碼`
},
password2: {
hint: `確認密碼`,
label: `再次輸入密碼`
},
phrase: {
hint: `帳戶恢復詞`,
label: `帳戶恢復詞`
},
windowsKey: {
label: `在Windows系統上由Parity 1.4.5以前的版本建立的私鑰`
}
},
title: {
accountInfo: `帳戶資訊`,
createAccount: `建立帳戶`,
createType: `建立型別`,
importWallet: `匯入錢包`
}
};

View File

@ -0,0 +1,106 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
add: `新增`, // Add
cancel: `取消`, // Cancel
close: `關閉`, // Close
create: `建立`, // Create
done: `完成`, // Done
next: `下一步`, // Next
sending: `傳送中...` // Sending...
},
deployment: {
message: `部署正在進行中` // The deployment is currently in progress
},
details: {
address: {
hint: `錢包的合約地址`, // the wallet contract address
label: `錢包地址` // wallet address
},
dayLimitMulti: {
hint: `無需確認即可使用的ETH數量`, // amount of ETH spendable without confirmations
label: `錢包每日限額` // wallet day limit
},
description: {
hint: `本地錢包描述`, // the local description for this wallet
label: `錢包描述(可選)` // wallet description (optional)
},
descriptionMulti: {
hint: `本地錢包描述`, // the local description for this wallet
label: `錢包描述(可選)` // wallet description (optional)
},
name: {
hint: `錢包本地名稱`, // the local name for this wallet
label: `錢包名稱` // wallet name
},
nameMulti: {
hint: `錢包本地名稱`, // the local name for this wallet
label: `錢包名稱` // wallet name
},
ownerMulti: {
hint: `合約的持有者帳戶`, // the owner account for this contract
label: `從帳戶 (contract owner)` // from account (contract owner)
},
ownersMulti: {
label: `其他錢包持有者` // other wallet owners
},
ownersMultiReq: {
hint: `接受交易所需的持有者人數`, // number of required owners to accept a transaction
label: `所需持有者` // required owners
}
},
info: {
added: `已新增`, // added
copyAddress: `複製地址至貼上板`, // copy address to clipboard
created: `{name}已被{deployedOrAdded}至`, // {name} has been {deployedOrAdded} at
dayLimit: `每日限額已被設定為{dayLimit}ETH`, // The daily limit is set to {dayLimit} ETH.
deployed: `已部署`, // deployed
numOwners: `需要{numOwners}個持有者才能確認一個交易`, // {numOwners} owners are required to confirm a transaction.
owners: `以下為錢包持有者` // The following are wallet owners
},
rejected: {
message: `部署被拒絕`, // The deployment has been rejected
state: `錢包不會被建立。你可以安全地關閉本視窗`, // The wallet will not be created. You can safely close this window.
title: `失敗` // rejected
},
states: {
completed: `合約部署已完成`, // The contract deployment has been completed
confirmationNeeded: `合約部署需要來自本錢包的其他持有者的確認`, // The contract deployment needs confirmations from other owners of the Wallet
preparing: `交易正在準備被網路廣播`, // Preparing transaction for network transmission
validatingCode: `正在驗證已部署的程式碼`, // Validating the deployed contract code
waitingConfirm: `正在等待Parity Secure Signer確認本交易`, // Waiting for confirmation of the transaction in the Parity Secure Signer
waitingReceipt: `正在等待合約部署交易收據` // Waiting for the contract deployment transaction receipt
},
steps: {
deployment: `錢包部署`, // wallet deployment
details: `錢包詳情`, // wallet details
info: `錢包資訊`, // wallet informaton
type: `錢包類別` // wallet type
},
type: {
multisig: {
description: `建立/部署一個{link}錢包`, // Create/Deploy a {link} Wallet
label: `多重簽名錢包`, // Multi-Sig Wallet
link: `標準多重簽名` // standard multi-signature
},
watch: {
description: `新增一個已有錢包到你的帳戶`, // Add an existing wallet to your accounts
label: `觀察錢包` // Watch a wallet
}
}
};

View File

@ -0,0 +1,20 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
loading: `正在載入`, // Loading
unavailable: `不能獲取這個dapp`// The dapp cannot be reached
};

View File

@ -0,0 +1,48 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
add: {
builtin: {
desc: `Parity團隊開發的實驗性的用以展示dapp的效能、整合、實驗性特性和控制特定網路的的客戶端行為`,
// Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.
label: `與Parity繫結的應用`// Applications bundled with Parity
},
label: `visible applications可見的應用`, // visible applications
local: {
desc: `All applications installed locally on the machine by the user for access by the Parity client.`,
label: `本地可用的應用`// Applications locally available
},
network: {
desc: `這些應用與Parity沒有關聯也不是Parity釋出的。 它們是由各自的作者控制的。 在使用以前,請確保你理解每個應用的目標。`,
// These applications are not affiliated with Parity nor are they published by Parity.Each remain under the control of their respective authors.Please ensure that you understand the goals for each application before interacting.
label: `全球網路上的應用`// Applications on the global network
}
},
button: {
edit: `編輯`, // edit
permissions: `許可`// permissions
},
external: {
accept: `我理解這些應用和Parity沒有關聯`, // I understand that these applications are not affiliated with Parity
warning: `第三方開發者開發的應用與Parity沒有關聯也不是Parity釋出的。 它們是由各自的作者控制的。 在使用以前,請確保你理解每個應用的目標。`
// Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.
},
label: `去中心化應用`, // Decentralized Applications
permissions: {
label: `可見的dapp帳戶`// visible dapp accounts
}
};

View File

@ -0,0 +1,24 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
password: {
hint: `提供帳戶密碼,確認刪除帳戶`, // provide the account password to confirm the account deletion
label: `帳戶密碼`// account password
},
question: `你確定你想永久地刪除下面的帳戶?`, // Are you sure you want to permanently delete the following account?
title: `確認刪除`// confirm removal
};

View File

@ -0,0 +1,90 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
busy: {
title: `部署正在進行中`// The deployment is currently in progress
},
button: {
cancel: `取消`, // Cancel
close: `關閉`, // Close
create: `建立`, // Create
done: `完成`, // Done
next: `下一個`// Next
},
completed: {
description: `你的合約已經被部署在`// Your contract has been deployed at
},
details: {
abi: {
hint: `合約的abi或者solc 組合輸出`, // the abi of the contract to deploy or solc combined-output
label: `abi / solc 組合輸出 `// abi / solc combined-output
},
address: {
hint: `這個合約所有者的帳戶`, // the owner account for this contract
label: `來自帳戶(合約所有者)`// from account (contract owner)
},
advanced: {
label: `高階的傳送選項`// advanced sending options
},
amount: {
hint: `轉到這個合約中的數額`, // the amount to transfer to the contract
label: `傳送數額{tag}`// amount to transfer (in {tag})
},
code: {
hint: `編譯好的合約程式碼`, // the compiled code of the contract to deploy
label: `程式碼`// code
},
contract: {
label: `選擇一個合約`// select a contract
},
description: {
hint: `對這個合約的描述`, // a description for the contract
label: `合約描述(可選)`// contract description (optional)
},
name: {
hint: `已經部署合約的名字`, // a name for the deployed contract
label: `合約名字`// contract name
}
},
owner: {
noneSelected: `選擇一個有效的地址作為合約的所有者`// a valid account as the contract owner needs to be selected
},
parameters: {
choose: `選擇合約引數`// Choose the contract parameters
},
rejected: {
description: `你可以安全地關閉視窗,合約部署不會發生。`, // You can safely close this window, the contract deployment will not occur.
title: `部署已經被拒絕`// The deployment has been rejected
},
state: {
completed: `合約部署已經完成`, // The contract deployment has been completed
confirmationNeeded: `這一操作需要這個合約其他所有人的確認。`, // The operation needs confirmations from the other owners of the contract
preparing: `為網路傳輸準備交易`, // Preparing transaction for network transmission
validatingCode: `驗證已經部署的合約的程式碼`, // Validating the deployed contract code
waitReceipt: `等待合約部署交易收據`, // Waiting for the contract deployment transaction receipt
waitSigner: `等待Parity Secure Signer中的交易被確認 `// Waiting for confirmation of the transaction in the Parity Secure Signer
},
title: {
completed: `完成`, // completed
deployment: `部署`, // deployment
details: `合約細節`, // contract details
extras: `額外資訊`, // extra information
failed: `部署失敗`, // deployment failed
parameters: `s合約引數`, // contract parameter
rejected: `拒絕`// rejected
}
};

View File

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

View File

@ -0,0 +1,34 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
description: {
hint: `此地址的描述`, // description for this address
label: `地址描述` // address description
},
name: {
label: `名稱` // name
},
passwordHint: {
hint: `密碼恢復提示`, // a hint to allow password recovery
label: `(可選)密碼提示` // (optional) password hint
},
tags: {
hint: `按<Enter>來新增一個標籤`, // press <Enter> to add a tag
label: `(可選)標籤` // (optional) tags
},
title: `編輯元資料` // edit metadata
};

View File

@ -0,0 +1,24 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
duplicateName: `名稱已存在`, // the name already exists
invalidKey: `原始私鑰需要被16進位制64個字元長度並以"0x"開頭`, // the raw key needs to be hex, 64 characters in length and contain the prefix "0x"
noFile: `選擇一個可用的錢包檔案來輸入`, // select a valid wallet file to import
noKey: `你需要提供原始私鑰`, // you need to provide the raw private key
noMatchPassword: `所提供的密碼不正確`, // the supplied passwords does not match
noName: `你需要明確一個可用的名稱` // you need to specify a valid name
};

View File

@ -0,0 +1,58 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
busy: {
posted: `你的交易已被公佈至網路`, // Your transaction has been posted to the network
title: `函式執行正在進行中`, // The function execution is in progress
waitAuth: `正在等待Parity Signer授權` // Waiting for authorization in the Parity Signer
},
button: {
cancel: `取消`, // cancel
done: `完成`, // done
next: `下一步`, // next
post: `公佈交易`, // post transaction
prev: `上一步` // prev
},
details: {
address: {
hint: `來自帳戶`, // from account
label: `將要交易的帳戶` // the account to transact with
},
advancedCheck: {
label: `高階傳送選項` // advanced sending options
},
amount: {
hint: `此交易將會發送的數量`, // the amount to send to with the transaction
label: `交易價值ETH` // transaction value (in ETH)
},
function: {
hint: `此合約將會呼叫的函式`, // the function to call on the contract
label: `將執行的函式` // function to execute
}
},
rejected: {
state: `你可以安全的關閉此視窗,函式將不會被執行。`, // You can safely close this window, the function execution will not occur.
title: `執行失敗` // The execution has been rejected
},
steps: {
advanced: `高階選項`, // advanced options
complete: `完成`, // complete
rejected: `失敗`, // rejected
sending: `傳送中`, // sending
transfer: `函式詳情` // function details
}
};

View File

@ -0,0 +1,22 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
install: `現在就安裝這個擴充套件`, // Install the extension now
intro: `Parity現在有一個Chrome的擴充套件可以安全的瀏覽以太坊所支援的分散式應用。我們強烈推薦你安裝這個擴充套件來進一步提升你的Parity使用體驗。`
// Parity now has an extension available for Chrome that allows safe browsing of Ethereum-enabled distributed applications.
// It is highly recommended that you install this extension to further enhance your Parity experience.
};

View File

@ -0,0 +1,29 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
close: `關閉`, // close
done: `關閉`, // close
request: `請求`// request
},
summary: {
done: `你已經向水龍頭請求Kovan ETH測試幣`, // Your Kovan ETH has been requested from the faucet which responded with -
info: `如果請求將Kovan ETH測試幣存入這個地址你需要確定這個地址在主網路上已經用簡訊驗證過了。 一旦執行水龍頭將向當前帳戶存入Kovan ETH測試幣。`
// To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet.Once executed the faucet will deposit Kovan ETH into the current account.
},
title: `Kovan ETH測試幣水龍頭`// Kovan ETH Faucet Kovan
};

View File

@ -0,0 +1,52 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
close: `關閉`, // Close
create: `建立`, // Create
next: `下一步`, // Next
print: `列印片語`, // Print Phrase
skip: `跳過`// Skip
},
completed: {
congrats: `恭喜!你的節點設定已經完成,你現在可以使用應用了。`,
// Congratulations! Your node setup has been completed successfully and you are ready to use the application.
next: `下面你將瀏覽可用的功能和通用的應用介面讓你最快速地使用Parity。`
// Next you will receive a walk-through of the available functions and the general application interface to get you up and running in record time.
},
title: {
completed: `完成`, // completed
newAccount: `新帳戶`, // new account
recovery: `恢復`, // recovery
terms: `條款`, // terms
welcome: `歡迎`// welcome
},
tnc: {
accept: `我接受這些條款和條件`// I accept these terms and conditions
},
welcome: {
description: `作為初次安裝的一部分下面的幾個步驟將指導你如何設定你的Parity和相關帳戶。我們的目標是使得使用Parity變得儘可能的簡單讓你成功執行所以請有點耐心。 一旦完成,你將擁有`,
// As part of a new installation, the next few steps will guide you through the process of setting up you Parity instance and your associated accounts.Our aim is to make it as simple as possible and to get you up and running in record-time, so please bear with us.Once completed you will have -
greeting: `歡迎使用Parity這是執行以太坊節點的最快和最簡單的方式。`, // Welcome to Parity, the fastest and simplest way to run your node.
next: `點選下一步繼續`, // Click Next to continue your journey.
step: {
account: `建立你的第一個Parity帳戶`, // Created your first Parity account
privacy: `理解你的隱私政策和運作條款`, // Understood our privacy policy & terms of operation
recovery: `有能力可以恢復你的帳戶`// Have the ability to recover your account
}
}
};

View File

@ -0,0 +1,38 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
account: {
visited: `已訪問{when}` // accessed {when}
},
accounts: {
none: `沒有可用的近期帳戶歷史`, // No recent accounts history available
title: `近期帳戶` // Recent Accounts
},
dapp: {
visited: `已訪問{when}` // accessed {when}
},
dapps: {
none: `沒有可用的近期應用歷史`, // No recent Applications history available
title: `近期Dapps` // Recent Dapps
},
title: `Parity首頁`, // Parity Home
url: {
none: `沒有可用的近期URL歷史`, // No recent URL history available
title: `網頁應用`, // Web Applications
visited: `已訪問{when}` // visited {when}
}
};

View File

@ -0,0 +1,105 @@
// 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 account from './account';
import accounts from './accounts';
import addAddress from './addAddress';
import addContract from './addContract';
import address from './address';
import addressSelect from './addressSelect';
import addresses from './addresses';
import application from './application';
import connection from './connection';
import contract from './contract';
import contracts from './contracts';
import createAccount from './createAccount';
import createWallet from './createWallet';
import dapp from './dapp';
import dapps from './dapps';
import deleteAccount from './deleteAccount';
import deployContract from './deployContract';
import editMeta from './editMeta';
import errors from './errors';
import executeContract from './executeContract';
import extension from './extension';
import faucet from './faucet';
import firstRun from './firstRun';
import home from './home';
import loadContract from './loadContract';
import parityBar from './parityBar';
import passwordChange from './passwordChange';
import saveContract from './saveContract';
import settings from './settings';
import shapeshift from './shapeshift';
import signer from './signer';
import status from './status';
import tabBar from './tabBar';
import transfer from './transfer';
import txEditor from './txEditor';
import ui from './ui';
import upgradeParity from './upgradeParity';
import vaults from './vaults';
import verification from './verification';
import wallet from './wallet';
import walletSettings from './walletSettings';
import web from './web';
import writeContract from './writeContract';
export default {
account,
accounts,
addAddress,
addContract,
address,
addresses,
addressSelect,
application,
connection,
contract,
contracts,
createAccount,
createWallet,
dapp,
dapps,
deleteAccount,
deployContract,
editMeta,
errors,
executeContract,
extension,
faucet,
firstRun,
home,
loadContract,
parityBar,
passwordChange,
saveContract,
settings,
signer,
shapeshift,
status,
tabBar,
transfer,
txEditor,
ui,
upgradeParity,
vaults,
verification,
wallet,
walletSettings,
web,
writeContract
};

View File

@ -0,0 +1,43 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
cancel: `取消`, // Cancel
load: `載入`, // Load
no: ``, // No
yes: `` // Yes
},
contract: {
savedAt: `已儲存{when}` // Saved {when}
},
header: {
saved: `已儲存的合約`, // Saved Contracts
snippets: `合約片段` // Contract Snippets
},
removal: {
confirm: `你確定要將以下合約從已儲存的合約中刪除嗎?`, // Are you sure you want to remove the following contract from your saved contracts?
savedAt: `已儲存{when}` // Saved {when}
},
tab: {
local: `本地`, // Local
snippets: `片段` // Snippets
},
title: {
remove: `確認刪除`, // confirm removal
view: `檢視合約` // view contracts
}
};

View File

@ -0,0 +1,29 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
close: `關閉` // Close
},
label: {
parity: `Parity`, // Parity
signer: `Signer` // Singer
},
title: {
accounts: `預設帳戶`, // Default Account
signer: `Parity Signer待處理` // Parity Signer: Pending
}
};

View File

@ -0,0 +1,54 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
cancel: `取消`, // Cancel
change: `更改`, // Change
test: `測試`, // Test
wait: `請等待...` // Wait...
},
currentPassword: {
hint: `此帳戶的原密碼`, // your current password for this account
label: `原密碼` // current password
},
newPassword: {
hint: `此帳戶的新密碼`, // the new password for this account
label: `新密碼` // new password
},
passwordHint: {
display: `提示{hint}`, // Hint {hint}
hint: `新密碼的提示`, // hint for the new password
label: `(可選)新的密碼提示` // (optional) new password hint
},
repeatPassword: {
error: `所提供的密碼不正確`, // the supplied passwords do not match
hint: `請重複此帳戶的新密碼`, // repeat the new password for this account
label: `重複新密碼` // repeat new password
},
success: `你的密碼已經成功更改`, // Your password has been successfully changed
tabChange: {
label: `更改密碼` // Change Password
},
tabTest: {
label: `測試密碼` // Test Password
},
testPassword: {
hint: `你的帳戶密碼`, // your account password
label: `密碼` // password
},
title: `密碼管理器` // Password Manager
};

View File

@ -0,0 +1,27 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
cancel: `取消`, // Cancel
save: `儲存` // Save
},
name: {
hint: `為此合約選擇一個名稱`, // choose a name for this contract
label: `合約名稱` // contract name
},
title: `儲存合約` // save contract
};

View File

@ -0,0 +1,126 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
background: {
button_more: `生成更多`, // generate more
overview_0: `你現在所看到的背景圖案在你的Parity安裝中是獨一無二的。每次創造一個新的Signer令牌都會改變一次圖案。這也保證了去中性化應用不能偽裝成可信的樣子。`,
// The background pattern you can see right now is unique to your Parity installation. It will change every time you create a new
// Signer token. This is so that decentralized applications cannot pretend to be trustworthy.
overview_1: `選擇一個你喜歡的圖案並記住它的樣子。這個圖案從現在開始會經常出現除非你清空了瀏覽器的快取或者使用了新的Signer令牌。`,
// Pick a pattern you like and memorize it. This Pattern will always be shown from now on, unless you clear your browser cache or
// use a new Signer token.
label: `背景` // background
},
parity: {
chains: {
chain_classic: `將Parity同步至以太坊經典網路`, // Parity syncs to the Ethereum Classic network
chain_dev: `將Parity使用一條本地開發用區塊鏈`, // Parity uses a local development chain
chain_expanse: `將Parity同步至Expanse網路`, // Parity syncs to the Expanse network
chain_foundation: `將Parity同步至以太坊基金會發起的以太坊網路`, // Parity syncs to the Ethereum network launched by the Ethereum Foundation
chain_kovan: `將Parity同步至Kovan測試網路`, // Parity syncs to the Kovan test network
chain_olympic: `將Parity同步至Olympic測試網路`, // Parity syncs to the Olympic test network
chain_ropsten: `將Parity同步至Ropsten測試網路`, // Parity syncs to the Ropsten test network
cmorden_kovan: `將Parity同步至Morden經典測試網路`, // Parity syncs to Morden (Classic) test network
hint: `Parity節點同步的區塊鏈`, // the chain for the Parity node to sync to
label: `將同步的區塊鏈/網路` // chain/network to sync
},
languages: {
hint: `此介面顯示的語言`, // the language this interface is displayed with
label: `介面語言` // UI language
},
loglevels: `選擇一個不同的logs層次`, // Choose the different logs level.
modes: {
hint: `Parity節點的同步模式`, // the syncing mode for the Parity node
label: `執行模式`, // mode of operation
mode_active: `Parity持續地同步區塊鏈`, // Parity continuously syncs the chain
mode_dark: `Parity只有在RPC啟用時才同步`, // Parity syncs only when the RPC is active
mode_offline: `Parity不同步`, // Parity doesn't sync
mode_passive: `Parity初始同步然後進入休眠並有規律地再同步` // Parity syncs initially, then sleeps and wakes regularly to resync
},
overview_0: `通過此介面控制Parity節點設定和同步設定`, // Control the Parity node settings and nature of syncing via this interface.
label: `parity` // parity
},
proxy: {
details_0: `除了通過IP地址和埠來訪問Parity你也能通過.parity子域名來使用Parity訪問 {homeProxy}。為了設定基於子域名的路由,你需要新增相關的代理記錄至你的瀏覽器。`,
// Instead of accessing Parity via the IP address and port, you will be able to access it via the .parity subdomain, by visiting
// {homeProxy}. To setup subdomain-based routing, you need to add the relevant proxy entries to your browser,
details_1: `如果想了解如何配置代理,教程已提供在{windowsLink}{macOSLink}和{ubuntuLink}。`,
// To learn how to configure the proxy, instructions are provided for {windowsLink}, {macOSLink} or {ubuntuLink}.
details_macos: `macOS`, // macOS
details_ubuntu: `Ubuntu`, // Ubuntu
details_windows: `Windows`, // Windows
overview_0: `代理設定使你可以通過一個可記憶的地址來訪問Parity和所有相關的去中性化應用。`,
// The proxy setup allows you to access Parity and all associated decentralized applications via memorable addresses.
label: `代理` // proxy
},
views: {
accounts: {
description: `一個此Parity例項所關聯和匯入的所有帳戶的列表。傳送交易、接收流入價值、管理你的帳目和資助你的帳戶。`,
// A list of all the accounts associated with and imported into this Parity instance. Send transactions, receive incoming values,
// manage your balances and fund your accounts.
label: `帳戶` // Accounts
},
addresses: {
description: `一個此Parity例項管理的所有聯絡人和地址簿記錄的列表。只需點選一個按鈕就可以觀察帳戶並獲得所有交易相關的資訊。`,
// A list of all contacts and address book entries managed by this Parity instance. Watch accounts and have the details available
// at the click of a button when transacting.
label: `地址簿` // Addressbook
},
apps: {
description: `與整個底層網路交流的分散式應用。新增應用,管理你的應用庫和與網路上的其他應用進行互動。`,
// Distributed applications that interact with the underlying network. Add applications, manage you application portfolio and
// interact with application from around the network.
label: `應用` // Applications
},
contracts: {
description: `觀察和互動已經被部署在網路上的特定合約。這是一個更注重技術的環境,特別為可以理解合約內部執行機制的高階使用者所設立。`,
// Watch and interact with specific contracts that have been deployed on the network. This is a more technically-focused environment,
// specifically for advanced users that understand the inner working of certain contracts.
label: `合約` // Contracts
},
overview_0: `僅可視部分對你可用的應用來管理應用介面`,
// Manage the available application views using only the parts of the application applicable to you.
overview_1: `你是終端使用者?預設設定為初學者和高階使用者進行了相同的設定。`,
// Are you an end-user? The defaults are setup for both beginner and advanced users alike.
overview_2: `你是開發者?新增一些功能來管理合約和與應用部署互動。`,
// Are you a developer? Add some features to manage contracts and interact with application deployments.
overview_3: `你是礦工或者運營一個大型節點?新增一些功能來讓你獲得更多有關節點執行的資訊。`,
// Are you a miner or run a large-scale node? Add the features to give you all the information needed to watch the node operation.
settings: {
description: `此介面。允許你自定義應用的選項、執行、視覺化和感官。`,
// This view. Allows you to customize the application in term of options, operation and look and feel.
label: `設定` // Settings
},
signer: {
description: `這個應用安全交易管理區域,你可以通過任何從本應用和其他分散式應用發起的即將傳送的交易`,
// The secure transaction management area of the application where you can approve any outgoing transactions made
// from the application as well as those placed into the queue by distributed applications.
label: `Signer` // Signer
},
status: {
description: `觀察Parity節點現在的執行情況網路連線數、實際執行例項的Logs和具體挖礦資訊如果已開啟並設定`,
// See how the Parity node is performing in terms of connections to the network, logs from the actual running instance
// and details of mining (if enabled and configured).
label: `狀態` // Status
},
label: `視窗`, // views
home: {
label: `首頁` // Home
}
},
label: `設定` // settings
};

View File

@ -0,0 +1,76 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
awaitingDepositStep: {
awaitingConfirmation: `正在等待你的{typeSymbol}資金交易的存款地址的確認資訊`,
// Awaiting confirmation of the deposit address for your {typeSymbol} funds exchange
awaitingDeposit: `{shapeshiftLink}正在等待{typeSymbol}的存入.從你的{typeSymbol}網路客戶端傳送資金至-`,
// {shapeshiftLink} is awaiting a {typeSymbol} deposit. Send the funds from your {typeSymbol} network client to -
minimumMaximum: `{minimum}至少, {maximum}至多` // {minimum} minimum, {maximum} maximum
},
awaitingExchangeStep: {
awaitingCompletion: `正在完成資金交易併發送資金至你的Parity帳戶`,
// Awaiting the completion of the funds exchange and transfer of funds to your Parity account.
receivedInfo: `{shapeshiftLink}已經收到存款-`
// {shapeshiftLink} has received a deposit of -
},
button: {
cancel: `取消`, // Cancel
done: `關閉`, // Close
shift: `轉換資金` // Shift Funds
},
completedStep: {
completed: `{shapeshiftLink}已經完成了資金交易。`, // {shapeshiftLink} has completed the funds exchange.
parityFunds: `資金的改變會馬上在你的Parity帳戶裡體現。`
// The change in funds will be reflected in your Parity account shortly.
},
errorStep: {
info: `通過{shapeshiftLink}進行的資金轉換因為一個交易的致命錯誤失敗了。交易提供的錯誤資訊如下:`
// The funds shifting via {shapeshiftLink} failed with a fatal error on the exchange. The error message received from the exchange
// is as follow:
},
optionsStep: {
noPairs: `目前沒有可匹配的交易/貨幣可用來進行轉換`,
// There are currently no exchange pairs/coins available to fund with.
returnAddr: {
hint: `轉換錯誤後的發回地址`, // the return address for send failures
label: `(可選){coinSymbol}發回地址` // (optional) {coinSymbol} return address
},
terms: {
label: `我理解ShapeShift.io是一個第三方服務使用此服務發生的任何資訊/資金髮送是完全不受Parity控制的`
// I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is
// completely out of the control of Parity
},
typeSelect: {
hint: `數字貨幣轉換的種類`, // the type of crypto conversion to do
label: `來自資金帳戶` // fund account from
}
},
price: {
minMax: `({minimum}至小, {maximum}至大)` // ({minimum} minimum, {maximum} maximum)
},
title: {
completed: `完成`, // completed
deposit: `等待存款`, // awaiting deposit
details: `詳情`, // details
error: `交易失敗`, // exchange failed
exchange: `等待交易` // awaiting exchange
},
warning: {
noPrice: `所選擇的型別沒有匹配的價格` // No price match was found for the selected type
}
};

View File

@ -0,0 +1,107 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
embedded: {
noPending: `目前沒有待處理的確認等待你的處理`
// There are currently no pending requests awaiting your confirmation
},
mainDetails: {
editTx: `編輯條款/gas/gasPrice`, // Edit conditions/gas/gasPrice
tooltips: {
total1: `包括交易費的交易的總價值是{total} {type}`,
// The value of the transaction including the mining fee is {total} {type}.
total2: `(包括了交易費 {fee} {token})`,
// (This includes a mining fee of {fee} {token})
value1: `交易價值` // The value of the transaction.
}
},
requestOrigin: {
dapp: `來自dapp {url}`, // by a dapp at {url}
ipc: `通過IPC會話`, // via IPC session
rpc: `通過RPC{rpc}`, // via RPC {rpc}
signerCurrent: `通過當前標籤頁`, // via current tab
signerUI: `通過互動會話`, // via UI session
unknownInterface: `通過未知互動`, // via unknown interface
unknownRpc: `未明確的`, // unidentified
unknownUrl: `未知URL` // unknown URL
},
requestsPage: {
noPending: `沒有請求需要你的確認`, // There are no requests requiring your confirmation.
pendingTitle: `待處理請求`, // Pending Requests
queueTitle: `本地交易` // Local Transactions
},
sending: {
hardware: {
confirm: `請在你連線的硬體裝置上確認交易`, // Please confirm the transaction on your attached hardware device
connect: `請在確認交易前連線你的硬體裝置` // Please attach your hardware device before confirming the transaction
}
},
signRequest: {
request: `一個簽名資料在請求你的帳號:`, // A request to sign data using your account:
state: {
confirmed: `通過`, // Confirmed
rejected: `拒絕` // Rejected
},
unknownBinary: `(未知二進位制資料)`, // (Unknown binary data)
warning: `警告:此操作的結果是不可逆的。請在確認資訊後再通過請求。`
// WARNING: This consequences of doing this may be grave. Confirm the request only if you are sure.
},
title: `可信的Signer`, // Trusted Signer
txPending: {
buttons: {
viewToggle: `檢視交易` // view transaction
}
},
txPendingConfirm: {
buttons: {
confirmBusy: `通過中...`, // Confirming...
confirmRequest: `通過請求` // Confirm Request
},
errors: {
invalidWallet: `所提供的錢包檔案不可用` // Given wallet file is invalid.
},
password: {
decrypt: {
hint: `解金鑰匙`, // decrypt the key
label: `鑰匙密碼` // Key Password
},
unlock: {
hint: `解鎖帳戶`, // unlock the account
label: `帳戶密碼` // Account Password
}
},
passwordHint: `(提示){passwordHint}`, // (hint) {passwordHint}
selectKey: {
hint: `此帳戶的鑰匙檔案`, // The keyfile to use for this account
label: `選擇本地鑰匙` // Select Local Key
},
tooltips: {
password: `請為此帳戶提供密碼` // Please provide a password for this account
}
},
txPendingForm: {
changedMind: `我改主意了`, // I've changed my mind
reject: `拒絕請求` // reject request
},
txPendingReject: {
buttons: {
reject: `拒絕請求` // Reject Request
},
info: `你確定要拒絕請求嗎?`, // Are you sure you want to reject request?
undone: `此操作是不可逆的` // This cannot be undone
}
};

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/>.
export default {
debug: {
reverse: `翻轉訂單`, // Reverse Order
stopped: `Parity的互動目前停止了重新整理和顯示Logs請啟動它來檢視最新的更新。`,
// Refresh and display of logs from Parity is currently stopped via the UI, start it to see the latest updates.
title: `節點Logs` // Node Logs
},
miningSettings: {
input: {
author: {
hint: `礦工名字`, // the mining author
label: `礦工` // author
},
extradata: {
hint: `提取挖到區塊的資料`, // extra data for mined blocks
label: `提取資料` // extradata
},
gasFloor: {
hint: `挖礦的gas下限目標`, // the gas floor target for mining
label: `gas下限目標` // gas floor target
},
gasPrice: {
hint: `挖礦的最低gas價格`, // the minimum gas price for mining
label: `最低gas價格` // minimal gas price
}
},
title: `挖礦設定` // mining settings
},
status: {
hashrate: `{hashrate} H/s`, // {hashrate} H/s
input: {
chain: ``, // chain
enode: `enode`, // enode
no: ``, // no
peers: `同步節點`, // peers
port: `網路埠`, // network port
rpcEnabled: `rpc開啟`, // rpc enabled
rpcInterface: `rpc互動`, // rpc interface
rpcPort: `rpc埠`, // rpc port
yes: `` // yes
},
title: {
bestBlock: `最新區塊`, // best block
hashRate: `雜湊率`, // hash rate
network: `網路設定`, // network settings
node: `節點`, // Node
peers: `同步節點` // peers
}
},
title: `狀態` // Status
};

View File

@ -0,0 +1,22 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
tooltip: {
overview: `在應用的不同部分和不同介面進行導航,在帳戶介面、代幣介面和分散式應用介面之間切換。`
// navigate between the different parts and views of the application, switching between an account view, token view and distributed application view
}
};

View File

@ -0,0 +1,62 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
advanced: {
data: {
hint: `交易附帶資料`, // the data to pass through with the transaction
label: `交易資料`// transaction data
}
},
buttons: {
back: `返回`, // Back
cancel: `取消`, // Cancel
close: `關閉`, // Close
next: `下一步`, // Next
send: `傳送`// Send
},
details: {
advanced: {
label: `高階傳送選項`// advanced sending options
},
amount: {
hint: `傳送數額`, // the amount to transfer to the recipient
label: `傳送數額{tag}`// amount to transfer (in {tag})
},
fullBalance: {
label: `所有的餘額`// full account balance
},
recipient: {
hint: `收款人地址`, // the recipient address
label: `收款人地址`// recipient address
},
sender: {
hint: `傳送人地址`, // the sender address
label: `傳送人地址`// sender address
},
total: {
label: `傳送數額`// total transaction amount
}
},
wallet: {
confirmation: `這筆交易需要其他人的確認。`, // This transaction needs confirmation from other owners.
operationHash: `操作雜湊`// operation hash
},
warning: {
wallet_spent_limit: `這筆轉帳的數額超過了每日轉帳數額上限。此交易需要其他人的確認才可以傳送成功。`
// This transaction value is above the remaining daily limit. It will need to be confirmed by other owners.
}
};

View File

@ -0,0 +1,40 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
condition: {
block: {
hint: `在某個區塊高度後傳送`, // The minimum block to send from
label: `交易傳送區塊`// Transaction send block
},
blocknumber: `在某個區塊後傳送`, // Send after BlockNumber
date: {
hint: `在某日後傳送`, // The minimum date to send from
label: `交易傳送日期`// Transaction send date
},
datetime: `在某日某時後傳送`, // Send after Date & Tim
label: `交易啟用的條件`, // Condition where transaction activates
none: `無條件`, // No conditions
time: {
hint: `在某時間後傳送`, // The minimum time to send from
label: `交易傳送時間`// Transaction send time
}
},
gas: {
info: `你可以基於最近的交易gas價格的分佈選擇gas價格。 gas價格越低交易費用越便宜。 gas 價格越高,交易被網路打包的速度越快。`
// You can choose the gas price based on the distribution of recent included transaction gas prices.The lower the gas price is, the cheaper the transaction will be.The higher the gas price is, the faster it should get mined by the network.
}
};

View File

@ -0,0 +1,163 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
actionbar: {
export: {
button: {
export: `匯出`// export
}
},
import: {
button: {
cancel: `取消`, // Cancel
confirm: `確認`, // Confirm
import: `匯入`// import
},
confirm: `確認這是你想匯入的`, // Confirm that this is what was intended to import.
error: `發生錯誤:{errorText}`, // An error occured: {errorText}
step: {
error: `錯誤`, // error
select: `選擇一個檔案`, // select a file
validate: `確認`// validate
},
title: `從一個檔案匯入`// Import from a file
},
search: {
hint: `輸入搜尋內容……`// Enter search input...
},
sort: {
sortBy: `根據{label}排序`, // Sort by {label}
typeDefault: `預設`, // Default
typeEth: `根據以太幣數額排序`, // Sort by ETH
typeName: `根據帳戶名字排序`, // Sort by name
typeTags: `根據標籤排序`// Sort by tags
}
},
balance: {
none: `這個帳戶沒有餘額`// No balances associated with this account
},
blockStatus: {
bestBlock: `最新區塊{blockNumber}`, // {blockNumber} best block
syncStatus: `currentBlock}/{highestBlock}區塊同步`, // {currentBlock}/{highestBlock} syncing{
warpRestore: `{percentage}%恢復`, // {percentage}% warp restore
warpStatus: `, {percentage}%歷史`// {percentage}% historic
},
confirmDialog: {
no: `不是`, // no
yes: ``// yes
},
copyToClipboard: {
copied: `複製{data}到貼上板`// copied {data} to clipboard
},
errors: {
close: `關閉`// close
},
fileSelect: {
defaultLabel: `拉一個檔案到這裡,或者選擇一個檔案上傳`// Drop a file here, or click to select a file to upload
},
gasPriceSelector: {
customTooltip: {
transactions: `{number} {number, plural, one {transaction} other {transactions}} with gas price set from {minPrice} to {maxPrice}`
}
},
identityName: {
null: ``, // NULL
unnamed: `未命名`// UNNAMED
},
methodDecoding: {
condition: {
block: `, {historic, select, true {Submitted} false {Submission}} at block {blockNumber}`,
time: `, {historic, select, true {Submitted} false {Submission}} at {timestamp}`
},
deploy: {
address: `在地址上部署一個合約`, // Deployed a contract at address
params: `附帶下面的引數:`, // with the following parameters:
willDeploy: `將要部署一個合約`, // Will deploy a contract
withValue: `, 傳送{value}`// sending {value}
},
gasUsed: `({gas}gas消耗)`, // {gas} gas used
gasValues: `{gas} gas ({gasPrice}M/{tag})`,
input: {
data: `資料`, // data
input: `輸入`, // input
withInput: `with the {inputDesc} {inputValue}`
},
receive: {
contract: `合約`, // the contract
info: `{historic, select, true {Received} false {Will receive}} {valueEth} from {aContract}{address}`
},
signature: {
info: `{historic, select, true {Executed} false {Will execute}} the {method} function on the contract {address} trsansferring {ethValue}{inputLength, plural, zero {,} other {passing the following {inputLength, plural, one {parameter} other {parameters}}}}`
},
token: {
transfer: `{historic, select, true {Transferred} false {Will transfer}} {value} to {address}`
},
transfer: {
contract: `the contract`,
info: `{historic, select, true {Transferred} false {Will transfer}} {valueEth} to {aContract}{address}`
},
txValues: `{historic, select, true {Provided} false {Provides}} {gasProvided}{gasUsed} for a total transaction value of {totalEthValue}`,
unknown: {
info: `{historic, select, true {Executed} false {Will execute}} the {method} on the contract {address} transferring {ethValue}.`
}
},
passwordStrength: {
label: `密碼強度`// password strength
},
tooltips: {
button: {
done: `完成`, // Done
next: `下一步`, // Next
skip: `跳過`// Skip
}
},
txHash: {
confirmations: `{count} {value, plural, one {confirmation} other {confirmations}}`,
oog: `這筆交易已經耗光了gas。請用更多的gas嘗試。`, // The transaction might have gone out of gas. Try again with more gas.
posted: `這筆交易已經被髮送到網路,附帶雜湊是{hashLink}`, // The transaction has been posted to the network with a hash of {hashLink}
waiting: `等待確認`// waiting for confirmations
},
vaultSelect: {
hint: `這個帳戶繫結的保險庫是`, // the vault this account is attached to
label: `相關保險庫`// associated vault
},
verification: {
gatherData: {
accountHasRequested: {
false: `.你還沒有從這個帳戶請求確認。`, // You did not request verification from this account yet
pending: `檢查一下你是否請求了驗證……`, // Checking if you requested verification…
true: `你已經從這個帳戶請求到驗證。`// You already requested verification from this account.
},
accountIsVerified: {
false: `你的帳戶還沒有被驗證。`, // Your account is not verified yet.
pending: `檢查一下你的帳戶是否已經被驗證……`, // Checking if your account is verified…
true: `你的帳戶已經被驗證。`// Your account is already verified.
},
fee: `額外的費用是{amount}ETH`, // The additional fee is {amount} ETH.
isAbleToRequest: {
pending: `驗證你的輸入……`// Validating your input…
},
isServerRunning: {
false: `驗證伺服器沒有在執行。`, // The verification server is not running.
pending: `檢查一下驗證伺服器是否在執行……`, // Checking if the verification server is running…
true: `驗證伺服器正在執行。`// The verification server is running.
},
nofee: `沒有額外的費用。`, // There is no additional fee.
termsOfService: `我同意下面的條款和條件。`// I agree to the terms and conditions below.
}
}
};

View File

@ -0,0 +1,55 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
busy: `你正在升級到Parity最新版本{newversion}。請等待升級過程完成。`,
// Your upgrade to Parity {newversion} is currently in progress. Please wait until the process completes.
button: {
close: `關閉`, // close
done: `完成`, // done
upgrade: `現在升級`// upgrade now
},
completed: `你升級到Parity最新版本{newversion}的操作已經完成。點選“完成”將自動重新載入這個應用。`,
// Your upgrade to Parity {newversion} has been successfully completed. Click "done" to automatically reload the application.
consensus: {
capable: `你當前的Parity版本能夠處理網路請求。`,
// Your current Parity version is capable of handling the network requirements.
capableUntil: `你當前的Parity版本能夠處理直到第{blockNumber}個區塊的網路請求。`,
// Your current Parity version is capable of handling the network requirements until block {blockNumber}
incapableSince: `你當前的Parity版本能夠處理第{blockNumber}個區塊以後的網路請求。`,
// Your current Parity version is incapable of handling the network requirements since block {blockNumber}
unknown: `你當前的Parity版本能夠處理網路請求。`
// Your current Parity version is capable of handling the network requirements.
},
failed: `升級到Parity最新版本{newversion}遇到錯誤,升級失敗。`,
// Your upgrade to Parity {newversion} has failed with an error.
info: {
currentVersion: `你現在正在執行{currentversion}版本。`, // You are currently running {currentversion}
next: `點選“現在升級”開始Parity升級。`, // Proceed with "upgrade now" to start your Parity upgrade.
upgrade: `可以升級到最新版本{newversion}`, // An upgrade to version {newversion} is available
welcome: `迎來到Parity升級指南讓你享受無縫升級到Parity最新版本的體驗。`
// Welcome to the Parity upgrade wizard, allowing you a completely seamless upgrade experience to the next version of Parity.歡
},
step: {
completed: `升級完成`, // upgrade completed
error: `錯誤`, // error
info: `可以升級`, // upgrade available
updating: `升級Parity`// upgrading parity
},
version: {
unknown: `未知`// unknown
}
};

View File

@ -0,0 +1,114 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
accounts: {
button: {
cancel: `取消`, // Cancel
execute: `設定` // Set
},
empty: `此保險庫中沒有帳戶`, // There are no accounts in this vault
title: `管理保險庫帳戶` // Manage Vault Accounts
},
button: {
accounts: `帳戶`, // accounts
add: `建立保險庫`, // create vault
close: `關閉`, // close
edit: `編輯`, // edit
open: `開啟` // open
},
confirmClose: {
info: `你即將關閉一個保險庫。所有與這個保險庫相關的帳戶在這個操作完成後都不再可見。如果想再見到關聯帳戶,請重新開啟保險庫。`,
// You are about to close a vault. Any accounts associated with the vault won't be visible after this operation concludes. To view
// the associated accounts, open the vault again.
title: `關閉保險庫` // Close Vault
},
confirmOpen: {
info: `你即將開啟一個保險庫。在確認了你的密碼之後,所有與這個保險庫關聯的帳戶都會可見。關閉保險庫會在介面中移除所有帳戶,直到保險庫被再次開啟。`,
// You are about to open a vault. After confirming your password, all accounts associated with this vault will be visible. Closing
// the vault will remove the accounts from view until the vault is opened again.
password: {
hint: `建立保險庫時設定的密碼`, // the password specified when creating the vault
label: `保險庫密碼` // vault password
},
title: `開啟保險庫` // Open Vault
},
create: {
button: {
close: `關閉`, // close
vault: `建立保險庫` // create valut
},
description: {
hint: `該保險庫更詳細的描述` // an extended description for the vault
},
descriptions: {
label: `(可選)描述` // (optional) description
},
hint: {
hint: `(可選)一個幫助記憶密碼的提示`, // (optional) a hint to help with remembering the password
label: `密碼提示` // password hint
},
name: {
hint: `一個保險庫的名字`, // a name for the vault
label: `保險庫名稱` // vault name
},
password: {
hint: `一個高強度且獨一無二的密碼`, // a strong, unique password
label: `密碼` // password
},
password2: {
hint: `驗證你的密碼`, // verify your password
label: `密碼(重複)` // password (repeat)
},
title: `建立一個新的保險庫` // Create a new vault
},
editMeta: {
allowPassword: `更改保險庫密碼`, // Change vault password
button: {
close: `關閉`, // close
save: `儲存` // save
},
currentPassword: {
hint: `保險庫的原密碼`, // your current vault password
label: `原密碼` // current password
},
description: {
hint: `此保險庫的描述`, // the description for this vault
label: `保險庫描述` // vault description
},
password: {
hint: `一個高強度且獨一無二的密碼`, // a strong, unique password
label: `新密碼` // new password
},
password2: {
hint: `驗證你的新密碼`, // verify your new password
label: `新密碼(重複)` // new password (repeat)
},
passwordHint: {
hint: `此保險庫的密碼提示`, // your password hint for this vault
label: `密碼提示` // password hint
},
title: `編輯保險庫元資料` // Edit Vault Metadata
},
empty: `目前沒有任何可顯示的保險庫`, // There are currently no vaults to display.
selector: {
noneAvailable: `目前沒有任何開啟、可選的保險庫。請在移動帳戶之前建立並開啟一個保險庫。`,
// There are currently no vaults opened and available for selection. Create and open some first before attempting to select
// a vault for an account move.
title: `選擇帳戶保險庫` // Select Account Vault
},
title: `保險庫管理` // Vault Management
};

View File

@ -0,0 +1,85 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
button: {
cancel: `取消`, // Cancel
done: `完成`, // Done
next: `下一步`// Next
},
code: {
error: `無效驗證碼`, // invalid code
hint: `輸入你收到的驗證碼。`, // Enter the code you received.
label: `驗證碼`, // verification code
sent: `驗證碼被髮送到接收者{receiver}.`// The verification code has been sent to {receiver}
},
confirmation: {
authorise: `驗證碼將被髮送到合約。請使用Parity Signer進行授權。`, // The verification code will be sent to the contract. Please authorize this using the Parity Signer.
windowOpen: `請保持這個視窗開啟狀態。`// Please keep this window open.
},
done: {
message: `恭喜,你的帳戶已經被認證。`// Congratulations, your account is verified!
},
email: {
enterCode: `輸入你從郵箱獲得驗證碼。`// Enter the code you received via e-mail.
},
gatherData: {
email: {
hint: `驗證碼將被髮送到這個地址`, // the code will be sent to this address
label: `郵箱地址`// e-mail address
},
phoneNumber: {
hint: `簡訊將被髮送到這個號碼`, // the SMS will be sent to this number
label: `國際格式的手機號碼`// phone number in international format
}
},
gatherDate: {
email: {
error: `無效郵箱`// invalid e-mail
},
phoneNumber: {
error: `無效數字`// invalid number
}
},
loading: `載入驗證資料`, // Loading verification data.
request: {
authorise: `驗證請求將被髮送到這個合約。請使用Parity Signer進行授權。`, // A verification request will be sent to the contract. Please authorize this using the Parity Signer.
requesting: `正在從Parity伺服器請求一個驗證碼等待它被輸入到合約。`, // Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.
windowOpen: `請保持視窗為開啟狀態。`// Please keep this window open.
},
sms: {
enterCode: `輸入你從簡訊收到的驗證碼。`// Enter the code you received via SMS.
},
steps: {
code: `輸入驗證碼`, // Enter Code
completed: `完成`, // Completed
confirm: `確認`, // Confirm
data: `輸入資料`, // Enter Data
method: `方式`, // Method
request: `請求`// Request
},
title: `t驗證你的帳戶`, // verify your accoun
types: {
email: {
description: `你所控制的郵箱地址的雜湊值將被儲存在區塊鏈。`, // The hash of the e-mail address you prove control over will be stored on the blockchain.
label: `郵箱驗證`// E-mail Verification
},
sms: {
description: `你所控制的手機號碼將被儲存在區塊鏈。`, // It will be stored on the blockchain that you control a phone number (not <em>which</em>).
label: `簡訊驗證`// SMS Verification
}
}
};

View File

@ -0,0 +1,46 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
edit: `編輯`, // edit
forget: `forget`,
settings: `設定`, // settings
transfer: `轉帳`// transfer
},
confirmations: {
buttons: {
confirmAs: `確定為……`, // Confirm As...
revokeAs: `撤回為……`// Revoke As...
},
none: `現在沒有交易需要確認。`, // No transactions needs confirmation right now.
tooltip: {
confirmed: `被{number}/{required}所有人確認`// Confirmed by {number}/{required} owners
}
},
details: {
requiredOwners: `這個錢包需要至少{owners}所有人驗證所有的操作(交易,修改)`,
// This wallet requires at least {owners} to validate any action (transactions, modifications).
requiredOwnersNumber: `{number} {numberValue, plural, one {owner} other {owners}}`,
spent: `{spent} has been spent today, out of {limit} set as the daily limit, which has been reset on {date}`,
title: `細節`// Details
},
title: `錢包管理`, // Wallet Management
transactions: {
none: `沒有交易被髮送。`, // No transactions has been sent.
title: `交易`// Transactions
}
};

View File

@ -0,0 +1,75 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
addOwner: {
title: `新增持有人` // Add Owner
},
buttons: {
cancel: `取消`, // Cancel
close: `關閉`, // Close
next: `下一個`, // Next
send: `傳送`, // Send
sending: `正在傳送...` // Sending...
},
changes: {
modificationString: `為了保證你做的修改會被執行,
其他的帳戶持有人也需要傳送相同的修改
他們可以通過貼上以下字串來簡單的完成更改`,
// For your modifications to be taken into account,
// other owners have to send the same modifications. They can paste
// this string to make it easier:
none: `錢包設定沒有發生任何更改。`, // No modifications have been made to the Wallet settings.
overview: `你將會造成以下更改` // You are about to make the following modifications
},
edit: {
message: `為了編輯這個合約的設定,至少{owners, number}
{owners, plural, one {owner } other {owners }}必須傳送完全相同的修改
你可以將字串化的修改貼上在這裡`
// In order to edit this contract's settings, at
// least {owners, number} {owners, plural, one {owner } other {owners }} have to
// send the very same modifications. You can paste a stringified version
// of the modifications here.
},
modifications: {
daylimit: {
hint: `不需要確認即可傳送的ETH數量`, // amount of ETH spendable without confirmations
label: `錢包每日限額` // wallet day limit
},
fromString: {
label: `修改` // modifications
},
owners: {
label: `其他錢包持有人` // other wallet owners
},
required: {
hint: `確認交易所需的通過持有人人數`, // number of required owners to accept a transaction
label: `所需持有人` // required owners
},
sender: {
hint: `作為此持有人傳送修改`, // send modifications as this owner
label: `來自帳戶 (wallet owner)` // from account (wallet owner)
}
},
ownersChange: {
details: `從 {from} 至 {to}`, // from {from} to {to}
title: `改變所需持有人`
},
rejected: `交易#{txid}已經被拒絕`, // The transaction #{txid} has been rejected
removeOwner: {
title: `移除持有人` // Remove Owner
}
};

View File

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

View File

@ -0,0 +1,62 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default {
buttons: {
autoCompile: `自動編譯`, // Auto-Compile
compile: `編譯`, // Compile
deploy: `部署`, // Deploy
import: `載入Solidity`, // Import Solidity
load: `載入`, // Load
new: `新建`, // New
optimise: `優化`, // Optimise
save: `儲存` // Save
},
compiling: {
action: `請編譯原始碼`, // Please compile the source code.
busy: `編譯中...` // Compiling...
},
details: {
saved: `(已儲存 {timestamp})` // (saved {timestamp})
},
error: {
noContract: `沒有找到合約`, // No contract has been found.
params: `發生瞭如下描述的一個錯誤` // An error occurred with the following description
},
input: {
abi: `ABI介面`, // ABI Interface
code: `位元組碼`, // Bytecode
metadata: `元資料`, // Metadata
swarm: `Swarm元資料雜湊` // Sarm Metadata Hash
},
title: {
contract: `選擇一個合約`, // Select a contract
loading: `載入中...`, // Loading...
main: `寫一個合約`, // Write a Contract
messages: `編譯器訊息`, // Compiler messages
new: `新建Solidity合約`, // New Solidity Contract
parameters: `變數`, // Parameters
saved: `已儲存 @ {timestamp}`, // saved @ {timestamp}
selectSolidity: `選擇Solidity版本`, // Select a Solidity version
solidity: `正在載入Solidity {version}` // Loading Solidity {version}
},
type: {
humanErc20: `Human代幣合約編碼`, // Implementation of the Human Token Contract
implementErc20: `ERC20代幣合約編碼`, // Implementation of the ERC20 Token Contract
multisig: `多籤錢包編碼`, // Implementation of a multisig Wallet
standardErc20: `標準ERC20代幣合約` // Standard ERC20 Token Contract
}
};

View File

@ -16,24 +16,24 @@
export default { export default {
button: { button: {
delete: `delete`, delete: `删除`, // delete
edit: `edit`, edit: `编辑`, // edit
faucet: `Kovan ETH`, faucet: `Kovan测试网络以太币`, // Kovan ETH
password: `password`, password: `密码`, // password
shapeshift: `shapeshift`, shapeshift: `shapeshift`,
transfer: `transfer`, transfer: `转账`, // transfer
verify: `verify` verify: `确认`// verify
}, },
hardware: { hardware: {
confirmDelete: `Are you sure you want to remove the following hardware address from your account list?` confirmDelete: `你确定从你的账户列表中移除下面的硬件地址吗?`
}, }, // Are you sure you want to remove the following hardware address from your account list?
header: { header: {
outgoingTransactions: `{count} outgoing transactions`, outgoingTransactions: `{count}笔正在发生的转账`, // {count} outgoing transactions
uuid: `uuid: {uuid}` uuid: `uuid: {uuid}`
}, },
title: `Account Management`, title: `账户管理`, // Account Management
transactions: { transactions: {
poweredBy: `Transaction list powered by {etherscan}`, poweredBy: `Transaction list powered by {etherscan}提供的交易列表`,
title: `transactions` title: `交易`// transactions
} }
}; };

View File

@ -16,19 +16,21 @@
export default { export default {
button: { button: {
newAccount: `account`, newAccount: `账户`, // account
newWallet: `wallet`, newWallet: `钱包`, // wallet
vaults: `vaults` vaults: `保险库`// vaults
}, },
summary: { summary: {
minedBlock: `Mined at block #{blockNumber}` minedBlock: `在第#{blockNumber}个区块被挖出`// Mined at block #{blockNumber}
}, },
title: `Accounts Overview`, title: `账户总览`, // Accounts Overview
tooltip: { tooltip: {
actions: `actions relating to the current view are available on the toolbar for quick access, be it for performing actions or creating a new item`, actions: `与当前视窗有关的操作可以在工具栏中快速被找到,不论是执行操作还是创建新项`,
overview: `your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account` // actions relating to the current view are available on the toolbar for quick access, be it for performing actions or creating a new item
overview: `你的账户很容易使用,使你可以编辑元信息、转账、查看交易和向账户充值`
// your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account
}, },
tooltips: { tooltips: {
owner: `{name} (owner)` owner: `{name}持有者`// {name} (owner)
} }
}; };

View File

@ -16,23 +16,26 @@
export default { export default {
button: { button: {
add: `Save Address`, add: `保存地址`, // Save Address
close: `Cancel` close: `取消` // Cancel
}, },
header: `To add a new entry to your addressbook, you need the network address of the account and can supply an optional description. Once added it will reflect in your address book.`, header: `如果想在地址簿中添加一条新的记录,你需要拥有账户的网络地址并提供一个的描述(可选)。一旦添加,记录就可以体现在你的地址簿中。`,
// To add a new entry to your addressbook, you need the network
// address of the account and can supply an optional description.
// Once added it will reflect in your address book.
input: { input: {
address: { address: {
hint: `the network address for the entry`, hint: `记录的网络地址`, // the network address for the entry
label: `network address` label: `网络地址` // network address
}, },
description: { description: {
hint: `an expanded description for the entry`, hint: `记录的详细描述`, // an expanded description for the entry
label: `(optional) address description` label: `(可选)地址描述` // (optional) address description
}, },
name: { name: {
hint: `a descriptive name for the entry`, hint: `记录的名字`, // a descriptive name for the entry
label: `address name` label: `地址名` // address name
} }
}, },
label: `add saved address` label: `添加已保存的地址` // add saved address
}; };

View File

@ -16,45 +16,45 @@
export default { export default {
abi: { abi: {
hint: `the abi for the contract`, hint: `合约的ABI`, // the abi for the contract
label: `contract abi` label: `合约ABI` // contract abi
}, },
abiType: { abiType: {
custom: { custom: {
description: `Contract created from custom ABI`, description: `通过自定义ABI创造的合约`, // Contract created from custom ABI
label: `Custom Contract` label: `自定义合约` // Custom Contract
}, },
multisigWallet: { multisigWallet: {
description: `Ethereum Multisig contract {link}`, description: `以太坊多重签名合约{link}`, // Ethereum Multisig contract {link}
label: `Multisig Wallet`, label: `多重签名钱包`, // Multisig Wallet
link: `see contract code` link: `参考合约代码` // see contract code
}, },
token: { token: {
description: `A standard {erc20} token`, description: `一个标准的{erc20}代币`, // A standard {erc20} token
erc20: `ERC 20`, erc20: `ERC 20`, // ERC 20
label: `Token` label: `代币` // Token
} }
}, },
address: { address: {
hint: `the network address for the contract`, hint: `合约的网络地址`, // the network address for the contract
label: `network address` label: `网络地址` // network address
}, },
button: { button: {
add: `Add Contract`, add: `添加合约`, // Add Contract
cancel: `Cancel`, cancel: `取消`, // Cancel
next: `Next`, next: `下一步`, // Next
prev: `Back` prev: `上一步` // Back
}, },
description: { description: {
hint: `an expanded description for the entry`, hint: `记录的详细描述`, // an expanded description for the entry
label: `(optional) contract description` label: `(可选)合约描述` // (optional) contract description
}, },
name: { name: {
hint: `a descriptive name for the contract`, hint: `合约的描述性名称`, // a descriptive name for the contract
label: `contract name` label: `合约名` // contract name
}, },
title: { title: {
details: `enter contract details`, details: `输入合约细节`, // enter contract details
type: `choose a contract type` type: `选择合约种类` // choose a contract type
} }
}; };

View File

@ -16,13 +16,13 @@
export default { export default {
buttons: { buttons: {
edit: `edit`, edit: `编辑`, // edit
forget: `forget`, forget: `忘记`, // forget
save: `save` save: `保存`// save
}, },
delete: { delete: {
confirmInfo: `Are you sure you want to remove the following address from your addressbook?`, confirmInfo: `你确定你想把下面的地址从你的地址簿中移除吗?`, // Are you sure you want to remove the following address from your addressbook?
title: `confirm removal` title: `确认移除`// confirm removal
}, },
title: `Address Information` title: `地址信息`// Address Information
}; };

View File

@ -15,12 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default { export default {
fromEmail: `Verified using email {email}`, fromEmail: `使用邮箱{email}进行确认`, // Verified using email {email}
fromRegistry: `{name} (from registry)`, fromRegistry: `{name}(来自注册)`, // {name} (from registry)
labels: { labels: {
accounts: `accounts`, accounts: `账户`, // accounts
contacts: `contacts`, contacts: `合约`, // contacts
contracts: `contracts` contracts: `合约`// contracts
}, },
noAccount: `No account matches this query...` noAccount: `查不到这个账户`// No account matches this query...
}; };

View File

@ -16,10 +16,10 @@
export default { export default {
buttons: { buttons: {
add: `address` add: `地址` // address
}, },
errors: { errors: {
invalidFile: `The provided file is invalid...` invalidFile: `提供的文件是无效的`// The provided file is invalid...
}, },
title: `Saved Addresses` title: `保存的地址`// Saved Addresses
}; };

View File

@ -16,15 +16,15 @@
export default { export default {
frame: { frame: {
error: `ERROR: This application cannot and should not be loaded in an embedded iFrame` error: `错误:这个应用不能也不应该载入到内置框架中`// ERROR: This application cannot and should not be loaded in an embedded iFrame
}, },
status: { status: {
consensus: { consensus: {
capable: `Capable`, capable: `可行`, // Capable
capableUntil: `Capable until #{blockNumber}`, capableUntil: `到第 #{blockNumber} 区块前可行`, // Capable until #{blockNumber}
incapableSince: `Incapable since #{blockNumber}`, incapableSince: `自第 #{blockNumber} 区块后不可行`, // Incapable since #{blockNumber}
unknown: `Unknown capability` unknown: `未知能力`// Unknown capability
}, },
upgrade: `Upgrade` upgrade: `升级`// Upgrade
} }
}; };

View File

@ -13,14 +13,18 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default { export default {
connectingAPI: `Connecting to the Parity Secure API.`, connectingAPI: `正在连接至Parity Secure API`, // Connecting to the Parity Secure API.
connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`, connectingNode: `正在连接Parity节点。如果弹出任何信息请确认你的Parity节点正在运行并连接至互联网。`,
invalidToken: `invalid signer token`, // Connecting to the Parity Node. If this informational message persists,
noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.`, // please ensure that your Parity node is running and reachable on the network.
invalidToken: `无效的签名令牌`, // invalid signer token
noConnection: `无法连接至Parity Secure API。请升级的你的安全令牌或者生成一个新的运行{newToken}并粘贴生成的令牌到下方。`,
// Unable to make a connection to the Parity Secure API. To update your secure
// token or to generate a new one, run {newToken} and paste the generated token
// into the space below.
token: { token: {
hint: `a generated token from Parity`, hint: `一个Parity生成的令牌`, // a generated token from Parity
label: `secure token` label: `安全令牌` // secure token
} }
}; };

View File

@ -16,26 +16,26 @@
export default { export default {
buttons: { buttons: {
close: `Close`, close: `关闭`, // close
details: `details`, details: `详细资料`, // details
edit: `edit`, edit: `编辑`, // edit
execute: `execute`, execute: `运行`, // execute
forget: `forget` forget: `forget` // forget
}, },
details: { details: {
title: `contract details` title: `合约细节` // contract details
}, },
events: { events: {
eventPending: `pending`, eventPending: `待定中`, // pending
noEvents: `No events has been sent from this contract.`, noEvents: `此合约没有发送过任何事件`, // No events has been sent from this contract.
title: `events` title: `事件` // events
}, },
minedBlock: `Mined at block #{blockNumber}`, minedBlock: `挖到了第{blockNumber}个区块`, // Mined at block #{blockNumber}
queries: { queries: {
buttons: { buttons: {
query: `Query` query: `查询` // Query
}, },
title: `queries` title: `查询` // queries
}, },
title: `Contract Information` title: `合约信息` // Contract Information
}; };

View File

@ -16,13 +16,13 @@
export default { export default {
buttons: { buttons: {
deploy: `deploy`, deploy: `部署`, // deploy
develop: `develop`, develop: `开发`, // develop
watch: `watch` watch: `观察` // watch
}, },
sortOrder: { sortOrder: {
date: `date`, date: `日期`, // date
minedBlock: `mined block` minedBlock: `挖到的区块` // mined block
}, },
title: `Contracts` title: `合约` // Contracts
}; };

View File

@ -16,91 +16,91 @@
export default { export default {
button: { button: {
add: `Add`, add: `添加`, // Add
cancel: `Cancel`, cancel: `取消`, // Cancel
close: `Close`, close: `关闭`, // Close
create: `Create`, create: `创建`, // Create
done: `Done`, done: `完成`, // Done
next: `Next`, next: `下一步`, // Next
sending: `Sending...` sending: `发送中...` // Sending...
}, },
deployment: { deployment: {
message: `The deployment is currently in progress` message: `部署正在进行中` // The deployment is currently in progress
}, },
details: { details: {
address: { address: {
hint: `the wallet contract address`, hint: `钱包的合约地址`, // the wallet contract address
label: `wallet address` label: `钱包地址` // wallet address
}, },
dayLimitMulti: { dayLimitMulti: {
hint: `amount of ETH spendable without confirmations`, hint: `无需确认即可使用的ETH数量`, // amount of ETH spendable without confirmations
label: `wallet day limit` label: `钱包每日限额` // wallet day limit
}, },
description: { description: {
hint: `the local description for this wallet`, hint: `本地钱包描述`, // the local description for this wallet
label: `wallet description (optional)` label: `钱包描述(可选)` // wallet description (optional)
}, },
descriptionMulti: { descriptionMulti: {
hint: `the local description for this wallet`, hint: `本地钱包描述`, // the local description for this wallet
label: `wallet description (optional)` label: `钱包描述(可选)` // wallet description (optional)
}, },
name: { name: {
hint: `the local name for this wallet`, hint: `钱包本地名称`, // the local name for this wallet
label: `wallet name` label: `钱包名称` // wallet name
}, },
nameMulti: { nameMulti: {
hint: `the local name for this wallet`, hint: `钱包本地名称`, // the local name for this wallet
label: `wallet name` label: `钱包名称` // wallet name
}, },
ownerMulti: { ownerMulti: {
hint: `the owner account for this contract`, hint: `合约的持有者账户`, // the owner account for this contract
label: `from account (contract owner)` label: `从账户 (contract owner)` // from account (contract owner)
}, },
ownersMulti: { ownersMulti: {
label: `other wallet owners` label: `其他钱包持有者` // other wallet owners
}, },
ownersMultiReq: { ownersMultiReq: {
hint: `number of required owners to accept a transaction`, hint: `接受交易所需的持有者人数`, // number of required owners to accept a transaction
label: `required owners` label: `所需持有者` // required owners
} }
}, },
info: { info: {
added: `added`, added: `已添加`, // added
copyAddress: `copy address to clipboard`, copyAddress: `复制地址至粘贴板`, // copy address to clipboard
created: `{name} has been {deployedOrAdded} at`, created: `{name}已被{deployedOrAdded}至`, // {name} has been {deployedOrAdded} at
dayLimit: `The daily limit is set to {dayLimit} ETH.`, dayLimit: `每日限额已被设置为{dayLimit}ETH`, // The daily limit is set to {dayLimit} ETH.
deployed: `deployed`, deployed: `已部署`, // deployed
numOwners: `{numOwners} owners are required to confirm a transaction.`, numOwners: `需要{numOwners}个持有者才能确认一个交易`, // {numOwners} owners are required to confirm a transaction.
owners: `The following are wallet owners` owners: `以下为钱包持有者` // The following are wallet owners
}, },
rejected: { rejected: {
message: `The deployment has been rejected`, message: `部署被拒绝`, // The deployment has been rejected
state: `The wallet will not be created. You can safely close this window.`, state: `钱包不会被创建。你可以安全地关闭本窗口`, // The wallet will not be created. You can safely close this window.
title: `rejected` title: `失败` // rejected
}, },
states: { states: {
completed: `The contract deployment has been completed`, completed: `合约部署已完成`, // The contract deployment has been completed
confirmationNeeded: `The contract deployment needs confirmations from other owners of the Wallet`, confirmationNeeded: `合约部署需要来自本钱包的其他持有者的确认`, // The contract deployment needs confirmations from other owners of the Wallet
preparing: `Preparing transaction for network transmission`, preparing: `交易正在准备被网络广播`, // Preparing transaction for network transmission
validatingCode: `Validating the deployed contract code`, validatingCode: `正在验证已部署的代码`, // Validating the deployed contract code
waitingConfirm: `Waiting for confirmation of the transaction in the Parity Secure Signer`, waitingConfirm: `正在等待Parity Secure Signer确认本交易`, // Waiting for confirmation of the transaction in the Parity Secure Signer
waitingReceipt: `Waiting for the contract deployment transaction receipt` waitingReceipt: `正在等待合约部署交易收据` // Waiting for the contract deployment transaction receipt
}, },
steps: { steps: {
deployment: `wallet deployment`, deployment: `钱包部署`, // wallet deployment
details: `wallet details`, details: `钱包详情`, // wallet details
info: `wallet informaton`, info: `钱包信息`, // wallet informaton
type: `wallet type` type: `钱包类别` // wallet type
}, },
type: { type: {
multisig: { multisig: {
description: `Create/Deploy a {link} Wallet`, description: `创建/部署一个{link}钱包`, // Create/Deploy a {link} Wallet
label: `Multi-Sig wallet`, label: `多重签名钱包`, // Multi-Sig Wallet
link: `standard multi-signature` link: `标准多重签名` // standard multi-signature
}, },
watch: { watch: {
description: `Add an existing wallet to your accounts`, description: `添加一个已有钱包到你的账户`, // Add an existing wallet to your accounts
label: `Watch a wallet` label: `观察钱包` // Watch a wallet
} }
} }
}; };

View File

@ -15,6 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default { export default {
loading: `Loading`, loading: `正在加载`, // Loading
unavailable: `The dapp cannot be reached` unavailable: `不能获取这个dapp`// The dapp cannot be reached
}; };

View File

@ -17,29 +17,32 @@
export default { export default {
add: { add: {
builtin: { builtin: {
desc: `Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.`, desc: `Parity团队开发的实验性的用以展示dapp的性能、集成、实验性特性和控制特定网络的的客户端行为`,
label: `Applications bundled with Parity` // Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.
label: `与Parity绑定的应用`// Applications bundled with Parity
}, },
label: `visible applications`, label: `visible applications可见的应用`, // visible applications
local: { local: {
desc: `All applications installed locally on the machine by the user for access by the Parity client.`, desc: `All applications installed locally on the machine by the user for access by the Parity client.`,
label: `Applications locally available` label: `本地可用的应用`// Applications locally available
}, },
network: { network: {
desc: `These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.`, desc: `这些应用与Parity没有关联也不是Parity发布的。 它们是由各自的作者控制的。 在使用以前,请确保你理解每个应用的目标。`,
label: `Applications on the global network` // These applications are not affiliated with Parity nor are they published by Parity.Each remain under the control of their respective authors.Please ensure that you understand the goals for each application before interacting.
label: `全球网络上的应用`// Applications on the global network
} }
}, },
button: { button: {
edit: `edit`, edit: `编辑`, // edit
permissions: `permissions` permissions: `许可`// permissions
}, },
external: { external: {
accept: `I understand that these applications are not affiliated with Parity`, accept: `我理解这些应用和Parity没有关联`, // I understand that these applications are not affiliated with Parity
warning: `Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.` warning: `第三方开发者开发的应用与Parity没有关联也不是Parity发布的。 它们是由各自的作者控制的。 在使用以前,请确保你理解每个应用的目标。`
// Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.
}, },
label: `Decentralized Applications`, label: `去中心化应用`, // Decentralized Applications
permissions: { permissions: {
label: `visible dapp accounts` label: `可见的dapp账户`// visible dapp accounts
} }
}; };

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