Merge branch 'master' into lightsync
This commit is contained in:
commit
a1f32de2d9
@ -231,10 +231,10 @@ linux-armv6:
|
|||||||
stage: build
|
stage: build
|
||||||
image: ethcore/rust-armv6:latest
|
image: ethcore/rust-armv6:latest
|
||||||
only:
|
only:
|
||||||
# - beta
|
- beta
|
||||||
# - tags
|
# - tags
|
||||||
# - stable
|
# - stable
|
||||||
- triggers
|
# - triggers
|
||||||
script:
|
script:
|
||||||
- export CC=arm-linux-gnueabi-gcc
|
- export CC=arm-linux-gnueabi-gcc
|
||||||
- export CXX=arm-linux-gnueabi-g++
|
- export CXX=arm-linux-gnueabi-g++
|
||||||
@ -312,8 +312,8 @@ darwin:
|
|||||||
- stable
|
- stable
|
||||||
- triggers
|
- triggers
|
||||||
script:
|
script:
|
||||||
- cargo build -j 8 --release -p ethstore #$CARGOFLAGS
|
|
||||||
- cargo build -j 8 --release #$CARGOFLAGS
|
- cargo build -j 8 --release #$CARGOFLAGS
|
||||||
|
- cargo build -j 8 --release -p ethstore #$CARGOFLAGS
|
||||||
- rm -rf parity.md5
|
- rm -rf parity.md5
|
||||||
- md5sum target/release/parity > parity.md5
|
- md5sum target/release/parity > parity.md5
|
||||||
- packagesbuild -v mac/Parity.pkgproj
|
- packagesbuild -v mac/Parity.pkgproj
|
||||||
@ -350,7 +350,7 @@ windows:
|
|||||||
- set RUST_BACKTRACE=1
|
- set RUST_BACKTRACE=1
|
||||||
- set RUSTFLAGS=%RUSTFLAGS%
|
- set RUSTFLAGS=%RUSTFLAGS%
|
||||||
- rustup default stable-x86_64-pc-windows-msvc
|
- rustup default stable-x86_64-pc-windows-msvc
|
||||||
- cargo build -j 8 --release #%CARGOFLAGS%
|
- cargo build --release #%CARGOFLAGS%
|
||||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
||||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||||
@ -408,7 +408,7 @@ test-darwin:
|
|||||||
test-windows:
|
test-windows:
|
||||||
stage: test
|
stage: test
|
||||||
only:
|
only:
|
||||||
- triggers
|
# - triggers
|
||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
script:
|
script:
|
||||||
|
197
Cargo.lock
generated
197
Cargo.lock
generated
@ -3,6 +3,7 @@ name = "parity"
|
|||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)",
|
"ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)",
|
||||||
"daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -32,9 +33,11 @@ dependencies = [
|
|||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parity-rpc-client 1.4.0",
|
||||||
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rpc-cli 1.4.0",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -42,7 +45,7 @@ dependencies = [
|
|||||||
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -58,6 +61,17 @@ name = "ansi_term"
|
|||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "app_dirs"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
@ -196,7 +210,7 @@ source = "git+https://github.com/ethcore/rust-ctrlc.git#f4927770f89eca80ec250911
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -277,7 +291,7 @@ name = "ethash"
|
|||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"sha3 0.1.0",
|
"sha3 0.1.0",
|
||||||
]
|
]
|
||||||
@ -395,7 +409,7 @@ dependencies = [
|
|||||||
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
|
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -501,7 +515,7 @@ dependencies = [
|
|||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
|
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -531,6 +545,7 @@ dependencies = [
|
|||||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
|
"jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -597,7 +612,7 @@ dependencies = [
|
|||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
@ -659,7 +674,7 @@ dependencies = [
|
|||||||
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rust-crypto 0.2.36 (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)",
|
||||||
@ -688,7 +703,7 @@ dependencies = [
|
|||||||
"ethkey 0.2.0",
|
"ethkey 0.2.0",
|
||||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -729,6 +744,14 @@ dependencies = [
|
|||||||
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcc"
|
name = "gcc"
|
||||||
version = "0.3.35"
|
version = "0.3.35"
|
||||||
@ -845,7 +868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -860,8 +883,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-core"
|
name = "jsonrpc-core"
|
||||||
version = "4.0.0"
|
version = "3.0.2"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -870,10 +893,22 @@ dependencies = [
|
|||||||
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpc-core"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-http-server"
|
name = "jsonrpc-http-server"
|
||||||
version = "6.1.1"
|
version = "6.1.1"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53"
|
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||||
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
@ -884,7 +919,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-ipc-server"
|
name = "jsonrpc-ipc-server"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53"
|
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -896,10 +931,19 @@ dependencies = [
|
|||||||
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpc-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||||
|
dependencies = [
|
||||||
|
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
|
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-tcp-server"
|
name = "jsonrpc-tcp-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53"
|
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -916,7 +960,7 @@ name = "kernel32-sys"
|
|||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1017,7 +1061,7 @@ dependencies = [
|
|||||||
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1033,7 +1077,7 @@ dependencies = [
|
|||||||
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1048,7 +1092,7 @@ dependencies = [
|
|||||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)",
|
"slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1064,7 +1108,7 @@ dependencies = [
|
|||||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nix 0.7.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)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1074,7 +1118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1085,7 +1129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1114,7 +1158,7 @@ dependencies = [
|
|||||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1253,6 +1297,15 @@ name = "odds"
|
|||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ole32-sys"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owning_ref"
|
name = "owning_ref"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -1272,6 +1325,26 @@ dependencies = [
|
|||||||
"syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parity-rpc-client"
|
||||||
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore-rpc 1.5.0",
|
||||||
|
"ethcore-signer 1.5.0",
|
||||||
|
"ethcore-util 1.5.0",
|
||||||
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui"
|
name = "parity-ui"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -1291,7 +1364,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#eb9d978ed5ad1c514b37e89c716f80b3c8d613b5"
|
source = "git+https://github.com/ethcore/js-precompiled.git#2cdda91549dfeebd94775b348a443f8ee5446e9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -1304,12 +1377,12 @@ dependencies = [
|
|||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.3.5"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1325,7 +1398,7 @@ dependencies = [
|
|||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (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)",
|
||||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1548,7 +1621,30 @@ dependencies = [
|
|||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpassword"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpc-cli"
|
||||||
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore-bigint 0.1.2",
|
||||||
|
"ethcore-rpc 1.5.0",
|
||||||
|
"ethcore-util 1.5.0",
|
||||||
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parity-rpc-client 1.4.0",
|
||||||
|
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1652,6 +1748,15 @@ dependencies = [
|
|||||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shell32-sys"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1786,13 +1891,21 @@ name = "target_info"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempdir"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "term"
|
name = "term"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1801,7 +1914,7 @@ version = "0.4.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1836,7 +1949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1932,7 +2045,7 @@ name = "vecio"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1963,7 +2076,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.2.6"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1991,10 +2104,15 @@ name = "ws2_32-sys"
|
|||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@ -2025,6 +2143,7 @@ dependencies = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03"
|
"checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03"
|
||||||
"checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962"
|
"checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962"
|
||||||
|
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
|
||||||
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
||||||
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
||||||
"checksum aster 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4df293303e8a52e1df7984ac1415e195f5fcbf51e4bb7bda54557861a3954a08"
|
"checksum aster 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4df293303e8a52e1df7984ac1415e195f5fcbf51e4bb7bda54557861a3954a08"
|
||||||
@ -2055,6 +2174,7 @@ dependencies = [
|
|||||||
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
|
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
|
||||||
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
|
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
|
||||||
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"
|
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"
|
||||||
|
"checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897"
|
||||||
"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312"
|
"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312"
|
||||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||||
"checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
"checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
||||||
@ -2068,9 +2188,11 @@ dependencies = [
|
|||||||
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
|
"checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c"
|
||||||
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
|
"checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76"
|
||||||
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
||||||
|
"checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414"
|
||||||
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
|
"checksum jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||||
@ -2110,11 +2232,12 @@ dependencies = [
|
|||||||
"checksum num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "51fedae97a05f7353612fe017ab705a37e6db8f4d67c5c6fe739a9e70d6eed09"
|
"checksum num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "51fedae97a05f7353612fe017ab705a37e6db8f4d67c5c6fe739a9e70d6eed09"
|
||||||
"checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77"
|
"checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77"
|
||||||
"checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1"
|
"checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1"
|
||||||
|
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
||||||
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
||||||
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
||||||
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
||||||
"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6"
|
"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6"
|
||||||
"checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125"
|
"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621"
|
||||||
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
||||||
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
|
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
|
||||||
"checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d"
|
"checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d"
|
||||||
@ -2141,6 +2264,7 @@ dependencies = [
|
|||||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||||
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
||||||
"checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f"
|
"checksum rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d3a99497c5c544e629cc8b359ae5ede321eba5fa8e5a8078f3ced727a976c3f"
|
||||||
|
"checksum rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab6e42be826e215f30ff830904f8f4a0933c6e2ae890e1af8b408f5bae60081e"
|
||||||
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
||||||
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
||||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||||
@ -2152,6 +2276,7 @@ dependencies = [
|
|||||||
"checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21"
|
"checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21"
|
||||||
"checksum serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e10f8a9d94b06cf5d3bef66475f04c8ff90950f1be7004c357ff9472ccbaebc"
|
"checksum serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e10f8a9d94b06cf5d3bef66475f04c8ff90950f1be7004c357ff9472ccbaebc"
|
||||||
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
|
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
|
||||||
|
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
|
||||||
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"
|
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"
|
||||||
"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
|
"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
|
||||||
"checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "<none>"
|
"checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "<none>"
|
||||||
@ -2170,6 +2295,7 @@ dependencies = [
|
|||||||
"checksum syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44bded3cabafc65c90b663b1071bd2d198a9ab7515e6ce729e4570aaf53c407e"
|
"checksum syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44bded3cabafc65c90b663b1071bd2d198a9ab7515e6ce729e4570aaf53c407e"
|
||||||
"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d"
|
"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d"
|
||||||
"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe"
|
"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe"
|
||||||
|
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||||
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
|
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
|
||||||
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
|
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
|
||||||
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
||||||
@ -2193,10 +2319,11 @@ dependencies = [
|
|||||||
"checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c"
|
"checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c"
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum webpki 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "813503a5985585e0812d430cd1328ee322f47f66629c8ed4ecab939cf9e92f91"
|
"checksum webpki 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "813503a5985585e0812d430cd1328ee322f47f66629c8ed4ecab939cf9e92f91"
|
||||||
"checksum winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfaaa8fbdaa618fa6914b59b2769d690dd7521920a18d84b42d254678dd5fd4"
|
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
"checksum ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)" = "<none>"
|
"checksum ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)" = "<none>"
|
||||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
|
"checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453"
|
||||||
"checksum xml-rs 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "65e74b96bd3179209dc70a980da6df843dff09e46eee103a0376c0949257e3ef"
|
"checksum xml-rs 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "65e74b96bd3179209dc70a980da6df843dff09e46eee103a0376c0949257e3ef"
|
||||||
"checksum xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "472a9d37c7c53ab2391161df5b89b1f3bf76dab6ab150d7941ecbdd832282082"
|
"checksum xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "472a9d37c7c53ab2391161df5b89b1f3bf76dab6ab150d7941ecbdd832282082"
|
||||||
"checksum zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3ceb33a75b3d0608942302eed325b59d2c3ed777cc6c01627ae14e5697c6a31c"
|
"checksum zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3ceb33a75b3d0608942302eed325b59d2c3ed777cc6c01627ae14e5697c6a31c"
|
||||||
|
@ -28,6 +28,7 @@ isatty = "0.1"
|
|||||||
toml = "0.2"
|
toml = "0.2"
|
||||||
serde = "0.8.0"
|
serde = "0.8.0"
|
||||||
serde_json = "0.8.0"
|
serde_json = "0.8.0"
|
||||||
|
app_dirs = "1.1.1"
|
||||||
hyper = { version = "0.9", default-features = false }
|
hyper = { version = "0.9", default-features = false }
|
||||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||||
fdlimit = "0.1"
|
fdlimit = "0.1"
|
||||||
@ -47,6 +48,8 @@ rlp = { path = "util/rlp" }
|
|||||||
ethcore-stratum = { path = "stratum" }
|
ethcore-stratum = { path = "stratum" }
|
||||||
ethcore-dapps = { path = "dapps", optional = true }
|
ethcore-dapps = { path = "dapps", optional = true }
|
||||||
clippy = { version = "0.0.103", optional = true}
|
clippy = { version = "0.0.103", optional = true}
|
||||||
|
rpc-cli = { path = "rpc_cli" }
|
||||||
|
parity-rpc-client = { path = "rpc_client" }
|
||||||
ethcore-light = { path = "ethcore/light" }
|
ethcore-light = { path = "ethcore/light" }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
@ -120,7 +120,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
|||||||
// Content is already being fetched
|
// Content is already being fetched
|
||||||
Some(&mut ContentStatus::Fetching(ref fetch_control)) => {
|
Some(&mut ContentStatus::Fetching(ref fetch_control)) => {
|
||||||
trace!(target: "dapps", "Content fetching in progress. Waiting...");
|
trace!(target: "dapps", "Content fetching in progress. Waiting...");
|
||||||
(None, fetch_control.to_handler(control))
|
(None, fetch_control.to_async_handler(path, control))
|
||||||
},
|
},
|
||||||
// We need to start fetching the content
|
// We need to start fetching the content
|
||||||
None => {
|
None => {
|
||||||
@ -129,11 +129,12 @@ impl<R: URLHint> ContentFetcher<R> {
|
|||||||
let content = self.resolver.resolve(content_hex);
|
let content = self.resolver.resolve(content_hex);
|
||||||
|
|
||||||
let cache = self.cache.clone();
|
let cache = self.cache.clone();
|
||||||
let on_done = move |id: String, result: Option<LocalPageEndpoint>| {
|
let id = content_id.clone();
|
||||||
|
let on_done = move |result: Option<LocalPageEndpoint>| {
|
||||||
let mut cache = cache.lock();
|
let mut cache = cache.lock();
|
||||||
match result {
|
match result {
|
||||||
Some(endpoint) => {
|
Some(endpoint) => {
|
||||||
cache.insert(id, ContentStatus::Ready(endpoint));
|
cache.insert(id.clone(), ContentStatus::Ready(endpoint));
|
||||||
},
|
},
|
||||||
// In case of error
|
// In case of error
|
||||||
None => {
|
None => {
|
||||||
@ -150,6 +151,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
|||||||
Some(URLHintResult::Dapp(dapp)) => {
|
Some(URLHintResult::Dapp(dapp)) => {
|
||||||
let (handler, fetch_control) = ContentFetcherHandler::new(
|
let (handler, fetch_control) = ContentFetcherHandler::new(
|
||||||
dapp.url(),
|
dapp.url(),
|
||||||
|
path,
|
||||||
control,
|
control,
|
||||||
DappInstaller {
|
DappInstaller {
|
||||||
id: content_id.clone(),
|
id: content_id.clone(),
|
||||||
@ -165,6 +167,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
|||||||
Some(URLHintResult::Content(content)) => {
|
Some(URLHintResult::Content(content)) => {
|
||||||
let (handler, fetch_control) = ContentFetcherHandler::new(
|
let (handler, fetch_control) = ContentFetcherHandler::new(
|
||||||
content.url,
|
content.url,
|
||||||
|
path,
|
||||||
control,
|
control,
|
||||||
ContentInstaller {
|
ContentInstaller {
|
||||||
id: content_id.clone(),
|
id: content_id.clone(),
|
||||||
@ -248,13 +251,14 @@ struct ContentInstaller {
|
|||||||
id: String,
|
id: String,
|
||||||
mime: String,
|
mime: String,
|
||||||
content_path: PathBuf,
|
content_path: PathBuf,
|
||||||
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
|
on_done: Box<Fn(Option<LocalPageEndpoint>) + Send>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentValidator for ContentInstaller {
|
impl ContentValidator for ContentInstaller {
|
||||||
type Error = ValidationError;
|
type Error = ValidationError;
|
||||||
|
|
||||||
fn validate_and_install(&self, path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
|
fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, ValidationError> {
|
||||||
|
let validate = || {
|
||||||
// Create dir
|
// Create dir
|
||||||
try!(fs::create_dir_all(&self.content_path));
|
try!(fs::create_dir_all(&self.content_path));
|
||||||
|
|
||||||
@ -279,12 +283,13 @@ impl ContentValidator for ContentInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try!(fs::copy(&path, &content_path));
|
try!(fs::copy(&path, &content_path));
|
||||||
|
Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))
|
||||||
|
};
|
||||||
|
|
||||||
Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)))
|
// Make sure to always call on_done (even in case of errors)!
|
||||||
}
|
let result = validate();
|
||||||
|
(self.on_done)(result.as_ref().ok().cloned());
|
||||||
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
|
result
|
||||||
(self.on_done)(self.id.clone(), endpoint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +297,7 @@ impl ContentValidator for ContentInstaller {
|
|||||||
struct DappInstaller {
|
struct DappInstaller {
|
||||||
id: String,
|
id: String,
|
||||||
dapps_path: PathBuf,
|
dapps_path: PathBuf,
|
||||||
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
|
on_done: Box<Fn(Option<LocalPageEndpoint>) + Send>,
|
||||||
embeddable_on: Option<(String, u16)>,
|
embeddable_on: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,9 +336,10 @@ impl DappInstaller {
|
|||||||
impl ContentValidator for DappInstaller {
|
impl ContentValidator for DappInstaller {
|
||||||
type Error = ValidationError;
|
type Error = ValidationError;
|
||||||
|
|
||||||
fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
|
fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, ValidationError> {
|
||||||
trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path);
|
trace!(target: "dapps", "Opening dapp bundle at {:?}", path);
|
||||||
let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path)));
|
let validate = || {
|
||||||
|
let mut file_reader = io::BufReader::new(try!(fs::File::open(path)));
|
||||||
let hash = try!(sha3(&mut file_reader));
|
let hash = try!(sha3(&mut file_reader));
|
||||||
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
|
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
|
||||||
if id != hash {
|
if id != hash {
|
||||||
@ -384,16 +390,14 @@ impl ContentValidator for DappInstaller {
|
|||||||
let manifest_path = target.join(MANIFEST_FILENAME);
|
let manifest_path = target.join(MANIFEST_FILENAME);
|
||||||
let mut manifest_file = try!(fs::File::create(manifest_path));
|
let mut manifest_file = try!(fs::File::create(manifest_path));
|
||||||
try!(manifest_file.write_all(manifest_str.as_bytes()));
|
try!(manifest_file.write_all(manifest_str.as_bytes()));
|
||||||
|
|
||||||
// Create endpoint
|
// Create endpoint
|
||||||
let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
|
let endpoint = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
|
||||||
|
Ok(endpoint)
|
||||||
|
};
|
||||||
|
|
||||||
// Return modified app manifest
|
let result = validate();
|
||||||
Ok((manifest.id.clone(), app))
|
(self.on_done)(result.as_ref().ok().cloned());
|
||||||
}
|
result
|
||||||
|
|
||||||
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
|
|
||||||
(self.on_done)(self.id.clone(), endpoint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,35 +22,41 @@ use std::sync::{mpsc, Arc};
|
|||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
use util::Mutex;
|
use util::Mutex;
|
||||||
use url::Url;
|
|
||||||
use fetch::{Client, Fetch, FetchResult};
|
use fetch::{Client, Fetch, FetchResult};
|
||||||
|
|
||||||
use hyper::{server, Decoder, Encoder, Next, Method, Control};
|
use hyper::{server, Decoder, Encoder, Next, Method, Control};
|
||||||
use hyper::net::HttpStream;
|
use hyper::net::HttpStream;
|
||||||
|
use hyper::uri::RequestUri;
|
||||||
use hyper::status::StatusCode;
|
use hyper::status::StatusCode;
|
||||||
|
|
||||||
use handlers::{ContentHandler, Redirection, extract_url};
|
use endpoint::EndpointPath;
|
||||||
use page::LocalPageEndpoint;
|
use handlers::ContentHandler;
|
||||||
|
use page::{LocalPageEndpoint, PageHandlerWaiting};
|
||||||
|
|
||||||
const FETCH_TIMEOUT: u64 = 30;
|
const FETCH_TIMEOUT: u64 = 30;
|
||||||
|
|
||||||
enum FetchState {
|
enum FetchState {
|
||||||
|
Waiting,
|
||||||
NotStarted(String),
|
NotStarted(String),
|
||||||
Error(ContentHandler),
|
Error(ContentHandler),
|
||||||
InProgress(mpsc::Receiver<FetchResult>),
|
InProgress(mpsc::Receiver<FetchResult>),
|
||||||
Done(String, LocalPageEndpoint, Redirection),
|
Done(LocalPageEndpoint, Box<PageHandlerWaiting>),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WaitResult {
|
||||||
|
Error(ContentHandler),
|
||||||
|
Done(LocalPageEndpoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ContentValidator {
|
pub trait ContentValidator {
|
||||||
type Error: fmt::Debug + fmt::Display;
|
type Error: fmt::Debug + fmt::Display;
|
||||||
|
|
||||||
fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>;
|
fn validate_and_install(&self, path: PathBuf) -> Result<LocalPageEndpoint, Self::Error>;
|
||||||
fn done(&self, Option<LocalPageEndpoint>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FetchControl {
|
pub struct FetchControl {
|
||||||
abort: Arc<AtomicBool>,
|
abort: Arc<AtomicBool>,
|
||||||
listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>,
|
listeners: Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>,
|
||||||
deadline: Instant,
|
deadline: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +71,10 @@ impl Default for FetchControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FetchControl {
|
impl FetchControl {
|
||||||
fn notify<F: Fn() -> FetchState>(&self, status: F) {
|
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
|
||||||
let mut listeners = self.listeners.lock();
|
let mut listeners = self.listeners.lock();
|
||||||
for (control, sender) in listeners.drain(..) {
|
for (control, sender) in listeners.drain(..) {
|
||||||
|
trace!(target: "dapps", "Resuming request waiting for content...");
|
||||||
if let Err(e) = sender.send(status()) {
|
if let Err(e) = sender.send(status()) {
|
||||||
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
|
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
|
||||||
} else {
|
} else {
|
||||||
@ -78,9 +85,9 @@ impl FetchControl {
|
|||||||
|
|
||||||
fn set_status(&self, status: &FetchState) {
|
fn set_status(&self, status: &FetchState) {
|
||||||
match *status {
|
match *status {
|
||||||
FetchState::Error(ref handler) => self.notify(|| FetchState::Error(handler.clone())),
|
FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())),
|
||||||
FetchState::Done(ref id, ref endpoint, ref handler) => self.notify(|| FetchState::Done(id.clone(), endpoint.clone(), handler.clone())),
|
FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())),
|
||||||
FetchState::NotStarted(_) | FetchState::InProgress(_) => {},
|
FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,44 +95,66 @@ impl FetchControl {
|
|||||||
self.abort.store(true, Ordering::SeqCst);
|
self.abort.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_handler(&self, control: Control) -> Box<server::Handler<HttpStream> + Send> {
|
pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
self.listeners.lock().push((control, tx));
|
self.listeners.lock().push((control, tx));
|
||||||
|
|
||||||
Box::new(WaitingHandler {
|
Box::new(WaitingHandler {
|
||||||
receiver: rx,
|
receiver: rx,
|
||||||
state: None,
|
state: FetchState::Waiting,
|
||||||
|
uri: RequestUri::default(),
|
||||||
|
path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WaitingHandler {
|
pub struct WaitingHandler {
|
||||||
receiver: mpsc::Receiver<FetchState>,
|
receiver: mpsc::Receiver<WaitResult>,
|
||||||
state: Option<FetchState>,
|
state: FetchState,
|
||||||
|
uri: RequestUri,
|
||||||
|
path: EndpointPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl server::Handler<HttpStream> for WaitingHandler {
|
impl server::Handler<HttpStream> for WaitingHandler {
|
||||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||||
|
self.uri = request.uri().clone();
|
||||||
Next::wait()
|
Next::wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||||
self.state = self.receiver.try_recv().ok();
|
let result = self.receiver.try_recv().ok();
|
||||||
Next::write()
|
self.state = match result {
|
||||||
|
Some(WaitResult::Error(handler)) => FetchState::Error(handler),
|
||||||
|
Some(WaitResult::Done(endpoint)) => {
|
||||||
|
let mut page_handler = endpoint.to_page_handler(self.path.clone());
|
||||||
|
page_handler.set_uri(&self.uri);
|
||||||
|
FetchState::Done(endpoint, page_handler)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
warn!("A result for waiting request was not received.");
|
||||||
|
FetchState::Waiting
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder),
|
||||||
|
FetchState::Error(ref mut handler) => handler.on_request_readable(decoder),
|
||||||
|
_ => Next::write(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||||
match self.state {
|
match self.state {
|
||||||
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response(res),
|
FetchState::Done(_, ref mut handler) => handler.on_response(res),
|
||||||
Some(FetchState::Error(ref mut handler)) => handler.on_response(res),
|
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||||
_ => Next::end(),
|
_ => Next::end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
match self.state {
|
match self.state {
|
||||||
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response_writable(encoder),
|
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
|
||||||
Some(FetchState::Error(ref mut handler)) => handler.on_response_writable(encoder),
|
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||||
_ => Next::end(),
|
_ => Next::end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,29 +166,19 @@ pub struct ContentFetcherHandler<H: ContentValidator> {
|
|||||||
status: FetchState,
|
status: FetchState,
|
||||||
client: Option<Client>,
|
client: Option<Client>,
|
||||||
installer: H,
|
installer: H,
|
||||||
request_url: Option<Url>,
|
path: EndpointPath,
|
||||||
|
uri: RequestUri,
|
||||||
embeddable_on: Option<(String, u16)>,
|
embeddable_on: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: ContentValidator> Drop for ContentFetcherHandler<H> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let result = match self.status {
|
|
||||||
FetchState::Done(_, ref result, _) => Some(result.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
self.installer.done(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H: ContentValidator> ContentFetcherHandler<H> {
|
impl<H: ContentValidator> ContentFetcherHandler<H> {
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
url: String,
|
url: String,
|
||||||
|
path: EndpointPath,
|
||||||
control: Control,
|
control: Control,
|
||||||
handler: H,
|
handler: H,
|
||||||
embeddable_on: Option<(String, u16)>,
|
embeddable_on: Option<(String, u16)>,
|
||||||
) -> (Self, Arc<FetchControl>) {
|
) -> (Self, Arc<FetchControl>) {
|
||||||
|
|
||||||
let fetch_control = Arc::new(FetchControl::default());
|
let fetch_control = Arc::new(FetchControl::default());
|
||||||
let client = Client::default();
|
let client = Client::default();
|
||||||
let handler = ContentFetcherHandler {
|
let handler = ContentFetcherHandler {
|
||||||
@ -168,7 +187,8 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
|
|||||||
client: Some(client),
|
client: Some(client),
|
||||||
status: FetchState::NotStarted(url),
|
status: FetchState::NotStarted(url),
|
||||||
installer: handler,
|
installer: handler,
|
||||||
request_url: None,
|
path: path,
|
||||||
|
uri: RequestUri::default(),
|
||||||
embeddable_on: embeddable_on,
|
embeddable_on: embeddable_on,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -192,7 +212,6 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
|
|||||||
|
|
||||||
impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<H> {
|
impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<H> {
|
||||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||||
self.request_url = extract_url(&request);
|
|
||||||
let status = if let FetchState::NotStarted(ref url) = self.status {
|
let status = if let FetchState::NotStarted(ref url) = self.status {
|
||||||
Some(match *request.method() {
|
Some(match *request.method() {
|
||||||
// Start fetching content
|
// Start fetching content
|
||||||
@ -205,8 +224,8 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
|||||||
Ok(receiver) => FetchState::InProgress(receiver),
|
Ok(receiver) => FetchState::InProgress(receiver),
|
||||||
Err(e) => FetchState::Error(ContentHandler::error(
|
Err(e) => FetchState::Error(ContentHandler::error(
|
||||||
StatusCode::BadGateway,
|
StatusCode::BadGateway,
|
||||||
"Unable To Start Dapp Download",
|
"Unable To Start Content Download",
|
||||||
"Could not initialize download of the dapp. It might be a problem with the remote server.",
|
"Could not initialize download of the content. It might be a problem with the remote server.",
|
||||||
Some(&format!("{}", e)),
|
Some(&format!("{}", e)),
|
||||||
self.embeddable_on.clone(),
|
self.embeddable_on.clone(),
|
||||||
)),
|
)),
|
||||||
@ -227,6 +246,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
|||||||
self.fetch_control.set_status(&status);
|
self.fetch_control.set_status(&status);
|
||||||
self.status = status;
|
self.status = status;
|
||||||
}
|
}
|
||||||
|
self.uri = request.uri().clone();
|
||||||
|
|
||||||
Next::read()
|
Next::read()
|
||||||
}
|
}
|
||||||
@ -266,11 +286,10 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
|||||||
self.embeddable_on.clone(),
|
self.embeddable_on.clone(),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
Ok((id, result)) => {
|
Ok(endpoint) => {
|
||||||
let url: String = self.request_url.take()
|
let mut handler = endpoint.to_page_handler(self.path.clone());
|
||||||
.map(|url| url.raw.into_string())
|
handler.set_uri(&self.uri);
|
||||||
.expect("Request URL always read in on_request; qed");
|
FetchState::Done(endpoint, handler)
|
||||||
FetchState::Done(id, result, Redirection::new(&url))
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Remove temporary zip file
|
// Remove temporary zip file
|
||||||
@ -306,7 +325,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
|||||||
|
|
||||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||||
match self.status {
|
match self.status {
|
||||||
FetchState::Done(_, _, ref mut handler) => handler.on_response(res),
|
FetchState::Done(_, ref mut handler) => handler.on_response(res),
|
||||||
FetchState::Error(ref mut handler) => handler.on_response(res),
|
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||||
_ => Next::end(),
|
_ => Next::end(),
|
||||||
}
|
}
|
||||||
@ -314,7 +333,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
|||||||
|
|
||||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||||
match self.status {
|
match self.status {
|
||||||
FetchState::Done(_, _, ref mut handler) => handler.on_response_writable(encoder),
|
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
|
||||||
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||||
_ => Next::end(),
|
_ => Next::end(),
|
||||||
}
|
}
|
||||||
|
@ -83,13 +83,19 @@ impl Default for PageCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A generic type for `PageHandler` allowing to set the URL.
|
||||||
|
/// Used by dapps fetching to set the URL after the content was downloaded.
|
||||||
|
pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send {
|
||||||
|
fn set_uri(&mut self, uri: &RequestUri);
|
||||||
|
}
|
||||||
|
|
||||||
/// A handler for a single webapp.
|
/// A handler for a single webapp.
|
||||||
/// Resolves correct paths and serves as a plumbing code between
|
/// Resolves correct paths and serves as a plumbing code between
|
||||||
/// hyper server and dapp.
|
/// hyper server and dapp.
|
||||||
pub struct PageHandler<T: Dapp> {
|
pub struct PageHandler<T: Dapp> {
|
||||||
/// A Dapp.
|
/// A Dapp.
|
||||||
pub app: T,
|
pub app: T,
|
||||||
/// File currently being served (or `None` if file does not exist).
|
/// File currently being served
|
||||||
pub file: ServedFile<T>,
|
pub file: ServedFile<T>,
|
||||||
/// Optional prefix to strip from path.
|
/// Optional prefix to strip from path.
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
@ -101,6 +107,21 @@ pub struct PageHandler<T: Dapp> {
|
|||||||
pub cache: PageCache,
|
pub cache: PageCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Dapp> PageHandlerWaiting for PageHandler<T> {
|
||||||
|
fn set_uri(&mut self, uri: &RequestUri) {
|
||||||
|
trace!(target: "dapps", "Setting URI: {:?}", uri);
|
||||||
|
self.file = match *uri {
|
||||||
|
RequestUri::AbsolutePath { ref path, .. } => {
|
||||||
|
self.app.file(&self.extract_path(path))
|
||||||
|
},
|
||||||
|
RequestUri::AbsoluteUri(ref url) => {
|
||||||
|
self.app.file(&self.extract_path(url.path()))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Dapp> PageHandler<T> {
|
impl<T: Dapp> PageHandler<T> {
|
||||||
fn extract_path(&self, path: &str) -> String {
|
fn extract_path(&self, path: &str) -> String {
|
||||||
let app_id = &self.path.app_id;
|
let app_id = &self.path.app_id;
|
||||||
@ -124,15 +145,7 @@ impl<T: Dapp> PageHandler<T> {
|
|||||||
|
|
||||||
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||||
self.file = match *req.uri() {
|
self.set_uri(req.uri());
|
||||||
RequestUri::AbsolutePath { ref path, .. } => {
|
|
||||||
self.app.file(&self.extract_path(path))
|
|
||||||
},
|
|
||||||
RequestUri::AbsoluteUri(ref url) => {
|
|
||||||
self.app.file(&self.extract_path(url.path()))
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
|
|
||||||
Next::write()
|
Next::write()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use mime_guess;
|
|||||||
use std::io::{Seek, Read, SeekFrom};
|
use std::io::{Seek, Read, SeekFrom};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use page::handler::{self, PageCache};
|
use page::handler::{self, PageCache, PageHandlerWaiting};
|
||||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -54,6 +54,36 @@ impl LocalPageEndpoint {
|
|||||||
pub fn path(&self) -> PathBuf {
|
pub fn path(&self) -> PathBuf {
|
||||||
self.path.clone()
|
self.path.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn page_handler_with_mime(&self, path: EndpointPath, mime: &str) -> handler::PageHandler<LocalSingleFile> {
|
||||||
|
handler::PageHandler {
|
||||||
|
app: LocalSingleFile { path: self.path.clone(), mime: mime.into() },
|
||||||
|
prefix: None,
|
||||||
|
path: path,
|
||||||
|
file: handler::ServedFile::new(None),
|
||||||
|
safe_to_embed_on: self.embeddable_on.clone(),
|
||||||
|
cache: self.cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> {
|
||||||
|
handler::PageHandler {
|
||||||
|
app: LocalDapp { path: self.path.clone() },
|
||||||
|
prefix: None,
|
||||||
|
path: path,
|
||||||
|
file: handler::ServedFile::new(None),
|
||||||
|
safe_to_embed_on: self.embeddable_on.clone(),
|
||||||
|
cache: self.cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> {
|
||||||
|
if let Some(ref mime) = self.mime {
|
||||||
|
Box::new(self.page_handler_with_mime(path, mime))
|
||||||
|
} else {
|
||||||
|
Box::new(self.page_handler(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint for LocalPageEndpoint {
|
impl Endpoint for LocalPageEndpoint {
|
||||||
@ -63,23 +93,9 @@ impl Endpoint for LocalPageEndpoint {
|
|||||||
|
|
||||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||||
if let Some(ref mime) = self.mime {
|
if let Some(ref mime) = self.mime {
|
||||||
Box::new(handler::PageHandler {
|
Box::new(self.page_handler_with_mime(path, mime))
|
||||||
app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() },
|
|
||||||
prefix: None,
|
|
||||||
path: path,
|
|
||||||
file: handler::ServedFile::new(None),
|
|
||||||
safe_to_embed_on: self.embeddable_on.clone(),
|
|
||||||
cache: self.cache,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Box::new(handler::PageHandler {
|
Box::new(self.page_handler(path))
|
||||||
app: LocalDapp { path: self.path.clone() },
|
|
||||||
prefix: None,
|
|
||||||
path: path,
|
|
||||||
file: handler::ServedFile::new(None),
|
|
||||||
safe_to_embed_on: self.embeddable_on.clone(),
|
|
||||||
cache: self.cache,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,5 @@ mod handler;
|
|||||||
|
|
||||||
pub use self::local::LocalPageEndpoint;
|
pub use self::local::LocalPageEndpoint;
|
||||||
pub use self::builtin::PageEndpoint;
|
pub use self::builtin::PageEndpoint;
|
||||||
pub use self::handler::PageCache;
|
pub use self::handler::{PageCache, PageHandlerWaiting};
|
||||||
|
|
||||||
|
@ -21,8 +21,9 @@
|
|||||||
},
|
},
|
||||||
"genesis": {
|
"genesis": {
|
||||||
"seal": {
|
"seal": {
|
||||||
"generic": {
|
"authority_round": {
|
||||||
"rlp": "0xc28080"
|
"step": "0x0",
|
||||||
|
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"difficulty": "0x20000",
|
"difficulty": "0x20000",
|
||||||
|
@ -17,10 +17,7 @@
|
|||||||
},
|
},
|
||||||
"genesis": {
|
"genesis": {
|
||||||
"seal": {
|
"seal": {
|
||||||
"generic": {
|
"generic": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
|
||||||
"fields": 1,
|
|
||||||
"rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"difficulty": "0x20000",
|
"difficulty": "0x20000",
|
||||||
"author": "0x0000000000000000000000000000000000000000",
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Ethereum Classic",
|
"name": "Ethereum Classic",
|
||||||
"forkName": "classic",
|
"dataDir": "classic",
|
||||||
"engine": {
|
"engine": {
|
||||||
"Ethash": {
|
"Ethash": {
|
||||||
"params": {
|
"params": {
|
||||||
@ -10,14 +10,15 @@
|
|||||||
"durationLimit": "0x0d",
|
"durationLimit": "0x0d",
|
||||||
"blockReward": "0x4563918244F40000",
|
"blockReward": "0x4563918244F40000",
|
||||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||||
"homesteadTransition": "0x118c30",
|
"homesteadTransition": 1150000,
|
||||||
"eip150Transition": "0x2625a0",
|
"eip150Transition": 2500000,
|
||||||
"eip155Transition": "0x7fffffffffffffff",
|
"eip155Transition": 3000000,
|
||||||
"eip160Transition": "0x7fffffffffffffff",
|
"eip160Transition": 3000000,
|
||||||
|
"ecip1010PauseTransition": 3000000,
|
||||||
|
"ecip1010ContinueTransition": 5000000,
|
||||||
|
|
||||||
"eip161abcTransition": "0x7fffffffffffffff",
|
"eip161abcTransition": "0x7fffffffffffffff",
|
||||||
"eip161dTransition": "0x7fffffffffffffff",
|
"eip161dTransition": "0x7fffffffffffffff"
|
||||||
"ecip1010PauseTransition": "0x2dc6c0",
|
|
||||||
"ecip1010ContinueTransition": "0x4c4b40"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Expanse",
|
"name": "Expanse",
|
||||||
"forkName": "expanse",
|
"dataDir": "expanse",
|
||||||
"engine": {
|
"engine": {
|
||||||
"Ethash": {
|
"Ethash": {
|
||||||
"params": {
|
"params": {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Frontier/Homestead",
|
"name": "Frontier/Homestead",
|
||||||
|
"dataDir": "ethereum",
|
||||||
"engine": {
|
"engine": {
|
||||||
"Ethash": {
|
"Ethash": {
|
||||||
"params": {
|
"params": {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Morden",
|
"name": "Morden",
|
||||||
|
"dataDir": "test",
|
||||||
"engine": {
|
"engine": {
|
||||||
"Ethash": {
|
"Ethash": {
|
||||||
"params": {
|
"params": {
|
||||||
@ -9,12 +10,15 @@
|
|||||||
"durationLimit": "0x0d",
|
"durationLimit": "0x0d",
|
||||||
"blockReward": "0x4563918244F40000",
|
"blockReward": "0x4563918244F40000",
|
||||||
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
|
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
|
||||||
"homesteadTransition": "0x789b0",
|
"homesteadTransition": 494000,
|
||||||
"eip150Transition": "0x1b34d8",
|
"eip150Transition": 1783000,
|
||||||
"eip155Transition": 1885000,
|
"eip155Transition": 1915000,
|
||||||
"eip160Transition": 1885000,
|
"eip160Transition": 1915000,
|
||||||
"eip161abcTransition": 1885000,
|
"ecip1010PauseTransition": 1915000,
|
||||||
"eip161dTransition": 1885000
|
"ecip1010ContinueTransition": 3415000,
|
||||||
|
|
||||||
|
"eip161abcTransition": "0x7fffffffffffffff",
|
||||||
|
"eip161dTransition": "0x7fffffffffffffff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Ropsten",
|
"name": "Ropsten",
|
||||||
|
"dataDir": "test",
|
||||||
"engine": {
|
"engine": {
|
||||||
"Ethash": {
|
"Ethash": {
|
||||||
"params": {
|
"params": {
|
||||||
|
@ -4,29 +4,27 @@
|
|||||||
"InstantSeal": null
|
"InstantSeal": null
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"accountStartNonce": "0x0100000",
|
"accountStartNonce": "0x0",
|
||||||
"maximumExtraDataSize": "0x20",
|
"maximumExtraDataSize": "0x20",
|
||||||
"minGasLimit": "0x1388",
|
"minGasLimit": "0x1388",
|
||||||
"networkID" : "0x2"
|
"networkID" : "0x11"
|
||||||
},
|
},
|
||||||
"genesis": {
|
"genesis": {
|
||||||
"seal": {
|
"seal": {
|
||||||
"generic": {
|
"generic": "0x0"
|
||||||
"rlp": "0x0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"difficulty": "0x20000",
|
"difficulty": "0x20000",
|
||||||
"author": "0x0000000000000000000000000000000000000000",
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
"timestamp": "0x00",
|
"timestamp": "0x00",
|
||||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"extraData": "0x",
|
"extraData": "0x",
|
||||||
"gasLimit": "0x2fefd8"
|
"gasLimit": "0x5B8D80"
|
||||||
},
|
},
|
||||||
"accounts": {
|
"accounts": {
|
||||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||||
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||||
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||||
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||||
"0x00a329c0648769a73afac7f9381e08fb43dbea72": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
|
"0x00a329c0648769a73afac7f9381e08fb43dbea72": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
ethcore/res/tendermint.json
Normal file
44
ethcore/res/tendermint.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "TestBFT",
|
||||||
|
"engine": {
|
||||||
|
"Tendermint": {
|
||||||
|
"params": {
|
||||||
|
"gasLimitBoundDivisor": "0x0400",
|
||||||
|
"authorities" : [
|
||||||
|
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
|
||||||
|
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"accountStartNonce": "0x0",
|
||||||
|
"maximumExtraDataSize": "0x20",
|
||||||
|
"minGasLimit": "0x1388",
|
||||||
|
"networkID" : "0x2323"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"seal": {
|
||||||
|
"tendermint": {
|
||||||
|
"round": "0x0",
|
||||||
|
"proposal": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"precommits": [
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"difficulty": "0x20000",
|
||||||
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
|
"timestamp": "0x00",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"extraData": "0x",
|
||||||
|
"gasLimit": "0x2fefd8"
|
||||||
|
},
|
||||||
|
"accounts": {
|
||||||
|
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||||
|
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||||
|
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||||
|
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||||
|
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
|
||||||
|
}
|
||||||
|
}
|
@ -18,14 +18,14 @@
|
|||||||
|
|
||||||
mod stores;
|
mod stores;
|
||||||
|
|
||||||
use self::stores::{AddressBook, DappsSettingsStore};
|
use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
use util::{Mutex, RwLock};
|
use util::RwLock;
|
||||||
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
|
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string};
|
||||||
use ethstore::dir::{KeyDirectory};
|
use ethstore::dir::MemoryDirectory;
|
||||||
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
||||||
use ethjson::misc::AccountMeta;
|
use ethjson::misc::AccountMeta;
|
||||||
pub use ethstore::ethkey::Signature;
|
pub use ethstore::ethkey::Signature;
|
||||||
@ -73,58 +73,47 @@ impl From<SSError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct NullDir {
|
|
||||||
accounts: RwLock<HashMap<Address, SafeAccount>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyDirectory for NullDir {
|
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, SSError> {
|
|
||||||
Ok(self.accounts.read().values().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, SSError> {
|
|
||||||
self.accounts.write().insert(account.address.clone(), account.clone());
|
|
||||||
Ok(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), SSError> {
|
|
||||||
self.accounts.write().remove(address);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dapp identifier
|
/// Dapp identifier
|
||||||
pub type DappId = String;
|
pub type DappId = String;
|
||||||
|
|
||||||
|
fn transient_sstore() -> EthMultiStore {
|
||||||
|
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountToken = String;
|
||||||
|
|
||||||
/// Account management.
|
/// Account management.
|
||||||
/// Responsible for unlocking accounts.
|
/// Responsible for unlocking accounts.
|
||||||
pub struct AccountProvider {
|
pub struct AccountProvider {
|
||||||
unlocked: Mutex<HashMap<Address, AccountData>>,
|
unlocked: RwLock<HashMap<Address, AccountData>>,
|
||||||
sstore: Box<SecretStore>,
|
|
||||||
address_book: RwLock<AddressBook>,
|
address_book: RwLock<AddressBook>,
|
||||||
dapps_settings: RwLock<DappsSettingsStore>,
|
dapps_settings: RwLock<DappsSettingsStore>,
|
||||||
|
/// Accounts on disk
|
||||||
|
sstore: Box<SecretStore>,
|
||||||
|
/// Accounts unlocked with rolling tokens
|
||||||
|
transient_sstore: EthMultiStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountProvider {
|
impl AccountProvider {
|
||||||
/// Creates new account provider.
|
/// Creates new account provider.
|
||||||
pub fn new(sstore: Box<SecretStore>) -> Self {
|
pub fn new(sstore: Box<SecretStore>) -> Self {
|
||||||
AccountProvider {
|
AccountProvider {
|
||||||
unlocked: Mutex::new(HashMap::new()),
|
unlocked: RwLock::new(HashMap::new()),
|
||||||
address_book: RwLock::new(AddressBook::new(sstore.local_path().into())),
|
address_book: RwLock::new(AddressBook::new(sstore.local_path().into())),
|
||||||
dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())),
|
dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())),
|
||||||
sstore: sstore,
|
sstore: sstore,
|
||||||
|
transient_sstore: transient_sstore(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates not disk backed provider.
|
/// Creates not disk backed provider.
|
||||||
pub fn transient_provider() -> Self {
|
pub fn transient_provider() -> Self {
|
||||||
AccountProvider {
|
AccountProvider {
|
||||||
unlocked: Mutex::new(HashMap::new()),
|
unlocked: RwLock::new(HashMap::new()),
|
||||||
address_book: RwLock::new(AddressBook::transient()),
|
address_book: RwLock::new(AddressBook::transient()),
|
||||||
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
|
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
|
||||||
sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
|
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
||||||
.expect("NullDir load always succeeds; qed"))
|
transient_sstore: transient_sstore(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,10 +156,49 @@ impl AccountProvider {
|
|||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a whitelist of accounts exposed for unknown dapps.
|
||||||
|
/// `None` means that all accounts will be visible.
|
||||||
|
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
||||||
|
self.dapps_settings.write().set_policy(match accounts {
|
||||||
|
None => NewDappsPolicy::AllAccounts,
|
||||||
|
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a whitelist of accounts exposed for unknown dapps.
|
||||||
|
/// `None` means that all accounts will be visible.
|
||||||
|
pub fn new_dapps_whitelist(&self) -> Result<Option<Vec<Address>>, Error> {
|
||||||
|
Ok(match self.dapps_settings.read().policy() {
|
||||||
|
NewDappsPolicy::AllAccounts => None,
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a list of dapps recently requesting accounts.
|
||||||
|
pub fn recent_dapps(&self) -> Result<Vec<DappId>, Error> {
|
||||||
|
Ok(self.dapps_settings.read().recent_dapps())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks dapp as recently used.
|
||||||
|
pub fn note_dapp_used(&self, dapp: DappId) -> Result<(), Error> {
|
||||||
|
let mut dapps = self.dapps_settings.write();
|
||||||
|
dapps.mark_dapp_used(dapp.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets addresses visile for dapp.
|
/// Gets addresses visile for dapp.
|
||||||
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
||||||
let accounts = self.dapps_settings.read().get();
|
let dapps = self.dapps_settings.read();
|
||||||
Ok(accounts.get(&dapp).map(|settings| settings.accounts.clone()).unwrap_or_else(Vec::new))
|
|
||||||
|
let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone());
|
||||||
|
match accounts {
|
||||||
|
Some(accounts) => Ok(accounts),
|
||||||
|
None => match dapps.policy() {
|
||||||
|
NewDappsPolicy::AllAccounts => self.accounts(),
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => Ok(accounts),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets addresses visile for dapp.
|
/// Sets addresses visile for dapp.
|
||||||
@ -231,11 +259,8 @@ impl AccountProvider {
|
|||||||
|
|
||||||
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
||||||
pub fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error> {
|
pub fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error> {
|
||||||
match self.sstore.sign(account, password, &Default::default()) {
|
self.sstore.test_password(account, password)
|
||||||
Ok(_) => Ok(true),
|
.map_err(Into::into)
|
||||||
Err(SSError::InvalidPassword) => Ok(false),
|
|
||||||
Err(e) => Err(Error::SStore(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Permanently removes an account.
|
/// Permanently removes an account.
|
||||||
@ -256,7 +281,7 @@ impl AccountProvider {
|
|||||||
let _ = try!(self.sstore.sign(&account, &password, &Default::default()));
|
let _ = try!(self.sstore.sign(&account, &password, &Default::default()));
|
||||||
|
|
||||||
// check if account is already unlocked pernamently, if it is, do nothing
|
// check if account is already unlocked pernamently, if it is, do nothing
|
||||||
let mut unlocked = self.unlocked.lock();
|
let mut unlocked = self.unlocked.write();
|
||||||
if let Some(data) = unlocked.get(&account) {
|
if let Some(data) = unlocked.get(&account) {
|
||||||
if let Unlock::Perm = data.unlock {
|
if let Unlock::Perm = data.unlock {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@ -273,7 +298,7 @@ impl AccountProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn password(&self, account: &Address) -> Result<String, Error> {
|
fn password(&self, account: &Address) -> Result<String, Error> {
|
||||||
let mut unlocked = self.unlocked.lock();
|
let mut unlocked = self.unlocked.write();
|
||||||
let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone();
|
let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone();
|
||||||
if let Unlock::Temp = data.unlock {
|
if let Unlock::Temp = data.unlock {
|
||||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||||
@ -304,7 +329,7 @@ impl AccountProvider {
|
|||||||
|
|
||||||
/// Checks if given account is unlocked
|
/// Checks if given account is unlocked
|
||||||
pub fn is_unlocked(&self, account: Address) -> bool {
|
pub fn is_unlocked(&self, account: Address) -> bool {
|
||||||
let unlocked = self.unlocked.lock();
|
let unlocked = self.unlocked.read();
|
||||||
unlocked.get(&account).is_some()
|
unlocked.get(&account).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +339,48 @@ impl AccountProvider {
|
|||||||
Ok(try!(self.sstore.sign(&account, &password, &message)))
|
Ok(try!(self.sstore.sign(&account, &password, &message)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
||||||
|
pub fn sign_with_token(&self, account: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), Error> {
|
||||||
|
let is_std_password = try!(self.sstore.test_password(&account, &token));
|
||||||
|
|
||||||
|
let new_token = random_string(16);
|
||||||
|
let signature = if is_std_password {
|
||||||
|
// Insert to transient store
|
||||||
|
try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token));
|
||||||
|
// sign
|
||||||
|
try!(self.sstore.sign(&account, &token, &message))
|
||||||
|
} else {
|
||||||
|
// check transient store
|
||||||
|
try!(self.transient_sstore.change_password(&account, &token, &new_token));
|
||||||
|
// and sign
|
||||||
|
try!(self.transient_sstore.sign(&account, &new_token, &message))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((signature, new_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
||||||
|
pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
|
||||||
|
-> Result<(Vec<u8>, AccountToken), Error>
|
||||||
|
{
|
||||||
|
let is_std_password = try!(self.sstore.test_password(&account, &token));
|
||||||
|
|
||||||
|
let new_token = random_string(16);
|
||||||
|
let message = if is_std_password {
|
||||||
|
// Insert to transient store
|
||||||
|
try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token));
|
||||||
|
// decrypt
|
||||||
|
try!(self.sstore.decrypt(&account, &token, shared_mac, message))
|
||||||
|
} else {
|
||||||
|
// check transient store
|
||||||
|
try!(self.transient_sstore.change_password(&account, &token, &new_token));
|
||||||
|
// and decrypt
|
||||||
|
try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((message, new_token))
|
||||||
|
}
|
||||||
|
|
||||||
/// Decrypts a message. If password is not provided the account must be unlocked.
|
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||||
pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
||||||
@ -370,15 +437,33 @@ mod tests {
|
|||||||
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 60000).is_err());
|
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 60000).is_err());
|
||||||
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 60000).is_ok());
|
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 60000).is_ok());
|
||||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||||
ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now());
|
ap.unlocked.write().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now());
|
||||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sign_and_return_token() {
|
||||||
|
// given
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
ap.sign_with_token(kp.address(), token.clone(), Default::default())
|
||||||
|
.expect("First usage of token should be correct.");
|
||||||
|
assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail.");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_set_dapps_addresses() {
|
fn should_set_dapps_addresses() {
|
||||||
// given
|
// given
|
||||||
let ap = AccountProvider::transient_provider();
|
let ap = AccountProvider::transient_provider();
|
||||||
let app = "app1".to_owned();
|
let app = "app1".to_owned();
|
||||||
|
// set `AllAccounts` policy
|
||||||
|
ap.set_new_dapps_whitelist(None).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap();
|
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap();
|
||||||
@ -386,4 +471,23 @@ mod tests {
|
|||||||
// then
|
// then
|
||||||
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_dapps_policy() {
|
||||||
|
// given
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
let address = ap.new_account("test").unwrap();
|
||||||
|
|
||||||
|
// When returning nothing
|
||||||
|
ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
||||||
|
|
||||||
|
// change to all
|
||||||
|
ap.set_new_dapps_whitelist(None).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
|
||||||
|
|
||||||
|
// change to a whitelist
|
||||||
|
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
|
||||||
|
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
//! Address Book and Dapps Settings Store
|
//! Address Book and Dapps Settings Store
|
||||||
|
|
||||||
use std::{fs, fmt, hash, ops};
|
use std::{fs, fmt, hash, ops};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use ethstore::ethkey::Address;
|
use ethstore::ethkey::Address;
|
||||||
use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings};
|
use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings, NewDappsPolicy as JsonNewDappsPolicy};
|
||||||
use account_provider::DappId;
|
use account_provider::DappId;
|
||||||
|
|
||||||
/// Disk-backed map from Address to String. Uses JSON.
|
/// Disk-backed map from Address to String. Uses JSON.
|
||||||
@ -105,43 +105,106 @@ impl From<DappsSettings> for JsonSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dapps user settings
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum NewDappsPolicy {
|
||||||
|
AllAccounts,
|
||||||
|
Whitelist(Vec<Address>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
||||||
|
fn from(s: JsonNewDappsPolicy) -> Self {
|
||||||
|
match s {
|
||||||
|
JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts,
|
||||||
|
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
|
||||||
|
accounts.into_iter().map(Into::into).collect()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
|
||||||
|
fn from(s: NewDappsPolicy) -> Self {
|
||||||
|
match s {
|
||||||
|
NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts,
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
|
||||||
|
accounts.into_iter().map(Into::into).collect()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_RECENT_DAPPS: usize = 10;
|
||||||
|
|
||||||
/// Disk-backed map from DappId to Settings. Uses JSON.
|
/// Disk-backed map from DappId to Settings. Uses JSON.
|
||||||
pub struct DappsSettingsStore {
|
pub struct DappsSettingsStore {
|
||||||
cache: DiskMap<DappId, DappsSettings>,
|
/// Dapps Settings
|
||||||
|
settings: DiskMap<DappId, DappsSettings>,
|
||||||
|
/// New Dapps Policy
|
||||||
|
policy: DiskMap<String, NewDappsPolicy>,
|
||||||
|
/// Recently Accessed Dapps (transient)
|
||||||
|
recent: VecDeque<DappId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DappsSettingsStore {
|
impl DappsSettingsStore {
|
||||||
/// Creates new store at given directory path.
|
/// Creates new store at given directory path.
|
||||||
pub fn new(path: String) -> Self {
|
pub fn new(path: String) -> Self {
|
||||||
let mut r = DappsSettingsStore {
|
let mut r = DappsSettingsStore {
|
||||||
cache: DiskMap::new(path, "dapps_accounts.json".into())
|
settings: DiskMap::new(path.clone(), "dapps_accounts.json".into()),
|
||||||
|
policy: DiskMap::new(path.clone(), "dapps_policy.json".into()),
|
||||||
|
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
|
||||||
};
|
};
|
||||||
r.cache.revert(JsonSettings::read_dapps_settings);
|
r.settings.revert(JsonSettings::read_dapps_settings);
|
||||||
|
r.policy.revert(JsonNewDappsPolicy::read_new_dapps_policy);
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates transient store (no changes are saved to disk).
|
/// Creates transient store (no changes are saved to disk).
|
||||||
pub fn transient() -> Self {
|
pub fn transient() -> Self {
|
||||||
DappsSettingsStore {
|
DappsSettingsStore {
|
||||||
cache: DiskMap::transient()
|
settings: DiskMap::transient(),
|
||||||
|
policy: DiskMap::transient(),
|
||||||
|
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get copy of the dapps settings
|
/// Get copy of the dapps settings
|
||||||
pub fn get(&self) -> HashMap<DappId, DappsSettings> {
|
pub fn settings(&self) -> HashMap<DappId, DappsSettings> {
|
||||||
self.cache.clone()
|
self.settings.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) {
|
/// Returns current new dapps policy
|
||||||
self.cache.save(JsonSettings::write_dapps_settings)
|
pub fn policy(&self) -> NewDappsPolicy {
|
||||||
|
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns recent dapps (in order of last request)
|
||||||
|
pub fn recent_dapps(&self) -> Vec<DappId> {
|
||||||
|
self.recent.iter().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks recent dapp as used
|
||||||
|
pub fn mark_dapp_used(&mut self, dapp: DappId) {
|
||||||
|
self.recent.retain(|id| id != &dapp);
|
||||||
|
self.recent.push_front(dapp);
|
||||||
|
while self.recent.len() > MAX_RECENT_DAPPS {
|
||||||
|
self.recent.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets current new dapps policy
|
||||||
|
pub fn set_policy(&mut self, policy: NewDappsPolicy) {
|
||||||
|
self.policy.insert("default".into(), policy);
|
||||||
|
self.policy.save(JsonNewDappsPolicy::write_new_dapps_policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets accounts for specific dapp.
|
||||||
pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) {
|
pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) {
|
||||||
{
|
{
|
||||||
let mut settings = self.cache.entry(id).or_insert_with(DappsSettings::default);
|
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
|
||||||
settings.accounts = accounts;
|
settings.accounts = accounts;
|
||||||
}
|
}
|
||||||
self.save();
|
self.settings.save(JsonSettings::write_dapps_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +279,7 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{AddressBook, DappsSettingsStore, DappsSettings};
|
use super::{AddressBook, DappsSettingsStore, DappsSettings, NewDappsPolicy};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use ethjson::misc::AccountMeta;
|
use ethjson::misc::AccountMeta;
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
@ -232,25 +295,6 @@ mod tests {
|
|||||||
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_save_and_reload_dapps_settings() {
|
|
||||||
// given
|
|
||||||
let temp = RandomTempPath::create_dir();
|
|
||||||
let path = temp.as_str().to_owned();
|
|
||||||
let mut b = DappsSettingsStore::new(path.clone());
|
|
||||||
|
|
||||||
// when
|
|
||||||
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
|
||||||
|
|
||||||
// then
|
|
||||||
let b = DappsSettingsStore::new(path);
|
|
||||||
assert_eq!(b.get(), hash_map![
|
|
||||||
"dappOne".into() => DappsSettings {
|
|
||||||
accounts: vec![1.into(), 2.into()],
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_remove_address() {
|
fn should_remove_address() {
|
||||||
let temp = RandomTempPath::create_dir();
|
let temp = RandomTempPath::create_dir();
|
||||||
@ -268,4 +312,58 @@ mod tests {
|
|||||||
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
|
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_save_and_reload_dapps_settings() {
|
||||||
|
// given
|
||||||
|
let temp = RandomTempPath::create_dir();
|
||||||
|
let path = temp.as_str().to_owned();
|
||||||
|
let mut b = DappsSettingsStore::new(path.clone());
|
||||||
|
|
||||||
|
// when
|
||||||
|
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
||||||
|
|
||||||
|
// then
|
||||||
|
let b = DappsSettingsStore::new(path);
|
||||||
|
assert_eq!(b.settings(), hash_map![
|
||||||
|
"dappOne".into() => DappsSettings {
|
||||||
|
accounts: vec![1.into(), 2.into()],
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_maintain_a_list_of_recent_dapps() {
|
||||||
|
let mut store = DappsSettingsStore::transient();
|
||||||
|
assert!(store.recent_dapps().is_empty(), "Initially recent dapps should be empty.");
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp1".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned()]);
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp2".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp2".to_owned(), "dapp1".to_owned()]);
|
||||||
|
|
||||||
|
store.mark_dapp_used("dapp1".into());
|
||||||
|
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned(), "dapp2".to_owned()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_store_dapps_policy() {
|
||||||
|
// given
|
||||||
|
let temp = RandomTempPath::create_dir();
|
||||||
|
let path = temp.as_str().to_owned();
|
||||||
|
let mut store = DappsSettingsStore::new(path.clone());
|
||||||
|
|
||||||
|
// Test default policy
|
||||||
|
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
|
||||||
|
|
||||||
|
// when
|
||||||
|
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let store = DappsSettingsStore::new(path);
|
||||||
|
assert_eq!(store.policy.clone(), hash_map![
|
||||||
|
"default".into() => NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use ipc::IpcConfig;
|
use ipc::IpcConfig;
|
||||||
use util::H256;
|
use util::{H256, Bytes};
|
||||||
|
|
||||||
/// Represents what has to be handled by actor listening to chain events
|
/// Represents what has to be handled by actor listening to chain events
|
||||||
#[ipc]
|
#[ipc]
|
||||||
@ -27,6 +27,8 @@ pub trait ChainNotify : Send + Sync {
|
|||||||
_enacted: Vec<H256>,
|
_enacted: Vec<H256>,
|
||||||
_retracted: Vec<H256>,
|
_retracted: Vec<H256>,
|
||||||
_sealed: Vec<H256>,
|
_sealed: Vec<H256>,
|
||||||
|
// Block bytes.
|
||||||
|
_proposed: Vec<Bytes>,
|
||||||
_duration: u64) {
|
_duration: u64) {
|
||||||
// does nothing by default
|
// does nothing by default
|
||||||
}
|
}
|
||||||
@ -41,6 +43,9 @@ pub trait ChainNotify : Send + Sync {
|
|||||||
// does nothing by default
|
// does nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// fires when chain broadcasts a message
|
||||||
|
fn broadcast(&self, _data: Vec<u8>) {}
|
||||||
|
|
||||||
/// fires when new transactions are received from a peer
|
/// fires when new transactions are received from a peer
|
||||||
fn transactions_received(&self,
|
fn transactions_received(&self,
|
||||||
_hashes: Vec<H256>,
|
_hashes: Vec<H256>,
|
||||||
|
@ -24,8 +24,8 @@ use time::precise_time_ns;
|
|||||||
// util
|
// util
|
||||||
use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, Hashable};
|
use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, Hashable};
|
||||||
use util::{journaldb, TrieFactory, Trie};
|
use util::{journaldb, TrieFactory, Trie};
|
||||||
use util::trie::TrieSpec;
|
|
||||||
use util::{U256, H256, Address, H2048, Uint, FixedHash};
|
use util::{U256, H256, Address, H2048, Uint, FixedHash};
|
||||||
|
use util::trie::TrieSpec;
|
||||||
use util::kvdb::*;
|
use util::kvdb::*;
|
||||||
|
|
||||||
// other
|
// other
|
||||||
@ -396,9 +396,10 @@ impl Client {
|
|||||||
/// This is triggered by a message coming from a block queue when the block is ready for insertion
|
/// This is triggered by a message coming from a block queue when the block is ready for insertion
|
||||||
pub fn import_verified_blocks(&self) -> usize {
|
pub fn import_verified_blocks(&self) -> usize {
|
||||||
let max_blocks_to_import = 4;
|
let max_blocks_to_import = 4;
|
||||||
let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = {
|
let (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration, is_empty) = {
|
||||||
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
|
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
|
||||||
let mut invalid_blocks = HashSet::new();
|
let mut invalid_blocks = HashSet::new();
|
||||||
|
let mut proposed_blocks = Vec::with_capacity(max_blocks_to_import);
|
||||||
let mut import_results = Vec::with_capacity(max_blocks_to_import);
|
let mut import_results = Vec::with_capacity(max_blocks_to_import);
|
||||||
|
|
||||||
let _import_lock = self.import_lock.lock();
|
let _import_lock = self.import_lock.lock();
|
||||||
@ -417,12 +418,17 @@ impl Client {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(closed_block) = self.check_and_close_block(&block) {
|
if let Ok(closed_block) = self.check_and_close_block(&block) {
|
||||||
|
if self.engine.is_proposal(&block.header) {
|
||||||
|
self.block_queue.mark_as_good(&[header.hash()]);
|
||||||
|
proposed_blocks.push(block.bytes);
|
||||||
|
} else {
|
||||||
imported_blocks.push(header.hash());
|
imported_blocks.push(header.hash());
|
||||||
|
|
||||||
let route = self.commit_block(closed_block, &header.hash(), &block.bytes);
|
let route = self.commit_block(closed_block, &header.hash(), &block.bytes);
|
||||||
import_results.push(route);
|
import_results.push(route);
|
||||||
|
|
||||||
self.report.write().accrue_block(&block);
|
self.report.write().accrue_block(&block);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
invalid_blocks.insert(header.hash());
|
invalid_blocks.insert(header.hash());
|
||||||
}
|
}
|
||||||
@ -436,7 +442,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
let is_empty = self.block_queue.mark_as_good(&imported_blocks);
|
let is_empty = self.block_queue.mark_as_good(&imported_blocks);
|
||||||
let duration_ns = precise_time_ns() - start;
|
let duration_ns = precise_time_ns() - start;
|
||||||
(imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty)
|
(imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration_ns, is_empty)
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -454,6 +460,7 @@ impl Client {
|
|||||||
enacted.clone(),
|
enacted.clone(),
|
||||||
retracted.clone(),
|
retracted.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
proposed_blocks.clone(),
|
||||||
duration,
|
duration,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -577,9 +584,10 @@ impl Client {
|
|||||||
self.miner.clone()
|
self.miner.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by PoA to try sealing on period change.
|
|
||||||
pub fn update_sealing(&self) {
|
/// Replace io channel. Useful for testing.
|
||||||
self.miner.update_sealing(self)
|
pub fn set_io_channel(&self, io_channel: IoChannel<ClientIoMessage>) {
|
||||||
|
*self.io_channel.lock() = io_channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to get a copy of a specific block's final state.
|
/// Attempt to get a copy of a specific block's final state.
|
||||||
@ -1290,6 +1298,18 @@ impl BlockChainClient for Client {
|
|||||||
self.miner.pending_transactions(self.chain.read().best_block_number())
|
self.miner.pending_transactions(self.chain.read().best_block_number())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn queue_consensus_message(&self, message: Bytes) {
|
||||||
|
let channel = self.io_channel.lock().clone();
|
||||||
|
if let Err(e) = channel.send(ClientIoMessage::NewMessage(message)) {
|
||||||
|
debug!("Ignoring the message, error queueing: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_consensus_message(&self, message: Bytes) {
|
||||||
|
self.notify(|notify| notify.broadcast(message.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn signing_network_id(&self) -> Option<u64> {
|
fn signing_network_id(&self) -> Option<u64> {
|
||||||
self.engine.signing_network_id(&self.latest_env_info())
|
self.engine.signing_network_id(&self.latest_env_info())
|
||||||
}
|
}
|
||||||
@ -1314,7 +1334,6 @@ impl BlockChainClient for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MiningBlockChainClient for Client {
|
impl MiningBlockChainClient for Client {
|
||||||
|
|
||||||
fn latest_schedule(&self) -> Schedule {
|
fn latest_schedule(&self) -> Schedule {
|
||||||
self.engine.schedule(&self.latest_env_info())
|
self.engine.schedule(&self.latest_env_info())
|
||||||
}
|
}
|
||||||
@ -1357,6 +1376,30 @@ impl MiningBlockChainClient for Client {
|
|||||||
&self.factories.vm
|
&self.factories.vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_sealing(&self) {
|
||||||
|
self.miner.update_sealing(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
|
||||||
|
if self.miner.submit_seal(self, block_hash, seal).is_err() {
|
||||||
|
warn!(target: "poa", "Wrong internal seal submission!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_proposal_block(&self, block: SealedBlock) {
|
||||||
|
self.notify(|notify| {
|
||||||
|
notify.new_blocks(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![block.rlp_bytes()],
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
||||||
let h = block.header().hash();
|
let h = block.header().hash();
|
||||||
let start = precise_time_ns();
|
let start = precise_time_ns();
|
||||||
@ -1381,6 +1424,7 @@ impl MiningBlockChainClient for Client {
|
|||||||
enacted.clone(),
|
enacted.clone(),
|
||||||
retracted.clone(),
|
retracted.clone(),
|
||||||
vec![h.clone()],
|
vec![h.clone()],
|
||||||
|
vec![],
|
||||||
precise_time_ns() - start,
|
precise_time_ns() - start,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -1416,6 +1460,12 @@ impl ::client::ProvingBlockChainClient for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Client {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.engine.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -90,6 +90,8 @@ pub struct TestBlockChainClient {
|
|||||||
pub ancient_block: RwLock<Option<(H256, u64)>>,
|
pub ancient_block: RwLock<Option<(H256, u64)>>,
|
||||||
/// First block info.
|
/// First block info.
|
||||||
pub first_block: RwLock<Option<(H256, u64)>>,
|
pub first_block: RwLock<Option<(H256, u64)>>,
|
||||||
|
/// Traces to return
|
||||||
|
pub traces: RwLock<Option<Vec<LocalizedTrace>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used for generating test client blocks.
|
/// Used for generating test client blocks.
|
||||||
@ -151,6 +153,7 @@ impl TestBlockChainClient {
|
|||||||
latest_block_timestamp: RwLock::new(10_000_000),
|
latest_block_timestamp: RwLock::new(10_000_000),
|
||||||
ancient_block: RwLock::new(None),
|
ancient_block: RwLock::new(None),
|
||||||
first_block: RwLock::new(None),
|
first_block: RwLock::new(None),
|
||||||
|
traces: RwLock::new(None),
|
||||||
};
|
};
|
||||||
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
|
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
|
||||||
client.genesis_hash = client.last_hash.read().clone();
|
client.genesis_hash = client.last_hash.read().clone();
|
||||||
@ -360,6 +363,18 @@ impl MiningBlockChainClient for TestBlockChainClient {
|
|||||||
fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult {
|
fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult {
|
||||||
Ok(H256::default())
|
Ok(H256::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn broadcast_proposal_block(&self, _block: SealedBlock) {}
|
||||||
|
|
||||||
|
fn update_sealing(&self) {
|
||||||
|
self.miner.update_sealing(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
|
||||||
|
if self.miner.submit_seal(self, block_hash, seal).is_err() {
|
||||||
|
warn!(target: "poa", "Wrong internal seal submission!")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockChainClient for TestBlockChainClient {
|
impl BlockChainClient for TestBlockChainClient {
|
||||||
@ -642,19 +657,19 @@ impl BlockChainClient for TestBlockChainClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_traces(&self, _filter: TraceFilter) -> Option<Vec<LocalizedTrace>> {
|
fn filter_traces(&self, _filter: TraceFilter) -> Option<Vec<LocalizedTrace>> {
|
||||||
unimplemented!();
|
self.traces.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trace(&self, _trace: TraceId) -> Option<LocalizedTrace> {
|
fn trace(&self, _trace: TraceId) -> Option<LocalizedTrace> {
|
||||||
unimplemented!();
|
self.traces.read().clone().and_then(|vec| vec.into_iter().next())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_traces(&self, _trace: TransactionId) -> Option<Vec<LocalizedTrace>> {
|
fn transaction_traces(&self, _trace: TransactionId) -> Option<Vec<LocalizedTrace>> {
|
||||||
unimplemented!();
|
self.traces.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_traces(&self, _trace: BlockId) -> Option<Vec<LocalizedTrace>> {
|
fn block_traces(&self, _trace: BlockId) -> Option<Vec<LocalizedTrace>> {
|
||||||
unimplemented!();
|
self.traces.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_transactions(&self, transactions: Vec<Bytes>, _peer_id: usize) {
|
fn queue_transactions(&self, transactions: Vec<Bytes>, _peer_id: usize) {
|
||||||
@ -663,6 +678,12 @@ impl BlockChainClient for TestBlockChainClient {
|
|||||||
self.miner.import_external_transactions(self, txs);
|
self.miner.import_external_transactions(self, txs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn queue_consensus_message(&self, message: Bytes) {
|
||||||
|
self.spec.engine.handle_message(&message).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_consensus_message(&self, _message: Bytes) {}
|
||||||
|
|
||||||
fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
||||||
self.miner.pending_transactions(self.chain_info().best_block_number)
|
self.miner.pending_transactions(self.chain_info().best_block_number)
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,12 @@ pub trait BlockChainClient : Sync + Send {
|
|||||||
/// Queue transactions for importing.
|
/// Queue transactions for importing.
|
||||||
fn queue_transactions(&self, transactions: Vec<Bytes>, peer_id: usize);
|
fn queue_transactions(&self, transactions: Vec<Bytes>, peer_id: usize);
|
||||||
|
|
||||||
|
/// Queue conensus engine message.
|
||||||
|
fn queue_consensus_message(&self, message: Bytes);
|
||||||
|
|
||||||
|
/// Used by PoA to communicate with peers.
|
||||||
|
fn broadcast_consensus_message(&self, message: Bytes);
|
||||||
|
|
||||||
/// list all transactions
|
/// list all transactions
|
||||||
fn pending_transactions(&self) -> Vec<SignedTransaction>;
|
fn pending_transactions(&self) -> Vec<SignedTransaction>;
|
||||||
|
|
||||||
@ -273,6 +279,15 @@ pub trait MiningBlockChainClient: BlockChainClient {
|
|||||||
/// Returns EvmFactory.
|
/// Returns EvmFactory.
|
||||||
fn vm_factory(&self) -> &EvmFactory;
|
fn vm_factory(&self) -> &EvmFactory;
|
||||||
|
|
||||||
|
/// Used by PoA to try sealing on period change.
|
||||||
|
fn update_sealing(&self);
|
||||||
|
|
||||||
|
/// Used by PoA to submit gathered signatures.
|
||||||
|
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
|
||||||
|
|
||||||
|
/// Broadcast a block proposal.
|
||||||
|
fn broadcast_proposal_block(&self, block: SealedBlock);
|
||||||
|
|
||||||
/// Import sealed block. Skips all verifications.
|
/// Import sealed block. Skips all verifications.
|
||||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult;
|
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, Rlp, View, encode};
|
|||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use block::*;
|
use block::*;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::Engine;
|
use engines::{Engine, Seal, EngineError};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use error::{Error, BlockError};
|
use error::{Error, BlockError};
|
||||||
use blockchain::extras::BlockDetails;
|
use blockchain::extras::BlockDetails;
|
||||||
@ -225,8 +225,8 @@ impl Engine for AuthorityRound {
|
|||||||
///
|
///
|
||||||
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
|
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
|
||||||
/// be returned.
|
/// be returned.
|
||||||
fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
if self.proposed.load(AtomicOrdering::SeqCst) { return None; }
|
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
|
||||||
let header = block.header();
|
let header = block.header();
|
||||||
let step = self.step();
|
let step = self.step();
|
||||||
if self.is_step_proposer(step, header.author()) {
|
if self.is_step_proposer(step, header.author()) {
|
||||||
@ -235,7 +235,8 @@ impl Engine for AuthorityRound {
|
|||||||
if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
|
if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
|
||||||
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
|
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
|
||||||
self.proposed.store(true, AtomicOrdering::SeqCst);
|
self.proposed.store(true, AtomicOrdering::SeqCst);
|
||||||
return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()];
|
||||||
|
return Seal::Regular(rlps);
|
||||||
} else {
|
} else {
|
||||||
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable.");
|
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable.");
|
||||||
}
|
}
|
||||||
@ -245,7 +246,7 @@ impl Engine for AuthorityRound {
|
|||||||
} else {
|
} else {
|
||||||
trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step);
|
trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step);
|
||||||
}
|
}
|
||||||
None
|
Seal::None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check the number of seal fields.
|
/// Check the number of seal fields.
|
||||||
@ -288,7 +289,7 @@ impl Engine for AuthorityRound {
|
|||||||
// Check if parent is from a previous step.
|
// Check if parent is from a previous step.
|
||||||
if step == try!(header_step(parent)) {
|
if step == try!(header_step(parent)) {
|
||||||
trace!(target: "poa", "Multiple blocks proposed for step {}.", step);
|
trace!(target: "poa", "Multiple blocks proposed for step {}.", step);
|
||||||
try!(Err(BlockError::DoubleVote(header.author().clone())));
|
try!(Err(EngineError::DoubleVote(header.author().clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
|
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
|
||||||
@ -347,6 +348,7 @@ mod tests {
|
|||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
|
use engines::Seal;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn has_valid_metadata() {
|
fn has_valid_metadata() {
|
||||||
@ -416,17 +418,17 @@ mod tests {
|
|||||||
let b2 = b2.close_and_lock();
|
let b2 = b2.close_and_lock();
|
||||||
|
|
||||||
engine.set_signer(addr1, "1".into());
|
engine.set_signer(addr1, "1".into());
|
||||||
if let Some(seal) = engine.generate_seal(b1.block()) {
|
if let Seal::Regular(seal) = engine.generate_seal(b1.block()) {
|
||||||
assert!(b1.clone().try_seal(engine, seal).is_ok());
|
assert!(b1.clone().try_seal(engine, seal).is_ok());
|
||||||
// Second proposal is forbidden.
|
// Second proposal is forbidden.
|
||||||
assert!(engine.generate_seal(b1.block()).is_none());
|
assert!(engine.generate_seal(b1.block()) == Seal::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.set_signer(addr2, "2".into());
|
engine.set_signer(addr2, "2".into());
|
||||||
if let Some(seal) = engine.generate_seal(b2.block()) {
|
if let Seal::Regular(seal) = engine.generate_seal(b2.block()) {
|
||||||
assert!(b2.clone().try_seal(engine, seal).is_ok());
|
assert!(b2.clone().try_seal(engine, seal).is_ok());
|
||||||
// Second proposal is forbidden.
|
// Second proposal is forbidden.
|
||||||
assert!(engine.generate_seal(b2.block()).is_none());
|
assert!(engine.generate_seal(b2.block()) == Seal::None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use account_provider::AccountProvider;
|
|||||||
use block::*;
|
use block::*;
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::Engine;
|
use engines::{Engine, Seal};
|
||||||
use env_info::EnvInfo;
|
use env_info::EnvInfo;
|
||||||
use error::{BlockError, Error};
|
use error::{BlockError, Error};
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
@ -112,20 +112,20 @@ impl Engine for BasicAuthority {
|
|||||||
///
|
///
|
||||||
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
|
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
|
||||||
/// be returned.
|
/// be returned.
|
||||||
fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
if let Some(ref ap) = *self.account_provider.lock() {
|
if let Some(ref ap) = *self.account_provider.lock() {
|
||||||
let header = block.header();
|
let header = block.header();
|
||||||
let message = header.bare_hash();
|
let message = header.bare_hash();
|
||||||
// account should be pernamently unlocked, otherwise sealing will fail
|
// account should be pernamently unlocked, otherwise sealing will fail
|
||||||
if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) {
|
if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) {
|
||||||
return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
|
return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
} else {
|
} else {
|
||||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
|
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
|
||||||
}
|
}
|
||||||
None
|
Seal::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
||||||
@ -199,6 +199,7 @@ mod tests {
|
|||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
|
use engines::Seal;
|
||||||
|
|
||||||
/// Create a new test chain spec with `BasicAuthority` consensus engine.
|
/// Create a new test chain spec with `BasicAuthority` consensus engine.
|
||||||
fn new_test_authority() -> Spec {
|
fn new_test_authority() -> Spec {
|
||||||
@ -269,9 +270,10 @@ mod tests {
|
|||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
let seal = engine.generate_seal(b.block()).unwrap();
|
if let Seal::Regular(seal) = engine.generate_seal(b.block()) {
|
||||||
assert!(b.try_seal(engine, seal).is_ok());
|
assert!(b.try_seal(engine, seal).is_ok());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seals_internally() {
|
fn seals_internally() {
|
||||||
|
@ -17,12 +17,11 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use util::Address;
|
use util::Address;
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use engines::Engine;
|
use engines::{Engine, Seal};
|
||||||
use env_info::EnvInfo;
|
use env_info::EnvInfo;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use block::ExecutedBlock;
|
use block::ExecutedBlock;
|
||||||
use util::Bytes;
|
|
||||||
|
|
||||||
/// An engine which does not provide any consensus mechanism, just seals blocks internally.
|
/// An engine which does not provide any consensus mechanism, just seals blocks internally.
|
||||||
pub struct InstantSeal {
|
pub struct InstantSeal {
|
||||||
@ -54,13 +53,13 @@ impl Engine for InstantSeal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
||||||
Schedule::new_post_eip150(usize::max_value(), false, false, false)
|
Schedule::new_post_eip150(usize::max_value(), true, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) }
|
fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) }
|
||||||
|
|
||||||
fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> {
|
fn generate_seal(&self, _block: &ExecutedBlock) -> Seal {
|
||||||
Some(Vec::new())
|
Seal::Regular(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +71,7 @@ mod tests {
|
|||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use block::*;
|
use block::*;
|
||||||
|
use engines::Seal;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn instant_can_seal() {
|
fn instant_can_seal() {
|
||||||
@ -84,9 +84,10 @@ mod tests {
|
|||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
let seal = engine.generate_seal(b.block()).unwrap();
|
if let Seal::Regular(seal) = engine.generate_seal(b.block()) {
|
||||||
assert!(b.try_seal(engine, seal).is_ok());
|
assert!(b.try_seal(engine, seal).is_ok());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn instant_cant_verify() {
|
fn instant_cant_verify() {
|
||||||
|
@ -20,11 +20,13 @@ mod null_engine;
|
|||||||
mod instant_seal;
|
mod instant_seal;
|
||||||
mod basic_authority;
|
mod basic_authority;
|
||||||
mod authority_round;
|
mod authority_round;
|
||||||
|
mod tendermint;
|
||||||
|
|
||||||
pub use self::null_engine::NullEngine;
|
pub use self::null_engine::NullEngine;
|
||||||
pub use self::instant_seal::InstantSeal;
|
pub use self::instant_seal::InstantSeal;
|
||||||
pub use self::basic_authority::BasicAuthority;
|
pub use self::basic_authority::BasicAuthority;
|
||||||
pub use self::authority_round::AuthorityRound;
|
pub use self::authority_round::AuthorityRound;
|
||||||
|
pub use self::tendermint::Tendermint;
|
||||||
|
|
||||||
use util::*;
|
use util::*;
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
@ -42,6 +44,47 @@ use ethereum::ethash;
|
|||||||
use blockchain::extras::BlockDetails;
|
use blockchain::extras::BlockDetails;
|
||||||
use views::HeaderView;
|
use views::HeaderView;
|
||||||
|
|
||||||
|
/// Voting errors.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EngineError {
|
||||||
|
/// Signature does not belong to an authority.
|
||||||
|
NotAuthorized(Address),
|
||||||
|
/// The same author issued different votes at the same step.
|
||||||
|
DoubleVote(Address),
|
||||||
|
/// The received block is from an incorrect proposer.
|
||||||
|
NotProposer(Mismatch<Address>),
|
||||||
|
/// Message was not expected.
|
||||||
|
UnexpectedMessage,
|
||||||
|
/// Seal field has an unexpected size.
|
||||||
|
BadSealFieldSize(OutOfBounds<usize>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for EngineError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::EngineError::*;
|
||||||
|
let msg = match *self {
|
||||||
|
DoubleVote(ref address) => format!("Author {} issued too many blocks.", address),
|
||||||
|
NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis),
|
||||||
|
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
|
||||||
|
UnexpectedMessage => "This Engine should not be fed messages.".into(),
|
||||||
|
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("Engine error ({})", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seal type.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum Seal {
|
||||||
|
/// Proposal seal; should be broadcasted, but not inserted into blockchain.
|
||||||
|
Proposal(Vec<Bytes>),
|
||||||
|
/// Regular block seal; should be part of the blockchain.
|
||||||
|
Regular(Vec<Bytes>),
|
||||||
|
/// Engine does generate seal for this block right now.
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
@ -94,7 +137,7 @@ pub trait Engine : Sync + Send {
|
|||||||
///
|
///
|
||||||
/// This operation is synchronous and may (quite reasonably) not be available, in which None will
|
/// This operation is synchronous and may (quite reasonably) not be available, in which None will
|
||||||
/// be returned.
|
/// be returned.
|
||||||
fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> { None }
|
fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::None }
|
||||||
|
|
||||||
/// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block)
|
/// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block)
|
||||||
/// 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.
|
||||||
@ -133,6 +176,10 @@ pub trait Engine : Sync + Send {
|
|||||||
header.set_gas_limit(parent.gas_limit().clone());
|
header.set_gas_limit(parent.gas_limit().clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle any potential consensus messages;
|
||||||
|
/// updating consensus state and potentially issuing a new one.
|
||||||
|
fn handle_message(&self, _message: &[u8]) -> Result<(), Error> { Err(EngineError::UnexpectedMessage.into()) }
|
||||||
|
|
||||||
// TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic
|
// TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic
|
||||||
// from Spec into here and removing the Spec::builtins field.
|
// from Spec into here and removing the Spec::builtins field.
|
||||||
/// Determine whether a particular address is a builtin contract.
|
/// Determine whether a particular address is a builtin contract.
|
||||||
@ -153,9 +200,16 @@ pub trait Engine : Sync + Send {
|
|||||||
ethash::is_new_best_block(best_total_difficulty, parent_details, new_header)
|
ethash::is_new_best_block(best_total_difficulty, parent_details, new_header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find out if the block is a proposal block and should not be inserted into the DB.
|
||||||
|
/// Takes a header of a fully verified block.
|
||||||
|
fn is_proposal(&self, _verified_header: &Header) -> bool { false }
|
||||||
|
|
||||||
/// Register an account which signs consensus messages.
|
/// Register an account which signs consensus messages.
|
||||||
fn set_signer(&self, _address: Address, _password: String) {}
|
fn set_signer(&self, _address: Address, _password: String) {}
|
||||||
|
|
||||||
|
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
||||||
|
fn stop(&self) {}
|
||||||
|
|
||||||
/// Add a channel for communication with Client which can be used for sealing.
|
/// Add a channel for communication with Client which can be used for sealing.
|
||||||
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
|
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
|
||||||
|
|
||||||
|
279
ethcore/src/engines/tendermint/message.rs
Normal file
279
ethcore/src/engines/tendermint/message.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
//! Tendermint message handling.
|
||||||
|
|
||||||
|
use util::*;
|
||||||
|
use super::{Height, Round, BlockHash, Step};
|
||||||
|
use error::Error;
|
||||||
|
use header::Header;
|
||||||
|
use rlp::*;
|
||||||
|
use ethkey::{recover, public_to_address};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct ConsensusMessage {
|
||||||
|
pub signature: H520,
|
||||||
|
pub height: Height,
|
||||||
|
pub round: Round,
|
||||||
|
pub step: Step,
|
||||||
|
pub block_hash: Option<BlockHash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn consensus_round(header: &Header) -> Result<Round, ::rlp::DecoderError> {
|
||||||
|
let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed");
|
||||||
|
UntrustedRlp::new(round_rlp.as_slice()).as_val()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConsensusMessage {
|
||||||
|
pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Self {
|
||||||
|
ConsensusMessage {
|
||||||
|
signature: signature,
|
||||||
|
height: height,
|
||||||
|
round: round,
|
||||||
|
step: step,
|
||||||
|
block_hash: block_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
||||||
|
Ok(ConsensusMessage {
|
||||||
|
signature: try!(UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()),
|
||||||
|
height: header.number() as Height,
|
||||||
|
round: try!(consensus_round(header)),
|
||||||
|
step: Step::Propose,
|
||||||
|
block_hash: Some(header.bare_hash()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_commit(proposal: &ConsensusMessage, signature: H520) -> Self {
|
||||||
|
ConsensusMessage {
|
||||||
|
signature: signature,
|
||||||
|
height: proposal.height,
|
||||||
|
round: proposal.round,
|
||||||
|
step: Step::Precommit,
|
||||||
|
block_hash: proposal.block_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_height(&self, height: Height) -> bool {
|
||||||
|
self.height == height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||||
|
self.height == height && self.round == round
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool {
|
||||||
|
self.height == height && self.round == round && self.step == step
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option<BlockHash>) -> bool {
|
||||||
|
self.height == h && self.round == r && self.step == s && self.block_hash == block_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_aligned(&self, m: &ConsensusMessage) -> bool {
|
||||||
|
self.is_block_hash(m.height, m.round, m.step, m.block_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> Result<Address, Error> {
|
||||||
|
let full_rlp = ::rlp::encode(self);
|
||||||
|
let block_info = Rlp::new(&full_rlp).at(1);
|
||||||
|
let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3()));
|
||||||
|
Ok(public_to_address(&public_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn precommit_hash(&self) -> H256 {
|
||||||
|
message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for ConsensusMessage {
|
||||||
|
fn partial_cmp(&self, m: &ConsensusMessage) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
fn number(&self) -> u8 {
|
||||||
|
match *self {
|
||||||
|
Step::Propose => 0,
|
||||||
|
Step::Prevote => 1,
|
||||||
|
Step::Precommit => 2,
|
||||||
|
Step::Commit => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for ConsensusMessage {
|
||||||
|
fn cmp(&self, m: &ConsensusMessage) -> Ordering {
|
||||||
|
if self.height != m.height {
|
||||||
|
self.height.cmp(&m.height)
|
||||||
|
} else if self.round != m.round {
|
||||||
|
self.round.cmp(&m.round)
|
||||||
|
} else if self.step != m.step {
|
||||||
|
self.step.number().cmp(&m.step.number())
|
||||||
|
} else {
|
||||||
|
self.signature.cmp(&m.signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for Step {
|
||||||
|
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||||
|
match try!(decoder.as_rlp().as_val()) {
|
||||||
|
0u8 => Ok(Step::Propose),
|
||||||
|
1 => Ok(Step::Prevote),
|
||||||
|
2 => Ok(Step::Precommit),
|
||||||
|
_ => Err(DecoderError::Custom("Invalid step.")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for Step {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
s.append(&self.number());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (signature, height, round, step, block_hash)
|
||||||
|
impl Decodable for ConsensusMessage {
|
||||||
|
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||||
|
let rlp = decoder.as_rlp();
|
||||||
|
let m = try!(rlp.at(1));
|
||||||
|
let block_message: H256 = try!(m.val_at(3));
|
||||||
|
Ok(ConsensusMessage {
|
||||||
|
signature: try!(rlp.val_at(0)),
|
||||||
|
height: try!(m.val_at(0)),
|
||||||
|
round: try!(m.val_at(1)),
|
||||||
|
step: try!(m.val_at(2)),
|
||||||
|
block_hash: match block_message.is_zero() {
|
||||||
|
true => None,
|
||||||
|
false => Some(block_message),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for ConsensusMessage {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
let info = message_info_rlp(self.height, self.round, self.step, self.block_hash);
|
||||||
|
s.begin_list(2)
|
||||||
|
.append(&self.signature)
|
||||||
|
.append_raw(&info, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Bytes {
|
||||||
|
// TODO: figure out whats wrong with nested list encoding
|
||||||
|
let mut s = RlpStream::new_list(5);
|
||||||
|
s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||||
|
s.out()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
||||||
|
let mut s = RlpStream::new_list(2);
|
||||||
|
s.append(signature).append_raw(vote_info, 1);
|
||||||
|
s.out()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use util::*;
|
||||||
|
use rlp::*;
|
||||||
|
use super::super::Step;
|
||||||
|
use super::*;
|
||||||
|
use account_provider::AccountProvider;
|
||||||
|
use header::Header;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode() {
|
||||||
|
let message = ConsensusMessage {
|
||||||
|
signature: H520::default(),
|
||||||
|
height: 10,
|
||||||
|
round: 123,
|
||||||
|
step: Step::Precommit,
|
||||||
|
block_hash: Some("1".sha3())
|
||||||
|
};
|
||||||
|
let raw_rlp = ::rlp::encode(&message).to_vec();
|
||||||
|
let rlp = Rlp::new(&raw_rlp);
|
||||||
|
assert_eq!(message, rlp.as_val());
|
||||||
|
|
||||||
|
let message = ConsensusMessage {
|
||||||
|
signature: H520::default(),
|
||||||
|
height: 1314,
|
||||||
|
round: 0,
|
||||||
|
step: Step::Prevote,
|
||||||
|
block_hash: None
|
||||||
|
};
|
||||||
|
let raw_rlp = ::rlp::encode(&message);
|
||||||
|
let rlp = Rlp::new(&raw_rlp);
|
||||||
|
assert_eq!(message, rlp.as_val());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_and_verify() {
|
||||||
|
let tap = Arc::new(AccountProvider::transient_provider());
|
||||||
|
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||||
|
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||||
|
|
||||||
|
let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default()));
|
||||||
|
|
||||||
|
let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi);
|
||||||
|
|
||||||
|
let rlp = UntrustedRlp::new(&raw_rlp);
|
||||||
|
let message: ConsensusMessage = rlp.as_val().unwrap();
|
||||||
|
match message.verify() { Ok(a) if a == addr => {}, _ => panic!(), };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proposal_message() {
|
||||||
|
let mut header = Header::default();
|
||||||
|
let seal = vec![
|
||||||
|
::rlp::encode(&0u8).to_vec(),
|
||||||
|
::rlp::encode(&H520::default()).to_vec(),
|
||||||
|
Vec::new()
|
||||||
|
];
|
||||||
|
header.set_seal(seal);
|
||||||
|
let message = ConsensusMessage::new_proposal(&header).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
ConsensusMessage {
|
||||||
|
signature: Default::default(),
|
||||||
|
height: 0,
|
||||||
|
round: 0,
|
||||||
|
step: Step::Propose,
|
||||||
|
block_hash: Some(header.bare_hash())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn message_info_from_header() {
|
||||||
|
let header = Header::default();
|
||||||
|
let pro = ConsensusMessage {
|
||||||
|
signature: Default::default(),
|
||||||
|
height: 0,
|
||||||
|
round: 0,
|
||||||
|
step: Step::Propose,
|
||||||
|
block_hash: Some(header.bare_hash())
|
||||||
|
};
|
||||||
|
let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||||
|
|
||||||
|
assert_eq!(pro.precommit_hash(), pre.sha3());
|
||||||
|
}
|
||||||
|
}
|
962
ethcore/src/engines/tendermint/mod.rs
Normal file
962
ethcore/src/engines/tendermint/mod.rs
Normal file
@ -0,0 +1,962 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
/// Tendermint BFT consensus engine with round robin proof-of-authority.
|
||||||
|
/// At each blockchain `Height` there can be multiple `Round`s of voting.
|
||||||
|
/// Signatures always sign `Height`, `Round`, `Step` and `BlockHash` which is a block hash without seal.
|
||||||
|
/// First a block with `Seal::Proposal` is issued by the designated proposer.
|
||||||
|
/// Next the `Round` proceeds through `Prevote` and `Precommit` `Step`s.
|
||||||
|
/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`.
|
||||||
|
/// Once enough votes have been gathered the proposer issues that block in the `Commit` step.
|
||||||
|
|
||||||
|
mod message;
|
||||||
|
mod transition;
|
||||||
|
mod params;
|
||||||
|
mod vote_collector;
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
|
use util::*;
|
||||||
|
use error::{Error, BlockError};
|
||||||
|
use header::Header;
|
||||||
|
use builtin::Builtin;
|
||||||
|
use env_info::EnvInfo;
|
||||||
|
use transaction::SignedTransaction;
|
||||||
|
use rlp::{UntrustedRlp, View};
|
||||||
|
use ethkey::{recover, public_to_address};
|
||||||
|
use account_provider::AccountProvider;
|
||||||
|
use block::*;
|
||||||
|
use spec::CommonParams;
|
||||||
|
use engines::{Engine, Seal, EngineError};
|
||||||
|
use blockchain::extras::BlockDetails;
|
||||||
|
use views::HeaderView;
|
||||||
|
use evm::Schedule;
|
||||||
|
use io::{IoService, IoChannel};
|
||||||
|
use service::ClientIoMessage;
|
||||||
|
use self::message::*;
|
||||||
|
use self::transition::TransitionHandler;
|
||||||
|
use self::params::TendermintParams;
|
||||||
|
use self::vote_collector::VoteCollector;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum Step {
|
||||||
|
Propose,
|
||||||
|
Prevote,
|
||||||
|
Precommit,
|
||||||
|
Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
pub fn is_pre(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Step::Prevote | Step::Precommit => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Height = usize;
|
||||||
|
pub type Round = usize;
|
||||||
|
pub type BlockHash = H256;
|
||||||
|
|
||||||
|
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
|
||||||
|
pub struct Tendermint {
|
||||||
|
params: CommonParams,
|
||||||
|
our_params: TendermintParams,
|
||||||
|
builtins: BTreeMap<Address, Builtin>,
|
||||||
|
step_service: IoService<Step>,
|
||||||
|
/// Address to be used as authority.
|
||||||
|
authority: RwLock<Address>,
|
||||||
|
/// Password used for signing messages.
|
||||||
|
password: RwLock<Option<String>>,
|
||||||
|
/// Blockchain height.
|
||||||
|
height: AtomicUsize,
|
||||||
|
/// Consensus round.
|
||||||
|
round: AtomicUsize,
|
||||||
|
/// Consensus step.
|
||||||
|
step: RwLock<Step>,
|
||||||
|
/// Vote accumulator.
|
||||||
|
votes: VoteCollector,
|
||||||
|
/// Channel for updating the sealing.
|
||||||
|
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
|
||||||
|
/// Used to sign messages and proposals.
|
||||||
|
account_provider: Mutex<Option<Arc<AccountProvider>>>,
|
||||||
|
/// Message for the last PoLC.
|
||||||
|
lock_change: RwLock<Option<ConsensusMessage>>,
|
||||||
|
/// Last lock round.
|
||||||
|
last_lock: AtomicUsize,
|
||||||
|
/// Bare hash of the proposed block, used for seal submission.
|
||||||
|
proposal: RwLock<Option<H256>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tendermint {
|
||||||
|
/// Create a new instance of Tendermint engine
|
||||||
|
pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> {
|
||||||
|
let engine = Arc::new(
|
||||||
|
Tendermint {
|
||||||
|
params: params,
|
||||||
|
our_params: our_params,
|
||||||
|
builtins: builtins,
|
||||||
|
step_service: try!(IoService::<Step>::start()),
|
||||||
|
authority: RwLock::new(Address::default()),
|
||||||
|
password: RwLock::new(None),
|
||||||
|
height: AtomicUsize::new(1),
|
||||||
|
round: AtomicUsize::new(0),
|
||||||
|
step: RwLock::new(Step::Propose),
|
||||||
|
votes: VoteCollector::new(),
|
||||||
|
message_channel: Mutex::new(None),
|
||||||
|
account_provider: Mutex::new(None),
|
||||||
|
lock_change: RwLock::new(None),
|
||||||
|
last_lock: AtomicUsize::new(0),
|
||||||
|
proposal: RwLock::new(None),
|
||||||
|
});
|
||||||
|
let handler = TransitionHandler { engine: Arc::downgrade(&engine) };
|
||||||
|
try!(engine.step_service.register_handler(Arc::new(handler)));
|
||||||
|
Ok(engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_sealing(&self) {
|
||||||
|
if let Some(ref channel) = *self.message_channel.lock() {
|
||||||
|
match channel.send(ClientIoMessage::UpdateSealing) {
|
||||||
|
Ok(_) => trace!(target: "poa", "UpdateSealing message sent."),
|
||||||
|
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
|
||||||
|
if let Some(ref channel) = *self.message_channel.lock() {
|
||||||
|
match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) {
|
||||||
|
Ok(_) => trace!(target: "poa", "SubmitSeal message sent."),
|
||||||
|
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_message(&self, message: Bytes) {
|
||||||
|
let channel = self.message_channel.lock().clone();
|
||||||
|
if let Some(ref channel) = channel {
|
||||||
|
match channel.send(ClientIoMessage::BroadcastMessage(message)) {
|
||||||
|
Ok(_) => trace!(target: "poa", "BroadcastMessage message sent."),
|
||||||
|
Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "broadcast_message: No IoChannel available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_message(&self, block_hash: Option<BlockHash>) -> Option<Bytes> {
|
||||||
|
if let Some(ref ap) = *self.account_provider.lock() {
|
||||||
|
let h = self.height.load(AtomicOrdering::SeqCst);
|
||||||
|
let r = self.round.load(AtomicOrdering::SeqCst);
|
||||||
|
let s = self.step.read();
|
||||||
|
let vote_info = message_info_rlp(h, r, *s, block_hash);
|
||||||
|
let authority = self.authority.read();
|
||||||
|
match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) {
|
||||||
|
Ok(signature) => {
|
||||||
|
let message_rlp = message_full_rlp(&signature, &vote_info);
|
||||||
|
let message = ConsensusMessage::new(signature, h, r, *s, block_hash);
|
||||||
|
self.votes.vote(message.clone(), *authority);
|
||||||
|
debug!(target: "poa", "Generated {:?} as {}.", message, *authority);
|
||||||
|
self.handle_valid_message(&message);
|
||||||
|
|
||||||
|
Some(message_rlp)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
trace!(target: "poa", "Could not sign the message {}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "No AccountProvider available.");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_and_broadcast_message(&self, block_hash: Option<BlockHash>) {
|
||||||
|
if let Some(message) = self.generate_message(block_hash) {
|
||||||
|
self.broadcast_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Broadcast all messages since last issued block to get the peers up to speed.
|
||||||
|
fn broadcast_old_messages(&self) {
|
||||||
|
for m in self.votes.get_up_to(self.height.load(AtomicOrdering::SeqCst)).into_iter() {
|
||||||
|
self.broadcast_message(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_next_height(&self, height: Height) {
|
||||||
|
let new_height = height + 1;
|
||||||
|
debug!(target: "poa", "Received a Commit, transitioning to height {}.", new_height);
|
||||||
|
self.last_lock.store(0, AtomicOrdering::SeqCst);
|
||||||
|
self.height.store(new_height, AtomicOrdering::SeqCst);
|
||||||
|
self.round.store(0, AtomicOrdering::SeqCst);
|
||||||
|
*self.lock_change.write() = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use via step_service to transition steps.
|
||||||
|
fn to_step(&self, step: Step) {
|
||||||
|
if let Err(io_err) = self.step_service.send_message(step) {
|
||||||
|
warn!(target: "poa", "Could not proceed to step {}.", io_err)
|
||||||
|
}
|
||||||
|
*self.step.write() = step;
|
||||||
|
match step {
|
||||||
|
Step::Propose => {
|
||||||
|
*self.proposal.write() = None;
|
||||||
|
self.update_sealing()
|
||||||
|
},
|
||||||
|
Step::Prevote => {
|
||||||
|
let block_hash = match *self.lock_change.read() {
|
||||||
|
Some(ref m) if !self.should_unlock(m.round) => m.block_hash,
|
||||||
|
_ => self.proposal.read().clone(),
|
||||||
|
};
|
||||||
|
self.generate_and_broadcast_message(block_hash);
|
||||||
|
},
|
||||||
|
Step::Precommit => {
|
||||||
|
trace!(target: "poa", "to_step: Precommit.");
|
||||||
|
let block_hash = match *self.lock_change.read() {
|
||||||
|
Some(ref m) if self.is_round(m) && m.block_hash.is_some() => {
|
||||||
|
trace!(target: "poa", "Setting last lock: {}", m.round);
|
||||||
|
self.last_lock.store(m.round, AtomicOrdering::SeqCst);
|
||||||
|
m.block_hash
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
self.generate_and_broadcast_message(block_hash);
|
||||||
|
},
|
||||||
|
Step::Commit => {
|
||||||
|
trace!(target: "poa", "to_step: Commit.");
|
||||||
|
// Commit the block using a complete signature set.
|
||||||
|
let round = self.round.load(AtomicOrdering::SeqCst);
|
||||||
|
let height = self.height.load(AtomicOrdering::SeqCst);
|
||||||
|
if let Some(block_hash) = *self.proposal.read() {
|
||||||
|
// Generate seal and remove old votes.
|
||||||
|
if self.is_proposer(&*self.authority.read()).is_ok() {
|
||||||
|
if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) {
|
||||||
|
trace!(target: "poa", "Collected seal: {:?}", seal);
|
||||||
|
let seal = vec![
|
||||||
|
::rlp::encode(&round).to_vec(),
|
||||||
|
::rlp::encode(&seal.proposal).to_vec(),
|
||||||
|
::rlp::encode(&seal.votes).to_vec()
|
||||||
|
];
|
||||||
|
self.submit_seal(block_hash, seal);
|
||||||
|
self.to_next_height(height);
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "Not enough votes found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_authority(&self, address: &Address) -> bool {
|
||||||
|
self.our_params.authorities.contains(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_above_threshold(&self, n: usize) -> bool {
|
||||||
|
n > self.our_params.authority_n * 2/3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if address is a proposer for given round.
|
||||||
|
fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> {
|
||||||
|
let ref p = self.our_params;
|
||||||
|
let proposer_nonce = height + round;
|
||||||
|
trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce);
|
||||||
|
let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed");
|
||||||
|
if proposer == address {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if address is the current proposer.
|
||||||
|
fn is_proposer(&self, address: &Address) -> Result<(), EngineError> {
|
||||||
|
self.is_round_proposer(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_height(&self, message: &ConsensusMessage) -> bool {
|
||||||
|
message.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_round(&self, message: &ConsensusMessage) -> bool {
|
||||||
|
message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_round(&self, n: Round) {
|
||||||
|
trace!(target: "poa", "increment_round: New round.");
|
||||||
|
self.round.fetch_add(n, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||||
|
self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round
|
||||||
|
&& lock_change_round < self.round.load(AtomicOrdering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn has_enough_any_votes(&self) -> bool {
|
||||||
|
let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read());
|
||||||
|
self.is_above_threshold(step_votes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool {
|
||||||
|
if message.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||||
|
let step_votes = self.votes.count_step_votes(message.height, message.round, message.step);
|
||||||
|
self.is_above_threshold(step_votes)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool {
|
||||||
|
let aligned_count = self.votes.count_aligned_votes(&message);
|
||||||
|
self.is_above_threshold(aligned_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
||||||
|
let is_newer_than_lock = match *self.lock_change.read() {
|
||||||
|
Some(ref lock) => message > lock,
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
let lock_change = is_newer_than_lock
|
||||||
|
&& message.step == Step::Prevote
|
||||||
|
&& message.block_hash.is_some()
|
||||||
|
&& self.has_enough_aligned_votes(message);
|
||||||
|
if lock_change {
|
||||||
|
trace!(target: "poa", "handle_valid_message: Lock change.");
|
||||||
|
*self.lock_change.write() = Some(message.clone());
|
||||||
|
}
|
||||||
|
// Check if it can affect the step transition.
|
||||||
|
if self.is_height(message) {
|
||||||
|
let next_step = match *self.step.read() {
|
||||||
|
Step::Precommit if self.has_enough_aligned_votes(message) => {
|
||||||
|
if message.block_hash.is_none() {
|
||||||
|
self.increment_round(1);
|
||||||
|
Some(Step::Propose)
|
||||||
|
} else {
|
||||||
|
Some(Step::Commit)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Step::Precommit if self.has_enough_future_step_votes(message) => {
|
||||||
|
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||||
|
Some(Step::Precommit)
|
||||||
|
},
|
||||||
|
// Avoid counting twice.
|
||||||
|
Step::Prevote if lock_change => Some(Step::Precommit),
|
||||||
|
Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit),
|
||||||
|
Step::Prevote if self.has_enough_future_step_votes(message) => {
|
||||||
|
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||||
|
Some(Step::Prevote)
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(step) = next_step {
|
||||||
|
trace!(target: "poa", "Transition to {:?} triggered.", step);
|
||||||
|
self.to_step(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine for Tendermint {
|
||||||
|
fn name(&self) -> &str { "Tendermint" }
|
||||||
|
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
|
||||||
|
/// (consensus round, proposal signature, authority signatures)
|
||||||
|
fn seal_fields(&self) -> usize { 3 }
|
||||||
|
|
||||||
|
fn params(&self) -> &CommonParams { &self.params }
|
||||||
|
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
|
||||||
|
|
||||||
|
fn maximum_uncle_count(&self) -> usize { 0 }
|
||||||
|
fn maximum_uncle_age(&self) -> usize { 0 }
|
||||||
|
|
||||||
|
/// Additional engine-specific information for the user/developer concerning `header`.
|
||||||
|
fn extra_info(&self, header: &Header) -> BTreeMap<String, String> {
|
||||||
|
let message = ConsensusMessage::new_proposal(header).expect("Invalid header.");
|
||||||
|
map![
|
||||||
|
"signature".into() => message.signature.to_string(),
|
||||||
|
"height".into() => message.height.to_string(),
|
||||||
|
"round".into() => message.round.to_string(),
|
||||||
|
"block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into())
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
||||||
|
Schedule::new_post_eip150(usize::max_value(), true, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
|
||||||
|
header.set_difficulty(parent.difficulty().clone());
|
||||||
|
header.set_gas_limit({
|
||||||
|
let gas_limit = parent.gas_limit().clone();
|
||||||
|
let bound_divisor = self.our_params.gas_limit_bound_divisor;
|
||||||
|
if gas_limit < gas_floor_target {
|
||||||
|
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
|
||||||
|
} else {
|
||||||
|
max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should this node participate.
|
||||||
|
fn is_sealer(&self, address: &Address) -> Option<bool> {
|
||||||
|
Some(self.is_authority(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to seal generate a proposal seal.
|
||||||
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
|
if let Some(ref ap) = *self.account_provider.lock() {
|
||||||
|
let header = block.header();
|
||||||
|
let author = header.author();
|
||||||
|
// Only proposer can generate seal if None was generated.
|
||||||
|
if self.is_proposer(author).is_err() || self.proposal.read().is_some() {
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let height = header.number() as Height;
|
||||||
|
let round = self.round.load(AtomicOrdering::SeqCst);
|
||||||
|
let bh = Some(header.bare_hash());
|
||||||
|
let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone());
|
||||||
|
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) {
|
||||||
|
// Insert Propose vote.
|
||||||
|
debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round);
|
||||||
|
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author);
|
||||||
|
// Remember proposal for later seal submission.
|
||||||
|
*self.proposal.write() = bh;
|
||||||
|
Seal::Proposal(vec![
|
||||||
|
::rlp::encode(&round).to_vec(),
|
||||||
|
::rlp::encode(&signature).to_vec(),
|
||||||
|
::rlp::EMPTY_LIST_RLP.to_vec()
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable");
|
||||||
|
Seal::None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "generate_seal: FAIL: accounts not provided");
|
||||||
|
Seal::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message(&self, rlp: &[u8]) -> Result<(), Error> {
|
||||||
|
let rlp = UntrustedRlp::new(rlp);
|
||||||
|
let message: ConsensusMessage = try!(rlp.as_val());
|
||||||
|
if !self.votes.is_old_or_known(&message) {
|
||||||
|
let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3())));
|
||||||
|
if !self.is_authority(&sender) {
|
||||||
|
try!(Err(EngineError::NotAuthorized(sender)));
|
||||||
|
}
|
||||||
|
self.broadcast_message(rlp.as_raw().to_vec());
|
||||||
|
trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender);
|
||||||
|
self.votes.vote(message.clone(), sender);
|
||||||
|
self.handle_valid_message(&message);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
|
let seal_length = header.seal().len();
|
||||||
|
if seal_length == self.seal_fields() {
|
||||||
|
let signatures_len = header.seal()[2].len();
|
||||||
|
if signatures_len >= 1 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(From::from(EngineError::BadSealFieldSize(OutOfBounds {
|
||||||
|
min: Some(1),
|
||||||
|
max: None,
|
||||||
|
found: signatures_len
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(From::from(BlockError::InvalidSealArity(
|
||||||
|
Mismatch { expected: self.seal_fields(), found: seal_length }
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
|
let proposal = try!(ConsensusMessage::new_proposal(header));
|
||||||
|
let proposer = try!(proposal.verify());
|
||||||
|
if !self.is_authority(&proposer) {
|
||||||
|
try!(Err(EngineError::NotAuthorized(proposer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let precommit_hash = proposal.precommit_hash();
|
||||||
|
let ref signatures_field = header.seal()[2];
|
||||||
|
let mut signature_count = 0;
|
||||||
|
let mut origins = HashSet::new();
|
||||||
|
for rlp in UntrustedRlp::new(signatures_field).iter() {
|
||||||
|
let precommit: ConsensusMessage = ConsensusMessage::new_commit(&proposal, try!(rlp.as_val()));
|
||||||
|
let address = match self.votes.get(&precommit) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => public_to_address(&try!(recover(&precommit.signature.into(), &precommit_hash))),
|
||||||
|
};
|
||||||
|
if !self.our_params.authorities.contains(&address) {
|
||||||
|
try!(Err(EngineError::NotAuthorized(address.to_owned())))
|
||||||
|
}
|
||||||
|
|
||||||
|
if origins.insert(address) {
|
||||||
|
signature_count += 1;
|
||||||
|
} else {
|
||||||
|
warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address);
|
||||||
|
try!(Err(BlockError::InvalidSeal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if its a proposal if there is not enough precommits.
|
||||||
|
if !self.is_above_threshold(signature_count) {
|
||||||
|
let signatures_len = signatures_field.len();
|
||||||
|
// Proposal has to have an empty signature list.
|
||||||
|
if signatures_len != 1 {
|
||||||
|
try!(Err(EngineError::BadSealFieldSize(OutOfBounds {
|
||||||
|
min: Some(1),
|
||||||
|
max: Some(1),
|
||||||
|
found: signatures_len
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
try!(self.is_round_proposer(proposal.height, proposal.round, &proposer));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
|
if header.number() == 0 {
|
||||||
|
try!(Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||||
|
}
|
||||||
|
|
||||||
|
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
|
||||||
|
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
|
||||||
|
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
|
||||||
|
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
|
||||||
|
try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() })));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
|
||||||
|
try!(t.check_low_s());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
|
||||||
|
t.sender().map(|_|()) // Perform EC recovery and cache sender
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_signer(&self, address: Address, password: String) {
|
||||||
|
*self.authority.write() = address;
|
||||||
|
*self.password.write() = Some(password);
|
||||||
|
self.to_step(Step::Propose);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self) {
|
||||||
|
self.step_service.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool {
|
||||||
|
let new_number = new_header.number();
|
||||||
|
let best_number = best_header.number();
|
||||||
|
trace!(target: "poa", "new_header: {}, best_header: {}", new_number, best_number);
|
||||||
|
if new_number != best_number {
|
||||||
|
new_number > best_number
|
||||||
|
} else {
|
||||||
|
let new_seal = new_header.seal();
|
||||||
|
let best_seal = best_header.seal();
|
||||||
|
let new_signatures = new_seal.get(2).expect("Tendermint seal should have three elements.").len();
|
||||||
|
let best_signatures = best_seal.get(2).expect("Tendermint seal should have three elements.").len();
|
||||||
|
if new_signatures > best_signatures {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let new_round: Round = ::rlp::Rlp::new(&new_seal.get(0).expect("Tendermint seal should have three elements.")).as_val();
|
||||||
|
let best_round: Round = ::rlp::Rlp::new(&best_seal.get(0).expect("Tendermint seal should have three elements.")).as_val();
|
||||||
|
new_round > best_round
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_proposal(&self, header: &Header) -> bool {
|
||||||
|
let signatures_len = header.seal()[2].len();
|
||||||
|
// Signatures have to be an empty list rlp.
|
||||||
|
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
||||||
|
if signatures_len != 1 {
|
||||||
|
// New Commit received, skip to next height.
|
||||||
|
trace!(target: "poa", "Received a commit for height {}, round {}.", proposal.height, proposal.round);
|
||||||
|
self.to_next_height(proposal.height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
||||||
|
debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer);
|
||||||
|
if self.is_round(&proposal) {
|
||||||
|
*self.proposal.write() = proposal.block_hash.clone();
|
||||||
|
}
|
||||||
|
self.votes.vote(proposal, proposer);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Equivalent to a timeout: to be used for tests.
|
||||||
|
fn step(&self) {
|
||||||
|
let next_step = match *self.step.read() {
|
||||||
|
Step::Propose => {
|
||||||
|
trace!(target: "poa", "Propose timeout.");
|
||||||
|
Step::Prevote
|
||||||
|
},
|
||||||
|
Step::Prevote if self.has_enough_any_votes() => {
|
||||||
|
trace!(target: "poa", "Prevote timeout.");
|
||||||
|
Step::Precommit
|
||||||
|
},
|
||||||
|
Step::Prevote => {
|
||||||
|
trace!(target: "poa", "Prevote timeout without enough votes.");
|
||||||
|
self.broadcast_old_messages();
|
||||||
|
Step::Prevote
|
||||||
|
},
|
||||||
|
Step::Precommit if self.has_enough_any_votes() => {
|
||||||
|
trace!(target: "poa", "Precommit timeout.");
|
||||||
|
self.increment_round(1);
|
||||||
|
Step::Propose
|
||||||
|
},
|
||||||
|
Step::Precommit => {
|
||||||
|
trace!(target: "poa", "Precommit timeout without enough votes.");
|
||||||
|
self.broadcast_old_messages();
|
||||||
|
Step::Precommit
|
||||||
|
},
|
||||||
|
Step::Commit => {
|
||||||
|
trace!(target: "poa", "Commit timeout.");
|
||||||
|
Step::Propose
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.to_step(next_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
|
||||||
|
trace!(target: "poa", "Register the IoChannel.");
|
||||||
|
*self.message_channel.lock() = Some(message_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
|
||||||
|
*self.account_provider.lock() = Some(account_provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use util::*;
|
||||||
|
use util::trie::TrieSpec;
|
||||||
|
use io::{IoContext, IoHandler};
|
||||||
|
use block::*;
|
||||||
|
use error::{Error, BlockError};
|
||||||
|
use header::Header;
|
||||||
|
use io::IoChannel;
|
||||||
|
use env_info::EnvInfo;
|
||||||
|
use tests::helpers::*;
|
||||||
|
use account_provider::AccountProvider;
|
||||||
|
use service::ClientIoMessage;
|
||||||
|
use spec::Spec;
|
||||||
|
use engines::{Engine, EngineError, Seal};
|
||||||
|
use super::*;
|
||||||
|
use super::message::*;
|
||||||
|
|
||||||
|
/// Accounts inserted with "0" and "1" are authorities. First proposer is "0".
|
||||||
|
fn setup() -> (Spec, Arc<AccountProvider>) {
|
||||||
|
let tap = Arc::new(AccountProvider::transient_provider());
|
||||||
|
let spec = Spec::new_test_tendermint();
|
||||||
|
spec.engine.register_account_provider(tap.clone());
|
||||||
|
(spec, tap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec<Bytes>) {
|
||||||
|
let mut db_result = get_temp_state_db();
|
||||||
|
let mut db = db_result.take();
|
||||||
|
spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap();
|
||||||
|
let genesis_header = spec.genesis_header();
|
||||||
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
|
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
|
let b = b.close_and_lock();
|
||||||
|
if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) {
|
||||||
|
(b, seal)
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vote<F>(engine: &Arc<Engine>, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
|
||||||
|
let mi = message_info_rlp(height, round, step, block_hash);
|
||||||
|
let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
|
||||||
|
engine.handle_message(&m).unwrap();
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proposal_seal(tap: &Arc<AccountProvider>, header: &Header, round: Round) -> Vec<Bytes> {
|
||||||
|
let author = header.author();
|
||||||
|
let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash()));
|
||||||
|
let signature = tap.sign(*author, None, vote_info.sha3()).unwrap();
|
||||||
|
vec![
|
||||||
|
::rlp::encode(&round).to_vec(),
|
||||||
|
::rlp::encode(&H520::from(signature)).to_vec(),
|
||||||
|
::rlp::EMPTY_LIST_RLP.to_vec()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn precommit_signatures(tap: &Arc<AccountProvider>, height: Height, round: Round, bare_hash: Option<H256>, v1: H160, v2: H160) -> Bytes {
|
||||||
|
let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash);
|
||||||
|
::rlp::encode(&vec![
|
||||||
|
H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()),
|
||||||
|
H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap())
|
||||||
|
]).to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
||||||
|
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
|
||||||
|
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||||
|
addr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Arc<Engine>, acc: &str) -> Address {
|
||||||
|
let addr = insert_and_unlock(tap, acc);
|
||||||
|
engine.set_signer(addr.clone(), acc.into());
|
||||||
|
addr
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestIo {
|
||||||
|
received: RwLock<Vec<ClientIoMessage>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestIo {
|
||||||
|
fn new() -> Arc<Self> { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoHandler<ClientIoMessage> for TestIo {
|
||||||
|
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
|
||||||
|
self.received.write().push(net_message.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_valid_metadata() {
|
||||||
|
let engine = Spec::new_test_tendermint().engine;
|
||||||
|
assert!(!engine.name().is_empty());
|
||||||
|
assert!(engine.version().major >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_return_schedule() {
|
||||||
|
let engine = Spec::new_test_tendermint().engine;
|
||||||
|
let schedule = engine.schedule(&EnvInfo {
|
||||||
|
number: 10000000,
|
||||||
|
author: 0.into(),
|
||||||
|
timestamp: 0,
|
||||||
|
difficulty: 0.into(),
|
||||||
|
last_hashes: Arc::new(vec![]),
|
||||||
|
gas_used: 0.into(),
|
||||||
|
gas_limit: 0.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(schedule.stack_limit > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verification_fails_on_short_seal() {
|
||||||
|
let engine = Spec::new_test_tendermint().engine;
|
||||||
|
let header = Header::default();
|
||||||
|
|
||||||
|
let verify_result = engine.verify_block_basic(&header, None);
|
||||||
|
|
||||||
|
match verify_result {
|
||||||
|
Err(Error::Block(BlockError::InvalidSealArity(_))) => {},
|
||||||
|
Err(_) => { panic!("should be block seal-arity mismatch error (got {:?})", verify_result); },
|
||||||
|
_ => { panic!("Should be error, got Ok"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn allows_correct_proposer() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
let engine = spec.engine;
|
||||||
|
|
||||||
|
let mut header = Header::default();
|
||||||
|
let validator = insert_and_unlock(&tap, "0");
|
||||||
|
header.set_author(validator);
|
||||||
|
let seal = proposal_seal(&tap, &header, 0);
|
||||||
|
header.set_seal(seal);
|
||||||
|
// Good proposer.
|
||||||
|
assert!(engine.verify_block_unordered(&header.clone(), None).is_ok());
|
||||||
|
|
||||||
|
let validator = insert_and_unlock(&tap, "1");
|
||||||
|
header.set_author(validator);
|
||||||
|
let seal = proposal_seal(&tap, &header, 0);
|
||||||
|
header.set_seal(seal);
|
||||||
|
// Bad proposer.
|
||||||
|
match engine.verify_block_unordered(&header, None) {
|
||||||
|
Err(Error::Engine(EngineError::NotProposer(_))) => {},
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let random = insert_and_unlock(&tap, "101");
|
||||||
|
header.set_author(random);
|
||||||
|
let seal = proposal_seal(&tap, &header, 0);
|
||||||
|
header.set_seal(seal);
|
||||||
|
// Not authority.
|
||||||
|
match engine.verify_block_unordered(&header, None) {
|
||||||
|
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
engine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seal_signatures_checking() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
let engine = spec.engine;
|
||||||
|
|
||||||
|
let mut header = Header::default();
|
||||||
|
let proposer = insert_and_unlock(&tap, "1");
|
||||||
|
header.set_author(proposer);
|
||||||
|
let mut seal = proposal_seal(&tap, &header, 0);
|
||||||
|
|
||||||
|
let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||||
|
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||||
|
|
||||||
|
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
|
||||||
|
header.set_seal(seal.clone());
|
||||||
|
|
||||||
|
// One good signature is not enough.
|
||||||
|
match engine.verify_block_unordered(&header, None) {
|
||||||
|
Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {},
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let voter = insert_and_unlock(&tap, "0");
|
||||||
|
let signature0 = tap.sign(voter, None, vote_info.sha3()).unwrap();
|
||||||
|
|
||||||
|
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec();
|
||||||
|
header.set_seal(seal.clone());
|
||||||
|
|
||||||
|
assert!(engine.verify_block_unordered(&header, None).is_ok());
|
||||||
|
|
||||||
|
let bad_voter = insert_and_unlock(&tap, "101");
|
||||||
|
let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap();
|
||||||
|
|
||||||
|
seal[2] = ::rlp::encode(&vec![H520::from(signature1), H520::from(bad_signature)]).to_vec();
|
||||||
|
header.set_seal(seal);
|
||||||
|
|
||||||
|
// One good and one bad signature.
|
||||||
|
match engine.verify_block_unordered(&header, None) {
|
||||||
|
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
engine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_generate_seal() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
|
||||||
|
let proposer = insert_and_register(&tap, &spec.engine, "1");
|
||||||
|
|
||||||
|
let (b, seal) = propose_default(&spec, proposer);
|
||||||
|
assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok());
|
||||||
|
spec.engine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_recognize_proposal() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
|
||||||
|
let proposer = insert_and_register(&tap, &spec.engine, "1");
|
||||||
|
|
||||||
|
let (b, seal) = propose_default(&spec, proposer);
|
||||||
|
let sealed = b.seal(spec.engine.as_ref(), seal).unwrap();
|
||||||
|
assert!(spec.engine.is_proposal(sealed.header()));
|
||||||
|
spec.engine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn relays_messages() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
let engine = spec.engine.clone();
|
||||||
|
let mut db_result = get_temp_state_db();
|
||||||
|
let mut db = db_result.take();
|
||||||
|
spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap();
|
||||||
|
|
||||||
|
let v0 = insert_and_register(&tap, &engine, "0");
|
||||||
|
let v1 = insert_and_register(&tap, &engine, "1");
|
||||||
|
|
||||||
|
let h = 0;
|
||||||
|
let r = 0;
|
||||||
|
|
||||||
|
// Propose
|
||||||
|
let (b, _) = propose_default(&spec, v1.clone());
|
||||||
|
let proposal = Some(b.header().bare_hash());
|
||||||
|
|
||||||
|
// Register IoHandler remembers messages.
|
||||||
|
let test_io = TestIo::new();
|
||||||
|
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
|
||||||
|
engine.register_message_channel(channel);
|
||||||
|
|
||||||
|
let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
|
||||||
|
|
||||||
|
let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
|
||||||
|
|
||||||
|
let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal);
|
||||||
|
|
||||||
|
// Relays all valid present and future messages.
|
||||||
|
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current)));
|
||||||
|
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current)));
|
||||||
|
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future)));
|
||||||
|
engine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seal_submission() {
|
||||||
|
let (spec, tap) = setup();
|
||||||
|
let engine = spec.engine.clone();
|
||||||
|
let mut db_result = get_temp_state_db();
|
||||||
|
let mut db = db_result.take();
|
||||||
|
spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap();
|
||||||
|
|
||||||
|
let v0 = insert_and_register(&tap, &engine, "0");
|
||||||
|
let v1 = insert_and_register(&tap, &engine, "1");
|
||||||
|
|
||||||
|
let h = 1;
|
||||||
|
let r = 0;
|
||||||
|
|
||||||
|
// Register IoHandler remembers messages.
|
||||||
|
let test_io = TestIo::new();
|
||||||
|
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
|
||||||
|
engine.register_message_channel(channel);
|
||||||
|
|
||||||
|
// Propose
|
||||||
|
let (b, mut seal) = propose_default(&spec, v1.clone());
|
||||||
|
let proposal = Some(b.header().bare_hash());
|
||||||
|
engine.step();
|
||||||
|
|
||||||
|
// Prevote.
|
||||||
|
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
|
||||||
|
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
|
||||||
|
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
|
||||||
|
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
|
||||||
|
|
||||||
|
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0);
|
||||||
|
let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()));
|
||||||
|
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1);
|
||||||
|
let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal));
|
||||||
|
|
||||||
|
assert!(first ^ second);
|
||||||
|
engine.stop();
|
||||||
|
}
|
||||||
|
}
|
72
ethcore/src/engines/tendermint/params.rs
Normal file
72
ethcore/src/engines/tendermint/params.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
//! Tendermint specific parameters.
|
||||||
|
|
||||||
|
use ethjson;
|
||||||
|
use super::transition::TendermintTimeouts;
|
||||||
|
use util::{Address, U256};
|
||||||
|
use time::Duration;
|
||||||
|
|
||||||
|
/// `Tendermint` params.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TendermintParams {
|
||||||
|
/// Gas limit divisor.
|
||||||
|
pub gas_limit_bound_divisor: U256,
|
||||||
|
/// List of authorities.
|
||||||
|
pub authorities: Vec<Address>,
|
||||||
|
/// Number of authorities.
|
||||||
|
pub authority_n: usize,
|
||||||
|
/// Timeout durations for different steps.
|
||||||
|
pub timeouts: TendermintTimeouts,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TendermintParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()];
|
||||||
|
let val_n = authorities.len();
|
||||||
|
TendermintParams {
|
||||||
|
gas_limit_bound_divisor: 0x0400.into(),
|
||||||
|
authorities: authorities,
|
||||||
|
authority_n: val_n,
|
||||||
|
timeouts: TendermintTimeouts::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_duration(ms: ethjson::uint::Uint) -> Duration {
|
||||||
|
let ms: usize = ms.into();
|
||||||
|
Duration::milliseconds(ms as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
||||||
|
fn from(p: ethjson::spec::TendermintParams) -> Self {
|
||||||
|
let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect();
|
||||||
|
let val_n = val.len();
|
||||||
|
let dt = TendermintTimeouts::default();
|
||||||
|
TendermintParams {
|
||||||
|
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
||||||
|
authorities: val,
|
||||||
|
authority_n: val_n,
|
||||||
|
timeouts: TendermintTimeouts {
|
||||||
|
propose: p.timeout_propose.map_or(dt.propose, to_duration),
|
||||||
|
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
|
||||||
|
precommit: p.timeout_precommit.map_or(dt.precommit, to_duration),
|
||||||
|
commit: p.timeout_commit.map_or(dt.commit, to_duration),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
ethcore/src/engines/tendermint/transition.rs
Normal file
96
ethcore/src/engines/tendermint/transition.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
//! Tendermint timeout handling.
|
||||||
|
|
||||||
|
use std::sync::Weak;
|
||||||
|
use time::Duration;
|
||||||
|
use io::{IoContext, IoHandler, TimerToken};
|
||||||
|
use super::{Tendermint, Step};
|
||||||
|
use engines::Engine;
|
||||||
|
|
||||||
|
pub struct TransitionHandler {
|
||||||
|
pub engine: Weak<Tendermint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base timeout of each step in ms.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TendermintTimeouts {
|
||||||
|
pub propose: Duration,
|
||||||
|
pub prevote: Duration,
|
||||||
|
pub precommit: Duration,
|
||||||
|
pub commit: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TendermintTimeouts {
|
||||||
|
pub fn for_step(&self, step: Step) -> Duration {
|
||||||
|
match step {
|
||||||
|
Step::Propose => self.propose,
|
||||||
|
Step::Prevote => self.prevote,
|
||||||
|
Step::Precommit => self.precommit,
|
||||||
|
Step::Commit => self.commit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TendermintTimeouts {
|
||||||
|
fn default() -> Self {
|
||||||
|
TendermintTimeouts {
|
||||||
|
propose: Duration::milliseconds(10000),
|
||||||
|
prevote: Duration::milliseconds(10000),
|
||||||
|
precommit: Duration::milliseconds(10000),
|
||||||
|
commit: Duration::milliseconds(10000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timer token representing the consensus step timeouts.
|
||||||
|
pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23;
|
||||||
|
|
||||||
|
fn set_timeout(io: &IoContext<Step>, timeout: Duration) {
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, timeout.num_milliseconds() as u64)
|
||||||
|
.unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoHandler<Step> for TransitionHandler {
|
||||||
|
fn initialize(&self, io: &IoContext<Step>) {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
set_timeout(io, engine.our_params.timeouts.propose)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout(&self, _io: &IoContext<Step>, timer: TimerToken) {
|
||||||
|
if timer == ENGINE_TIMEOUT_TOKEN {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
engine.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(&self, io: &IoContext<Step>, next_step: &Step) {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) {
|
||||||
|
warn!(target: "poa", "Could not remove consensus timer {}.", io_err)
|
||||||
|
}
|
||||||
|
match *next_step {
|
||||||
|
Step::Propose => set_timeout(io, engine.our_params.timeouts.propose),
|
||||||
|
Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote),
|
||||||
|
Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit),
|
||||||
|
Step::Commit => set_timeout(io, engine.our_params.timeouts.commit),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
272
ethcore/src/engines/tendermint/vote_collector.rs
Normal file
272
ethcore/src/engines/tendermint/vote_collector.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
//! Collects votes on hashes at each height and round.
|
||||||
|
|
||||||
|
use util::*;
|
||||||
|
use super::message::ConsensusMessage;
|
||||||
|
use super::{Height, Round, Step};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VoteCollector {
|
||||||
|
/// Storing all Proposals, Prevotes and Precommits.
|
||||||
|
votes: RwLock<BTreeMap<ConsensusMessage, Address>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SealSignatures {
|
||||||
|
pub proposal: H520,
|
||||||
|
pub votes: Vec<H520>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SealSignatures {
|
||||||
|
fn eq(&self, other: &SealSignatures) -> bool {
|
||||||
|
self.proposal == other.proposal
|
||||||
|
&& self.votes.iter().collect::<HashSet<_>>() == other.votes.iter().collect::<HashSet<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for SealSignatures {}
|
||||||
|
|
||||||
|
impl VoteCollector {
|
||||||
|
pub fn new() -> VoteCollector {
|
||||||
|
let mut collector = BTreeMap::new();
|
||||||
|
// Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||||
|
collector.insert(ConsensusMessage {
|
||||||
|
signature: H520::default(),
|
||||||
|
height: 0,
|
||||||
|
round: 0,
|
||||||
|
step: Step::Propose,
|
||||||
|
block_hash: None
|
||||||
|
},
|
||||||
|
Address::default());
|
||||||
|
VoteCollector { votes: RwLock::new(collector) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert vote if it is newer than the oldest one.
|
||||||
|
pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option<Address> {
|
||||||
|
self.votes.write().insert(message, voter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool {
|
||||||
|
self.votes.read().get(message).map_or(false, |a| {
|
||||||
|
trace!(target: "poa", "Known message from {}: {:?}.", a, message);
|
||||||
|
true
|
||||||
|
}) || {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
let is_old = guard.keys().next().map_or(true, |oldest| message <= oldest);
|
||||||
|
if is_old { trace!(target: "poa", "Old message {:?}.", message); }
|
||||||
|
is_old
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Throws out messages older than message, leaves message as marker for the oldest.
|
||||||
|
pub fn throw_out_old(&self, message: &ConsensusMessage) {
|
||||||
|
let mut guard = self.votes.write();
|
||||||
|
let new_collector = guard.split_off(message);
|
||||||
|
*guard = new_collector;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option<SealSignatures> {
|
||||||
|
let bh = Some(block_hash);
|
||||||
|
let (proposal, votes) = {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh));
|
||||||
|
let proposal = current_signatures.next().cloned();
|
||||||
|
let votes = current_signatures
|
||||||
|
.skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh))
|
||||||
|
.filter(|m| m.is_block_hash(height, round, Step::Precommit, bh))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
(proposal, votes)
|
||||||
|
};
|
||||||
|
if votes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Remove messages that are no longer relevant.
|
||||||
|
votes.last().map(|m| self.throw_out_old(m));
|
||||||
|
let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect();
|
||||||
|
votes_vec.sort();
|
||||||
|
proposal.map(|p| SealSignatures {
|
||||||
|
proposal: p.signature,
|
||||||
|
votes: votes_vec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
guard.keys()
|
||||||
|
.skip_while(|m| !m.is_aligned(message))
|
||||||
|
// sorted by signature so might not be continuous
|
||||||
|
.filter(|m| m.is_aligned(message))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step));
|
||||||
|
let mut origins = HashSet::new();
|
||||||
|
let mut n = 0;
|
||||||
|
for (message, origin) in current {
|
||||||
|
if message.is_step(height, round, step) {
|
||||||
|
if origins.insert(origin) {
|
||||||
|
n += 1;
|
||||||
|
} else {
|
||||||
|
warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_up_to(&self, height: Height) -> Vec<Bytes> {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
guard
|
||||||
|
.keys()
|
||||||
|
.filter(|m| m.step.is_pre())
|
||||||
|
.take_while(|m| m.height <= height)
|
||||||
|
.map(|m| ::rlp::encode(m).to_vec())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, message: &ConsensusMessage) -> Option<Address> {
|
||||||
|
let guard = self.votes.read();
|
||||||
|
guard.get(message).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use util::*;
|
||||||
|
use super::*;
|
||||||
|
use super::super::{Height, Round, BlockHash, Step};
|
||||||
|
use super::super::message::ConsensusMessage;
|
||||||
|
|
||||||
|
fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>) -> Option<H160> {
|
||||||
|
full_vote(collector, signature, h, r, step, block_hash, H160::random())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>, address: Address) -> Option<H160> {
|
||||||
|
collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seal_retrieval() {
|
||||||
|
let collector = VoteCollector::new();
|
||||||
|
let bh = Some("1".sha3());
|
||||||
|
let h = 1;
|
||||||
|
let r = 2;
|
||||||
|
let mut signatures = Vec::new();
|
||||||
|
for _ in 0..5 {
|
||||||
|
signatures.push(H520::random());
|
||||||
|
}
|
||||||
|
// Wrong height proposal.
|
||||||
|
random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone());
|
||||||
|
// Good proposal
|
||||||
|
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone());
|
||||||
|
// Wrong block proposal.
|
||||||
|
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3()));
|
||||||
|
// Wrong block precommit.
|
||||||
|
random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3()));
|
||||||
|
// Wrong round proposal.
|
||||||
|
random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone());
|
||||||
|
// Prevote.
|
||||||
|
random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone());
|
||||||
|
// Relevant precommit.
|
||||||
|
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||||
|
// Replcated vote.
|
||||||
|
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||||
|
// Wrong round precommit.
|
||||||
|
random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||||
|
// Wrong height precommit.
|
||||||
|
random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone());
|
||||||
|
// Relevant precommit.
|
||||||
|
random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone());
|
||||||
|
// Wrong round precommit, same signature.
|
||||||
|
random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||||
|
// Wrong round precommit.
|
||||||
|
random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone());
|
||||||
|
let seal = SealSignatures {
|
||||||
|
proposal: signatures[0],
|
||||||
|
votes: signatures[1..3].to_vec()
|
||||||
|
};
|
||||||
|
assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_votes() {
|
||||||
|
let collector = VoteCollector::new();
|
||||||
|
// good prevote
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||||
|
// good precommit
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||||
|
// good prevote
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||||
|
// good prevote
|
||||||
|
let same_sig = H520::random();
|
||||||
|
random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||||
|
random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3()));
|
||||||
|
// good precommit
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3()));
|
||||||
|
// good prevote
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||||
|
|
||||||
|
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4);
|
||||||
|
assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2);
|
||||||
|
|
||||||
|
let message = ConsensusMessage {
|
||||||
|
signature: H520::default(),
|
||||||
|
height: 3,
|
||||||
|
round: 2,
|
||||||
|
step: Step::Prevote,
|
||||||
|
block_hash: Some("1".sha3())
|
||||||
|
};
|
||||||
|
assert_eq!(collector.count_aligned_votes(&message), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_old() {
|
||||||
|
let collector = VoteCollector::new();
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||||
|
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||||
|
|
||||||
|
let message = ConsensusMessage {
|
||||||
|
signature: H520::default(),
|
||||||
|
height: 3,
|
||||||
|
round: 2,
|
||||||
|
step: Step::Precommit,
|
||||||
|
block_hash: Some("1".sha3())
|
||||||
|
};
|
||||||
|
collector.throw_out_old(&message);
|
||||||
|
assert_eq!(collector.votes.read().len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malicious_authority() {
|
||||||
|
let collector = VoteCollector::new();
|
||||||
|
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default());
|
||||||
|
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default());
|
||||||
|
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1);
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ use client::Error as ClientError;
|
|||||||
use ipc::binary::{BinaryConvertError, BinaryConvertable};
|
use ipc::binary::{BinaryConvertError, BinaryConvertable};
|
||||||
use types::block_import_error::BlockImportError;
|
use types::block_import_error::BlockImportError;
|
||||||
use snapshot::Error as SnapshotError;
|
use snapshot::Error as SnapshotError;
|
||||||
|
use engines::EngineError;
|
||||||
use ethkey::Error as EthkeyError;
|
use ethkey::Error as EthkeyError;
|
||||||
|
|
||||||
pub use types::executed::{ExecutionError, CallError};
|
pub use types::executed::{ExecutionError, CallError};
|
||||||
@ -167,8 +168,6 @@ pub enum BlockError {
|
|||||||
UnknownParent(H256),
|
UnknownParent(H256),
|
||||||
/// Uncle parent given is unknown.
|
/// Uncle parent given is unknown.
|
||||||
UnknownUncleParent(H256),
|
UnknownUncleParent(H256),
|
||||||
/// The same author issued different votes at the same step.
|
|
||||||
DoubleVote(H160),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BlockError {
|
impl fmt::Display for BlockError {
|
||||||
@ -202,7 +201,6 @@ impl fmt::Display for BlockError {
|
|||||||
RidiculousNumber(ref oob) => format!("Implausible block number. {}", oob),
|
RidiculousNumber(ref oob) => format!("Implausible block number. {}", oob),
|
||||||
UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
|
UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
|
||||||
UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
|
UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
|
||||||
DoubleVote(ref address) => format!("Author {} issued too many blocks.", address),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_fmt(format_args!("Block error ({})", msg))
|
f.write_fmt(format_args!("Block error ({})", msg))
|
||||||
@ -263,6 +261,8 @@ pub enum Error {
|
|||||||
Snappy(::util::snappy::InvalidInput),
|
Snappy(::util::snappy::InvalidInput),
|
||||||
/// Snapshot error.
|
/// Snapshot error.
|
||||||
Snapshot(SnapshotError),
|
Snapshot(SnapshotError),
|
||||||
|
/// Consensus vote error.
|
||||||
|
Engine(EngineError),
|
||||||
/// Ethkey error.
|
/// Ethkey error.
|
||||||
Ethkey(EthkeyError),
|
Ethkey(EthkeyError),
|
||||||
}
|
}
|
||||||
@ -285,6 +285,7 @@ impl fmt::Display for Error {
|
|||||||
Error::StdIo(ref err) => err.fmt(f),
|
Error::StdIo(ref err) => err.fmt(f),
|
||||||
Error::Snappy(ref err) => err.fmt(f),
|
Error::Snappy(ref err) => err.fmt(f),
|
||||||
Error::Snapshot(ref err) => err.fmt(f),
|
Error::Snapshot(ref err) => err.fmt(f),
|
||||||
|
Error::Engine(ref err) => err.fmt(f),
|
||||||
Error::Ethkey(ref err) => err.fmt(f),
|
Error::Ethkey(ref err) => err.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,6 +384,12 @@ impl From<SnapshotError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<EngineError> for Error {
|
||||||
|
fn from(err: EngineError) -> Error {
|
||||||
|
Error::Engine(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<EthkeyError> for Error {
|
impl From<EthkeyError> for Error {
|
||||||
fn from(err: EthkeyError) -> Error {
|
fn from(err: EthkeyError) -> Error {
|
||||||
Error::Ethkey(err)
|
Error::Ethkey(err)
|
||||||
|
@ -26,12 +26,12 @@ use state::{State, CleanupMode};
|
|||||||
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId};
|
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId};
|
||||||
use client::TransactionImportResult;
|
use client::TransactionImportResult;
|
||||||
use executive::contract_address;
|
use executive::contract_address;
|
||||||
use block::{ClosedBlock, SealedBlock, IsBlock, Block};
|
use block::{ClosedBlock, IsBlock, Block};
|
||||||
use error::*;
|
use error::*;
|
||||||
use transaction::{Action, SignedTransaction};
|
use transaction::{Action, SignedTransaction};
|
||||||
use receipt::{Receipt, RichReceipt};
|
use receipt::{Receipt, RichReceipt};
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::Engine;
|
use engines::{Engine, Seal};
|
||||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||||
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
||||||
use miner::work_notify::WorkPoster;
|
use miner::work_notify::WorkPoster;
|
||||||
@ -466,34 +466,43 @@ impl Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed),
|
/// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal.
|
||||||
/// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine.
|
|
||||||
fn seal_block_internally(&self, block: ClosedBlock) -> Result<SealedBlock, Option<ClosedBlock>> {
|
|
||||||
trace!(target: "miner", "seal_block_internally: attempting internal seal.");
|
|
||||||
let s = self.engine.generate_seal(block.block());
|
|
||||||
if let Some(seal) = s {
|
|
||||||
trace!(target: "miner", "seal_block_internally: managed internal seal. importing...");
|
|
||||||
block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| {
|
|
||||||
warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e);
|
|
||||||
Err(None)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
trace!(target: "miner", "seal_block_internally: unable to generate seal internally");
|
|
||||||
Err(Some(block))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uses Engine to seal the block internally and then imports it to chain.
|
|
||||||
fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool {
|
fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool {
|
||||||
if !block.transactions().is_empty() || self.forced_sealing() {
|
if !block.transactions().is_empty() || self.forced_sealing() {
|
||||||
if let Ok(sealed) = self.seal_block_internally(block) {
|
trace!(target: "miner", "seal_block_internally: attempting internal seal.");
|
||||||
if chain.import_sealed_block(sealed).is_ok() {
|
match self.engine.generate_seal(block.block()) {
|
||||||
trace!(target: "miner", "import_block_internally: imported internally sealed block");
|
// Save proposal for later seal submission and broadcast it.
|
||||||
return true
|
Seal::Proposal(seal) => {
|
||||||
}
|
trace!(target: "miner", "Received a Proposal seal.");
|
||||||
}
|
{
|
||||||
|
let mut sealing_work = self.sealing_work.lock();
|
||||||
|
sealing_work.queue.push(block.clone());
|
||||||
|
sealing_work.queue.use_last_ref();
|
||||||
}
|
}
|
||||||
|
block
|
||||||
|
.lock()
|
||||||
|
.seal(&*self.engine, seal)
|
||||||
|
.map(|sealed| { chain.broadcast_proposal_block(sealed); true })
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("ERROR: seal failed when given internally generated seal: {}", e);
|
||||||
false
|
false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// Directly import a regular sealed block.
|
||||||
|
Seal::Regular(seal) =>
|
||||||
|
block
|
||||||
|
.lock()
|
||||||
|
.seal(&*self.engine, seal)
|
||||||
|
.map(|sealed| chain.import_sealed_block(sealed).is_ok())
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("ERROR: seal failed when given internally generated seal: {}", e);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
Seal::None => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares work which has to be done to seal.
|
/// Prepares work which has to be done to seal.
|
||||||
@ -1024,7 +1033,6 @@ impl MinerService for Miner {
|
|||||||
self.transaction_queue.lock().last_nonce(address)
|
self.transaction_queue.lock().last_nonce(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Update sealing if required.
|
/// Update sealing if required.
|
||||||
/// Prepare the block and work if the Engine does not seal internally.
|
/// Prepare the block and work if the Engine does not seal internally.
|
||||||
fn update_sealing(&self, chain: &MiningBlockChainClient) {
|
fn update_sealing(&self, chain: &MiningBlockChainClient) {
|
||||||
@ -1039,7 +1047,9 @@ impl MinerService for Miner {
|
|||||||
let (block, original_work_hash) = self.prepare_block(chain);
|
let (block, original_work_hash) = self.prepare_block(chain);
|
||||||
if self.seals_internally {
|
if self.seals_internally {
|
||||||
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
||||||
self.seal_and_import_block_internally(chain, block);
|
if self.seal_and_import_block_internally(chain, block) {
|
||||||
|
trace!(target: "miner", "update_sealing: imported internally sealed block");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work");
|
trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work");
|
||||||
self.prepare_work(block, original_work_hash);
|
self.prepare_work(block, original_work_hash);
|
||||||
|
@ -20,7 +20,7 @@ use util::*;
|
|||||||
use io::*;
|
use io::*;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use error::*;
|
use error::*;
|
||||||
use client::{Client, ClientConfig, ChainNotify};
|
use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify};
|
||||||
use miner::Miner;
|
use miner::Miner;
|
||||||
use snapshot::ManifestData;
|
use snapshot::ManifestData;
|
||||||
use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
|
use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
|
||||||
@ -28,11 +28,9 @@ use std::sync::atomic::AtomicBool;
|
|||||||
|
|
||||||
#[cfg(feature="ipc")]
|
#[cfg(feature="ipc")]
|
||||||
use nanoipc;
|
use nanoipc;
|
||||||
#[cfg(feature="ipc")]
|
|
||||||
use client::BlockChainClient;
|
|
||||||
|
|
||||||
/// Message type for external and internal events
|
/// Message type for external and internal events
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum ClientIoMessage {
|
pub enum ClientIoMessage {
|
||||||
/// Best Block Hash in chain has been changed
|
/// Best Block Hash in chain has been changed
|
||||||
NewChainHead,
|
NewChainHead,
|
||||||
@ -50,6 +48,12 @@ pub enum ClientIoMessage {
|
|||||||
TakeSnapshot(u64),
|
TakeSnapshot(u64),
|
||||||
/// Trigger sealing update (useful for internal sealing).
|
/// Trigger sealing update (useful for internal sealing).
|
||||||
UpdateSealing,
|
UpdateSealing,
|
||||||
|
/// Submit seal (useful for internal sealing).
|
||||||
|
SubmitSeal(H256, Vec<Bytes>),
|
||||||
|
/// Broadcast a message to the network.
|
||||||
|
BroadcastMessage(Bytes),
|
||||||
|
/// New consensus message received.
|
||||||
|
NewMessage(Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Client service setup. Creates and registers client and network services with the IO subsystem.
|
/// Client service setup. Creates and registers client and network services with the IO subsystem.
|
||||||
@ -77,9 +81,6 @@ impl ClientService {
|
|||||||
panic_handler.forward_from(&io_service);
|
panic_handler.forward_from(&io_service);
|
||||||
|
|
||||||
info!("Configured for {} using {} engine", Colour::White.bold().paint(spec.name.clone()), Colour::Yellow.bold().paint(spec.engine.name()));
|
info!("Configured for {} using {} engine", Colour::White.bold().paint(spec.name.clone()), Colour::Yellow.bold().paint(spec.engine.name()));
|
||||||
if spec.fork_name.is_some() {
|
|
||||||
warn!("Your chain is an alternative fork. {}", Colour::Red.bold().paint("TRANSACTIONS MAY BE REPLAYED ON THE MAINNET!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let mut db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
|
||||||
@ -220,9 +221,11 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler {
|
|||||||
debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e);
|
debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientIoMessage::UpdateSealing => {
|
ClientIoMessage::UpdateSealing => self.client.update_sealing(),
|
||||||
trace!(target: "authorityround", "message: UpdateSealing");
|
ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()),
|
||||||
self.client.update_sealing()
|
ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()),
|
||||||
|
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
|
||||||
|
trace!(target: "poa", "Invalid message received: {}", e);
|
||||||
},
|
},
|
||||||
_ => {} // ignore other messages
|
_ => {} // ignore other messages
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use service::ClientIoMessage;
|
|||||||
use views::HeaderView;
|
use views::HeaderView;
|
||||||
|
|
||||||
use io::IoChannel;
|
use io::IoChannel;
|
||||||
use util::hash::H256;
|
use util::{H256, Bytes};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -107,6 +107,7 @@ impl ChainNotify for Watcher {
|
|||||||
_: Vec<H256>,
|
_: Vec<H256>,
|
||||||
_: Vec<H256>,
|
_: Vec<H256>,
|
||||||
_: Vec<H256>,
|
_: Vec<H256>,
|
||||||
|
_: Vec<Bytes>,
|
||||||
_duration: u64)
|
_duration: u64)
|
||||||
{
|
{
|
||||||
if self.oracle.is_major_importing() { return }
|
if self.oracle.is_major_importing() { return }
|
||||||
@ -174,6 +175,7 @@ mod tests {
|
|||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
vec![],
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
//! Spec seal.
|
//! Spec seal.
|
||||||
|
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
use util::hash::{H64, H256};
|
use util::hash::{H64, H256, H520};
|
||||||
use ethjson;
|
use ethjson;
|
||||||
|
|
||||||
/// Classic ethereum seal.
|
/// Classic ethereum seal.
|
||||||
@ -32,23 +32,55 @@ impl Into<Generic> for Ethereum {
|
|||||||
fn into(self) -> Generic {
|
fn into(self) -> Generic {
|
||||||
let mut s = RlpStream::new_list(2);
|
let mut s = RlpStream::new_list(2);
|
||||||
s.append(&self.mix_hash).append(&self.nonce);
|
s.append(&self.mix_hash).append(&self.nonce);
|
||||||
Generic {
|
Generic(s.out())
|
||||||
rlp: s.out()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic seal.
|
/// AuthorityRound seal.
|
||||||
pub struct Generic {
|
pub struct AuthorityRound {
|
||||||
/// Seal rlp.
|
/// Seal step.
|
||||||
pub rlp: Vec<u8>,
|
pub step: usize,
|
||||||
|
/// Seal signature.
|
||||||
|
pub signature: H520,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tendermint seal.
|
||||||
|
pub struct Tendermint {
|
||||||
|
/// Seal round.
|
||||||
|
pub round: usize,
|
||||||
|
/// Proposal seal signature.
|
||||||
|
pub proposal: H520,
|
||||||
|
/// Precommit seal signatures.
|
||||||
|
pub precommits: Vec<H520>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Generic> for AuthorityRound {
|
||||||
|
fn into(self) -> Generic {
|
||||||
|
let mut s = RlpStream::new_list(2);
|
||||||
|
s.append(&self.step).append(&self.signature);
|
||||||
|
Generic(s.out())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Generic> for Tendermint {
|
||||||
|
fn into(self) -> Generic {
|
||||||
|
let mut s = RlpStream::new_list(3);
|
||||||
|
s.append(&self.round).append(&self.proposal).append(&self.precommits);
|
||||||
|
Generic(s.out())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Generic(pub Vec<u8>);
|
||||||
|
|
||||||
/// Genesis seal type.
|
/// Genesis seal type.
|
||||||
pub enum Seal {
|
pub enum Seal {
|
||||||
/// Classic ethereum seal.
|
/// Classic ethereum seal.
|
||||||
Ethereum(Ethereum),
|
Ethereum(Ethereum),
|
||||||
/// Generic seal.
|
/// AuthorityRound seal.
|
||||||
|
AuthorityRound(AuthorityRound),
|
||||||
|
/// Tendermint seal.
|
||||||
|
Tendermint(Tendermint),
|
||||||
|
/// Generic RLP seal.
|
||||||
Generic(Generic),
|
Generic(Generic),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,9 +91,16 @@ impl From<ethjson::spec::Seal> for Seal {
|
|||||||
nonce: eth.nonce.into(),
|
nonce: eth.nonce.into(),
|
||||||
mix_hash: eth.mix_hash.into()
|
mix_hash: eth.mix_hash.into()
|
||||||
}),
|
}),
|
||||||
ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic {
|
ethjson::spec::Seal::AuthorityRound(ar) => Seal::AuthorityRound(AuthorityRound {
|
||||||
rlp: g.rlp.into()
|
step: ar.step.into(),
|
||||||
})
|
signature: ar.signature.into()
|
||||||
|
}),
|
||||||
|
ethjson::spec::Seal::Tendermint(tender) => Seal::Tendermint(Tendermint {
|
||||||
|
round: tender.round.into(),
|
||||||
|
proposal: tender.proposal.into(),
|
||||||
|
precommits: tender.precommits.into_iter().map(Into::into).collect()
|
||||||
|
}),
|
||||||
|
ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic(g.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +109,9 @@ impl Into<Generic> for Seal {
|
|||||||
fn into(self) -> Generic {
|
fn into(self) -> Generic {
|
||||||
match self {
|
match self {
|
||||||
Seal::Generic(generic) => generic,
|
Seal::Generic(generic) => generic,
|
||||||
Seal::Ethereum(eth) => eth.into()
|
Seal::Ethereum(eth) => eth.into(),
|
||||||
|
Seal::AuthorityRound(ar) => ar.into(),
|
||||||
|
Seal::Tendermint(tender) => tender.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
use util::*;
|
use util::*;
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound};
|
use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound, Tendermint};
|
||||||
use pod_state::*;
|
use pod_state::*;
|
||||||
use account_db::*;
|
use account_db::*;
|
||||||
use header::{BlockNumber, Header};
|
use header::{BlockNumber, Header};
|
||||||
@ -66,8 +66,8 @@ pub struct Spec {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
/// What engine are we using for this?
|
/// What engine are we using for this?
|
||||||
pub engine: Arc<Engine>,
|
pub engine: Arc<Engine>,
|
||||||
/// The fork identifier for this chain. Only needed to distinguish two chains sharing the same genesis.
|
/// Name of the subdir inside the main data dir to use for chain data and settings.
|
||||||
pub fork_name: Option<String>,
|
pub data_dir: String,
|
||||||
|
|
||||||
/// Known nodes on the network in enode format.
|
/// Known nodes on the network in enode format.
|
||||||
pub nodes: Vec<String>,
|
pub nodes: Vec<String>,
|
||||||
@ -107,13 +107,13 @@ impl From<ethjson::spec::Spec> for Spec {
|
|||||||
fn from(s: ethjson::spec::Spec) -> Self {
|
fn from(s: ethjson::spec::Spec) -> Self {
|
||||||
let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect();
|
let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect();
|
||||||
let g = Genesis::from(s.genesis);
|
let g = Genesis::from(s.genesis);
|
||||||
let seal: GenericSeal = g.seal.into();
|
let GenericSeal(seal_rlp) = g.seal.into();
|
||||||
let params = CommonParams::from(s.params);
|
let params = CommonParams::from(s.params);
|
||||||
Spec {
|
Spec {
|
||||||
name: s.name.into(),
|
name: s.name.clone().into(),
|
||||||
params: params.clone(),
|
params: params.clone(),
|
||||||
engine: Spec::engine(s.engine, params, builtins),
|
engine: Spec::engine(s.engine, params, builtins),
|
||||||
fork_name: s.fork_name.map(Into::into),
|
data_dir: s.data_dir.unwrap_or(s.name).into(),
|
||||||
nodes: s.nodes.unwrap_or_else(Vec::new),
|
nodes: s.nodes.unwrap_or_else(Vec::new),
|
||||||
parent_hash: g.parent_hash,
|
parent_hash: g.parent_hash,
|
||||||
transactions_root: g.transactions_root,
|
transactions_root: g.transactions_root,
|
||||||
@ -124,7 +124,7 @@ impl From<ethjson::spec::Spec> for Spec {
|
|||||||
gas_used: g.gas_used,
|
gas_used: g.gas_used,
|
||||||
timestamp: g.timestamp,
|
timestamp: g.timestamp,
|
||||||
extra_data: g.extra_data,
|
extra_data: g.extra_data,
|
||||||
seal_rlp: seal.rlp,
|
seal_rlp: seal_rlp,
|
||||||
state_root_memo: RwLock::new(g.state_root),
|
state_root_memo: RwLock::new(g.state_root),
|
||||||
genesis_state: From::from(s.accounts),
|
genesis_state: From::from(s.accounts),
|
||||||
}
|
}
|
||||||
@ -146,7 +146,8 @@ impl Spec {
|
|||||||
ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)),
|
ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)),
|
||||||
ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)),
|
ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)),
|
||||||
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)),
|
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)),
|
||||||
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Consensus engine could not be started."),
|
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."),
|
||||||
|
ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins).expect("Failed to start the Tendermint consensus engine."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +209,7 @@ impl Spec {
|
|||||||
|
|
||||||
/// Overwrite the genesis components.
|
/// Overwrite the genesis components.
|
||||||
pub fn overwrite_genesis_params(&mut self, g: Genesis) {
|
pub fn overwrite_genesis_params(&mut self, g: Genesis) {
|
||||||
let seal: GenericSeal = g.seal.into();
|
let GenericSeal(seal_rlp) = g.seal.into();
|
||||||
self.parent_hash = g.parent_hash;
|
self.parent_hash = g.parent_hash;
|
||||||
self.transactions_root = g.transactions_root;
|
self.transactions_root = g.transactions_root;
|
||||||
self.receipts_root = g.receipts_root;
|
self.receipts_root = g.receipts_root;
|
||||||
@ -218,7 +219,7 @@ impl Spec {
|
|||||||
self.gas_used = g.gas_used;
|
self.gas_used = g.gas_used;
|
||||||
self.timestamp = g.timestamp;
|
self.timestamp = g.timestamp;
|
||||||
self.extra_data = g.extra_data;
|
self.extra_data = g.extra_data;
|
||||||
self.seal_rlp = seal.rlp;
|
self.seal_rlp = seal_rlp;
|
||||||
self.state_root_memo = RwLock::new(g.state_root);
|
self.state_root_memo = RwLock::new(g.state_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +276,10 @@ impl Spec {
|
|||||||
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
|
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
|
||||||
/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
|
/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
|
||||||
pub fn new_test_round() -> Self { load_bundled!("authority_round") }
|
pub fn new_test_round() -> Self { load_bundled!("authority_round") }
|
||||||
|
|
||||||
|
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work).
|
||||||
|
/// Account "0".sha3() and "1".sha3() are a authorities.
|
||||||
|
pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -457,7 +457,6 @@ impl StateDB {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use state::Account;
|
use state::Account;
|
||||||
@ -531,4 +530,3 @@ mod tests {
|
|||||||
assert!(s.get_cached_account(&address).is_none());
|
assert!(s.get_cached_account(&address).is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use super::trace::{Action, Res};
|
|||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
|
|
||||||
/// Localized trace.
|
/// Localized trace.
|
||||||
#[derive(Debug, PartialEq, Binary)]
|
#[derive(Debug, PartialEq, Clone, Binary)]
|
||||||
pub struct LocalizedTrace {
|
pub struct LocalizedTrace {
|
||||||
/// Type of action performed by a transaction.
|
/// Type of action performed by a transaction.
|
||||||
pub action: Action,
|
pub action: Action,
|
||||||
|
@ -18,6 +18,7 @@ use std::ops::{Deref, DerefMut};
|
|||||||
use std::cmp::PartialEq;
|
use std::cmp::PartialEq;
|
||||||
use std::{mem, fmt};
|
use std::{mem, fmt};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
||||||
use secp256k1::key::{SecretKey, PublicKey};
|
use secp256k1::key::{SecretKey, PublicKey};
|
||||||
use rustc_serialize::hex::{ToHex, FromHex};
|
use rustc_serialize::hex::{ToHex, FromHex};
|
||||||
@ -116,6 +117,18 @@ impl Default for Signature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Signature {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
H520::from(self.0).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Signature {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Signature(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<[u8; 65]> for Signature {
|
impl From<[u8; 65]> for Signature {
|
||||||
fn from(s: [u8; 65]) -> Self {
|
fn from(s: [u8; 65]) -> Self {
|
||||||
Signature(s)
|
Signature(s)
|
||||||
|
@ -18,7 +18,6 @@ use std::{fs, io};
|
|||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use time;
|
use time;
|
||||||
use ethkey::Address;
|
|
||||||
use {json, SafeAccount, Error};
|
use {json, SafeAccount, Error};
|
||||||
use json::Uuid;
|
use json::Uuid;
|
||||||
use super::KeyDirectory;
|
use super::KeyDirectory;
|
||||||
@ -106,6 +105,11 @@ impl KeyDirectory for DiskDirectory {
|
|||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
// Disk store handles updates correctly iff filename is the same
|
||||||
|
self.insert(account)
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
// transform account into key file
|
// transform account into key file
|
||||||
let keyfile: json::KeyFile = account.clone().into();
|
let keyfile: json::KeyFile = account.clone().into();
|
||||||
@ -138,12 +142,12 @@ impl KeyDirectory for DiskDirectory {
|
|||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
// enumerate all entries in keystore
|
// enumerate all entries in keystore
|
||||||
// and find entry with given address
|
// and find entry with given address
|
||||||
let to_remove = try!(self.files())
|
let to_remove = try!(self.files())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|&(_, ref account)| &account.address == address);
|
.find(|&(_, ref acc)| acc == account);
|
||||||
|
|
||||||
// remove it
|
// remove it
|
||||||
match to_remove {
|
match to_remove {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ethkey::Address;
|
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||||
|
|
||||||
@ -89,7 +88,11 @@ impl KeyDirectory for GethDirectory {
|
|||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
self.dir.remove(address)
|
self.dir.update(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
ethstore/src/dir/memory.rs
Normal file
67
ethstore/src/dir/memory.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use ethkey::Address;
|
||||||
|
|
||||||
|
use {SafeAccount, Error};
|
||||||
|
use super::KeyDirectory;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MemoryDirectory {
|
||||||
|
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyDirectory for MemoryDirectory {
|
||||||
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
Ok(self.accounts.read().values().cloned().flatten().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
let mut lock = self.accounts.write();
|
||||||
|
let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
// If the filename is the same we just need to replace the entry
|
||||||
|
accounts.retain(|acc| acc.filename != account.filename);
|
||||||
|
accounts.push(account.clone());
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
let mut lock = self.accounts.write();
|
||||||
|
let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
accounts.push(account.clone());
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
let mut accounts = self.accounts.write();
|
||||||
|
let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) {
|
||||||
|
if let Some(position) = accounts.iter().position(|acc| acc == account) {
|
||||||
|
accounts.remove(position);
|
||||||
|
}
|
||||||
|
accounts.is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if is_empty {
|
||||||
|
accounts.remove(&account.address);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,12 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use ethkey::Address;
|
|
||||||
use std::path::{PathBuf};
|
use std::path::{PathBuf};
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
|
|
||||||
mod disk;
|
mod disk;
|
||||||
mod geth;
|
mod geth;
|
||||||
|
mod memory;
|
||||||
mod parity;
|
mod parity;
|
||||||
|
|
||||||
pub enum DirectoryType {
|
pub enum DirectoryType {
|
||||||
@ -30,10 +30,12 @@ pub enum DirectoryType {
|
|||||||
pub trait KeyDirectory: Send + Sync {
|
pub trait KeyDirectory: Send + Sync {
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error>;
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||||
fn path(&self) -> Option<&PathBuf> { None }
|
fn path(&self) -> Option<&PathBuf> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::disk::DiskDirectory;
|
pub use self::disk::DiskDirectory;
|
||||||
pub use self::geth::GethDirectory;
|
pub use self::geth::GethDirectory;
|
||||||
|
pub use self::memory::MemoryDirectory;
|
||||||
pub use self::parity::ParityDirectory;
|
pub use self::parity::ParityDirectory;
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ethkey::Address;
|
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||||
|
|
||||||
@ -68,7 +67,11 @@ impl KeyDirectory for ParityDirectory {
|
|||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
self.dir.remove(address)
|
self.dir.update(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,23 +16,19 @@
|
|||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use ethkey::KeyPair;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use crypto::KEY_ITERATIONS;
|
use crypto::KEY_ITERATIONS;
|
||||||
use random::Random;
|
use random::Random;
|
||||||
use ethkey::{Signature, Address, Message, Secret, Public};
|
use ethkey::{Signature, Address, Message, Secret, Public, KeyPair};
|
||||||
use dir::KeyDirectory;
|
use dir::KeyDirectory;
|
||||||
use account::SafeAccount;
|
use account::SafeAccount;
|
||||||
use {Error, SecretStore};
|
|
||||||
use json;
|
|
||||||
use json::Uuid;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use presale::PresaleWallet;
|
use presale::PresaleWallet;
|
||||||
use import;
|
use json::{self, Uuid};
|
||||||
|
use {import, Error, SimpleSecretStore, SecretStore};
|
||||||
|
|
||||||
pub struct EthStore {
|
pub struct EthStore {
|
||||||
dir: Box<KeyDirectory>,
|
store: EthMultiStore,
|
||||||
iterations: u32,
|
|
||||||
cache: RwLock<BTreeMap<Address, SafeAccount>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthStore {
|
impl EthStore {
|
||||||
@ -41,57 +37,46 @@ impl EthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
|
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
|
||||||
let accounts = try!(directory.load());
|
Ok(EthStore {
|
||||||
let cache = accounts.into_iter().map(|account| (account.address.clone(), account)).collect();
|
store: try!(EthMultiStore::open_with_iterations(directory, iterations)),
|
||||||
let store = EthStore {
|
})
|
||||||
dir: directory,
|
|
||||||
iterations: iterations,
|
|
||||||
cache: RwLock::new(cache),
|
|
||||||
};
|
|
||||||
Ok(store)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save(&self, account: SafeAccount) -> Result<(), Error> {
|
|
||||||
// save to file
|
|
||||||
let account = try!(self.dir.insert(account.clone()));
|
|
||||||
|
|
||||||
// update cache
|
|
||||||
let mut cache = self.cache.write();
|
|
||||||
cache.insert(account.address.clone(), account);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload_accounts(&self) -> Result<(), Error> {
|
|
||||||
let mut cache = self.cache.write();
|
|
||||||
let accounts = try!(self.dir.load());
|
|
||||||
let new_accounts: BTreeMap<_, _> = accounts.into_iter().map(|account| (account.address.clone(), account)).collect();
|
|
||||||
mem::replace(&mut *cache, new_accounts);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, address: &Address) -> Result<SafeAccount, Error> {
|
fn get(&self, address: &Address) -> Result<SafeAccount, Error> {
|
||||||
{
|
let mut accounts = try!(self.store.get(address)).into_iter();
|
||||||
let cache = self.cache.read();
|
accounts.next().ok_or(Error::InvalidAccount)
|
||||||
if let Some(account) = cache.get(address) {
|
|
||||||
return Ok(account.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try!(self.reload_accounts());
|
|
||||||
let cache = self.cache.read();
|
impl SimpleSecretStore for EthStore {
|
||||||
cache.get(address).cloned().ok_or(Error::InvalidAccount)
|
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
||||||
|
self.store.insert_account(secret, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accounts(&self) -> Result<Vec<Address>, Error> {
|
||||||
|
self.store.accounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
|
||||||
|
self.store.change_password(address, old_password, new_password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
||||||
|
self.store.remove_account(address, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, address: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||||
|
let account = try!(self.get(address));
|
||||||
|
account.sign(password, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let account = try!(self.get(account));
|
||||||
|
account.decrypt(password, shared_mac, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SecretStore for EthStore {
|
impl SecretStore for EthStore {
|
||||||
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
|
||||||
let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed));
|
|
||||||
let id: [u8; 16] = Random::random();
|
|
||||||
let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned());
|
|
||||||
let address = account.address.clone();
|
|
||||||
try!(self.save(account));
|
|
||||||
Ok(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error> {
|
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error> {
|
||||||
let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned())));
|
let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned())));
|
||||||
let wallet = PresaleWallet::from(json_wallet);
|
let wallet = PresaleWallet::from(json_wallet);
|
||||||
@ -105,48 +90,20 @@ impl SecretStore for EthStore {
|
|||||||
let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword));
|
let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword));
|
||||||
safe_account.address = try!(KeyPair::from_secret(secret)).address();
|
safe_account.address = try!(KeyPair::from_secret(secret)).address();
|
||||||
let address = safe_account.address.clone();
|
let address = safe_account.address.clone();
|
||||||
try!(self.save(safe_account));
|
try!(self.store.import(safe_account));
|
||||||
Ok(address)
|
Ok(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accounts(&self) -> Result<Vec<Address>, Error> {
|
fn test_password(&self, address: &Address, password: &str) -> Result<bool, Error> {
|
||||||
try!(self.reload_accounts());
|
let account = try!(self.get(address));
|
||||||
Ok(self.cache.read().keys().cloned().collect())
|
Ok(account.check_password(password))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
|
fn copy_account(&self, new_store: &SimpleSecretStore, address: &Address, password: &str, new_password: &str) -> Result<(), Error> {
|
||||||
// change password
|
|
||||||
let account = try!(self.get(address));
|
let account = try!(self.get(address));
|
||||||
let account = try!(account.change_password(old_password, new_password, self.iterations));
|
let secret = try!(account.crypto.secret(password));
|
||||||
|
try!(new_store.insert_account(secret, new_password));
|
||||||
// save to file
|
|
||||||
self.save(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
|
||||||
let can_remove = {
|
|
||||||
let account = try!(self.get(address));
|
|
||||||
account.check_password(password)
|
|
||||||
};
|
|
||||||
|
|
||||||
if can_remove {
|
|
||||||
try!(self.dir.remove(address));
|
|
||||||
let mut cache = self.cache.write();
|
|
||||||
cache.remove(address);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err(Error::InvalidPassword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign(&self, address: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
|
|
||||||
let account = try!(self.get(address));
|
|
||||||
account.sign(password, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
|
||||||
let account = try!(self.get(account));
|
|
||||||
account.decrypt(password, shared_mac, message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public(&self, account: &Address, password: &str) -> Result<Public, Error> {
|
fn public(&self, account: &Address, password: &str) -> Result<Public, Error> {
|
||||||
@ -170,23 +127,25 @@ impl SecretStore for EthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_name(&self, address: &Address, name: String) -> Result<(), Error> {
|
fn set_name(&self, address: &Address, name: String) -> Result<(), Error> {
|
||||||
let mut account = try!(self.get(address));
|
let old = try!(self.get(address));
|
||||||
|
let mut account = old.clone();
|
||||||
account.name = name;
|
account.name = name;
|
||||||
|
|
||||||
// save to file
|
// save to file
|
||||||
self.save(account)
|
self.store.update(old, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> {
|
fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> {
|
||||||
let mut account = try!(self.get(address));
|
let old = try!(self.get(address));
|
||||||
|
let mut account = old.clone();
|
||||||
account.meta = meta;
|
account.meta = meta;
|
||||||
|
|
||||||
// save to file
|
// save to file
|
||||||
self.save(account)
|
self.store.update(old, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_path(&self) -> String {
|
fn local_path(&self) -> String {
|
||||||
self.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new())
|
self.store.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
||||||
@ -194,6 +153,288 @@ impl SecretStore for EthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
||||||
import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet)
|
import::import_geth_accounts(&*self.store.dir, desired.into_iter().collect(), testnet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address`
|
||||||
|
pub struct EthMultiStore {
|
||||||
|
dir: Box<KeyDirectory>,
|
||||||
|
iterations: u32,
|
||||||
|
cache: RwLock<BTreeMap<Address, Vec<SafeAccount>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EthMultiStore {
|
||||||
|
|
||||||
|
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
|
||||||
|
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
|
||||||
|
let store = EthMultiStore {
|
||||||
|
dir: directory,
|
||||||
|
iterations: iterations,
|
||||||
|
cache: Default::default(),
|
||||||
|
};
|
||||||
|
try!(store.reload_accounts());
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_accounts(&self) -> Result<(), Error> {
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let accounts = try!(self.dir.load());
|
||||||
|
|
||||||
|
let mut new_accounts = BTreeMap::new();
|
||||||
|
for account in accounts {
|
||||||
|
let mut entry = new_accounts.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
entry.push(account);
|
||||||
|
}
|
||||||
|
mem::replace(&mut *cache, new_accounts);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, address: &Address) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
{
|
||||||
|
let cache = self.cache.read();
|
||||||
|
if let Some(accounts) = cache.get(address) {
|
||||||
|
if !accounts.is_empty() {
|
||||||
|
return Ok(accounts.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(self.reload_accounts());
|
||||||
|
let cache = self.cache.read();
|
||||||
|
let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount));
|
||||||
|
if accounts.is_empty() {
|
||||||
|
Err(Error::InvalidAccount)
|
||||||
|
} else {
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import(&self, account: SafeAccount) -> Result<(), Error> {
|
||||||
|
// save to file
|
||||||
|
let account = try!(self.dir.insert(account));
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
accounts.push(account);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, old: SafeAccount, new: SafeAccount) -> Result<(), Error> {
|
||||||
|
// save to file
|
||||||
|
let account = try!(self.dir.update(new));
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
// Remove old account
|
||||||
|
accounts.retain(|acc| acc != &old);
|
||||||
|
// And push updated to the end
|
||||||
|
accounts.push(account);
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleSecretStore for EthMultiStore {
|
||||||
|
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
||||||
|
let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed));
|
||||||
|
let id: [u8; 16] = Random::random();
|
||||||
|
let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned());
|
||||||
|
let address = account.address.clone();
|
||||||
|
try!(self.import(account));
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accounts(&self) -> Result<Vec<Address>, Error> {
|
||||||
|
try!(self.reload_accounts());
|
||||||
|
Ok(self.cache.read().keys().cloned().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
|
||||||
|
for account in accounts {
|
||||||
|
// Skip if password is invalid
|
||||||
|
if !account.check_password(password) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from dir
|
||||||
|
try!(self.dir.remove(&account));
|
||||||
|
|
||||||
|
// Remove from cache
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let is_empty = {
|
||||||
|
let mut accounts = cache.get_mut(address).expect("Entry exists, because it was returned by `get`; qed");
|
||||||
|
if let Some(position) = accounts.iter().position(|acc| acc == &account) {
|
||||||
|
accounts.remove(position);
|
||||||
|
}
|
||||||
|
accounts.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_empty {
|
||||||
|
cache.remove(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
for account in accounts {
|
||||||
|
// Change password
|
||||||
|
let new_account = try!(account.change_password(old_password, new_password, self.iterations));
|
||||||
|
try!(self.update(account, new_account));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, address: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
for account in accounts {
|
||||||
|
if account.check_password(password) {
|
||||||
|
return account.sign(password, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let accounts = try!(self.get(account));
|
||||||
|
for account in accounts {
|
||||||
|
if account.check_password(password) {
|
||||||
|
return account.decrypt(password, shared_mac, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use dir::MemoryDirectory;
|
||||||
|
use ethkey::{Random, Generator, KeyPair};
|
||||||
|
use secret_store::{SimpleSecretStore, SecretStore};
|
||||||
|
use super::{EthStore, EthMultiStore};
|
||||||
|
|
||||||
|
fn keypair() -> KeyPair {
|
||||||
|
Random.generate().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store() -> EthStore {
|
||||||
|
EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multi_store() -> EthMultiStore {
|
||||||
|
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_insert_account_successfully() {
|
||||||
|
// given
|
||||||
|
let store = store();
|
||||||
|
let keypair = keypair();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(address, keypair.address());
|
||||||
|
assert!(store.get(&address).is_ok(), "Should contain account.");
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 1, "Should have one account.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_update_meta_and_name() {
|
||||||
|
// given
|
||||||
|
let store = store();
|
||||||
|
let keypair = keypair();
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
assert_eq!(&store.meta(&address).unwrap(), "{}");
|
||||||
|
assert_eq!(&store.name(&address).unwrap(), "");
|
||||||
|
|
||||||
|
// when
|
||||||
|
store.set_meta(&address, "meta".into()).unwrap();
|
||||||
|
store.set_name(&address, "name".into()).unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(&store.meta(&address).unwrap(), "meta");
|
||||||
|
assert_eq!(&store.name(&address).unwrap(), "name");
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_remove_account() {
|
||||||
|
// given
|
||||||
|
let store = store();
|
||||||
|
let keypair = keypair();
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
store.remove_account(&address, "test").unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 0, "Should remove account.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_true_if_password_is_correct() {
|
||||||
|
// given
|
||||||
|
let store = store();
|
||||||
|
let keypair = keypair();
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
|
||||||
|
// when
|
||||||
|
let res1 = store.test_password(&address, "x").unwrap();
|
||||||
|
let res2 = store.test_password(&address, "test").unwrap();
|
||||||
|
|
||||||
|
assert!(!res1, "First password should be invalid.");
|
||||||
|
assert!(res2, "Second password should be correct.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multistore_should_be_able_to_have_the_same_account_twice() {
|
||||||
|
// given
|
||||||
|
let store = multi_store();
|
||||||
|
let keypair = keypair();
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
let address2 = store.insert_account(keypair.secret().clone(), "xyz").unwrap();
|
||||||
|
assert_eq!(address, address2);
|
||||||
|
|
||||||
|
// when
|
||||||
|
assert!(store.remove_account(&address, "test").is_ok(), "First password should work.");
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
||||||
|
|
||||||
|
assert!(store.remove_account(&address, "xyz").is_ok(), "Second password should work too.");
|
||||||
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_copy_account() {
|
||||||
|
// given
|
||||||
|
let store = store();
|
||||||
|
let multi_store = multi_store();
|
||||||
|
let keypair = keypair();
|
||||||
|
let address = store.insert_account(keypair.secret().clone(), "test").unwrap();
|
||||||
|
assert_eq!(multi_store.accounts().unwrap().len(), 0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
store.copy_account(&multi_store, &address, "test", "xyz").unwrap();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(store.test_password(&address, "test").unwrap(), "First password should work for store.");
|
||||||
|
assert!(multi_store.sign(&address, "xyz", &Default::default()).is_ok(), "Second password should work for second store.");
|
||||||
|
assert_eq!(multi_store.accounts().unwrap().len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -50,8 +50,8 @@ mod secret_store;
|
|||||||
|
|
||||||
pub use self::account::SafeAccount;
|
pub use self::account::SafeAccount;
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
pub use self::ethstore::EthStore;
|
pub use self::ethstore::{EthStore, EthMultiStore};
|
||||||
pub use self::import::{import_accounts, read_geth_accounts};
|
pub use self::import::{import_accounts, read_geth_accounts};
|
||||||
pub use self::presale::PresaleWallet;
|
pub use self::presale::PresaleWallet;
|
||||||
pub use self::secret_store::SecretStore;
|
pub use self::secret_store::{SimpleSecretStore, SecretStore};
|
||||||
pub use self::random::random_phrase;
|
pub use self::random::{random_phrase, random_string};
|
||||||
|
@ -51,10 +51,16 @@ pub fn random_phrase(words: usize) -> String {
|
|||||||
.map(|s| s.to_owned())
|
.map(|s| s.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
let mut rng = OsRng::new().unwrap();
|
let mut rng = OsRng::new().expect("Not able to operate without random source.");
|
||||||
(0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ")
|
(0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a random string of given length.
|
||||||
|
pub fn random_string(length: usize) -> String {
|
||||||
|
let mut rng = OsRng::new().expect("Not able to operate without random source.");
|
||||||
|
rng.gen_ascii_chars().take(length).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::random_phrase;
|
use super::random_phrase;
|
||||||
|
@ -18,18 +18,25 @@ use ethkey::{Address, Message, Signature, Secret, Public};
|
|||||||
use Error;
|
use Error;
|
||||||
use json::Uuid;
|
use json::Uuid;
|
||||||
|
|
||||||
pub trait SecretStore: Send + Sync {
|
pub trait SimpleSecretStore: Send + Sync {
|
||||||
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
|
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
|
||||||
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error>;
|
|
||||||
fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error>;
|
|
||||||
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;
|
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;
|
||||||
fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>;
|
fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>;
|
||||||
|
|
||||||
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
|
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
|
||||||
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
|
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
|
||||||
fn public(&self, account: &Address, password: &str) -> Result<Public, Error>;
|
|
||||||
|
|
||||||
fn accounts(&self) -> Result<Vec<Address>, Error>;
|
fn accounts(&self) -> Result<Vec<Address>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SecretStore: SimpleSecretStore {
|
||||||
|
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error>;
|
||||||
|
fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error>;
|
||||||
|
fn copy_account(&self, new_store: &SimpleSecretStore, account: &Address, password: &str, new_password: &str) -> Result<(), Error>;
|
||||||
|
fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error>;
|
||||||
|
|
||||||
|
fn public(&self, account: &Address, password: &str) -> Result<Public, Error>;
|
||||||
|
|
||||||
fn uuid(&self, account: &Address) -> Result<Uuid, Error>;
|
fn uuid(&self, account: &Address) -> Result<Uuid, Error>;
|
||||||
fn name(&self, account: &Address) -> Result<String, Error>;
|
fn name(&self, account: &Address) -> Result<String, Error>;
|
||||||
fn meta(&self, account: &Address) -> Result<String, Error>;
|
fn meta(&self, account: &Address) -> Result<String, Error>;
|
||||||
|
@ -19,7 +19,7 @@ extern crate ethstore;
|
|||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use ethstore::{SecretStore, EthStore};
|
use ethstore::{EthStore, SimpleSecretStore};
|
||||||
use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address};
|
use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address};
|
||||||
use ethstore::dir::DiskDirectory;
|
use ethstore::dir::DiskDirectory;
|
||||||
use util::TransientDir;
|
use util::TransientDir;
|
||||||
|
@ -18,7 +18,6 @@ use std::path::PathBuf;
|
|||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
use rand::{Rng, OsRng};
|
use rand::{Rng, OsRng};
|
||||||
use ethstore::dir::{KeyDirectory, DiskDirectory};
|
use ethstore::dir::{KeyDirectory, DiskDirectory};
|
||||||
use ethstore::ethkey::Address;
|
|
||||||
use ethstore::{Error, SafeAccount};
|
use ethstore::{Error, SafeAccount};
|
||||||
|
|
||||||
pub fn random_dir() -> PathBuf {
|
pub fn random_dir() -> PathBuf {
|
||||||
@ -64,11 +63,15 @@ impl KeyDirectory for TransientDir {
|
|||||||
self.dir.load()
|
self.dir.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
|
self.dir.update(account)
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(address)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
js/.stylelintrc.json
Normal file
8
js/.stylelintrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "stylelint-config-standard",
|
||||||
|
"rules": {
|
||||||
|
"selector-pseudo-class-no-unknown": [
|
||||||
|
true, { "ignorePseudoClasses": ["global"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.2.119",
|
"version": "0.2.127",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -38,8 +38,11 @@
|
|||||||
"start:app": "node webpack/dev.server",
|
"start:app": "node webpack/dev.server",
|
||||||
"clean": "rm -rf ./build ./coverage",
|
"clean": "rm -rf ./build ./coverage",
|
||||||
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
|
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
|
||||||
"lint": "eslint --ignore-path .gitignore ./src/",
|
"lint": "npm run lint:css && npm run lint:js",
|
||||||
"lint:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
"lint:cached": "npm run lint:css && npm run lint:js:cached",
|
||||||
|
"lint:css": "stylelint ./src/**/*.css",
|
||||||
|
"lint:js": "eslint --ignore-path .gitignore ./src/",
|
||||||
|
"lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
||||||
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
||||||
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
||||||
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
||||||
@ -118,6 +121,8 @@
|
|||||||
"sinon-as-promised": "4.0.2",
|
"sinon-as-promised": "4.0.2",
|
||||||
"sinon-chai": "2.8.0",
|
"sinon-chai": "2.8.0",
|
||||||
"style-loader": "0.13.1",
|
"style-loader": "0.13.1",
|
||||||
|
"stylelint": "7.6.0",
|
||||||
|
"stylelint-config-standard": "15.0.0",
|
||||||
"url-loader": "0.5.7",
|
"url-loader": "0.5.7",
|
||||||
"webpack": "2.1.0-beta.27",
|
"webpack": "2.1.0-beta.27",
|
||||||
"webpack-dev-middleware": "1.8.4",
|
"webpack-dev-middleware": "1.8.4",
|
||||||
|
33
js/src/3rdparty/email-verification/index.js
vendored
Normal file
33
js/src/3rdparty/email-verification/index.js
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 { stringify } from 'querystring';
|
||||||
|
|
||||||
|
export const postToServer = (query, isTestnet = false) => {
|
||||||
|
const port = isTestnet ? 28443 : 18443;
|
||||||
|
query = stringify(query);
|
||||||
|
return fetch(`https://email-verification.parity.io:${port}/?` + query, {
|
||||||
|
method: 'POST', mode: 'cors', cache: 'no-store'
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return res.json().then((data) => {
|
||||||
|
if (res.ok) {
|
||||||
|
return data.message;
|
||||||
|
}
|
||||||
|
throw new Error(data.message || 'unknown error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
23
js/src/3rdparty/email-verification/terms-of-service.js
vendored
Normal file
23
js/src/3rdparty/email-verification/terms-of-service.js
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
<ul>
|
||||||
|
<li>todo</li>
|
||||||
|
</ul>
|
||||||
|
);
|
11
js/src/3rdparty/sms-verification/index.js
vendored
11
js/src/3rdparty/sms-verification/index.js
vendored
@ -15,17 +15,6 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { stringify } from 'querystring';
|
import { stringify } from 'querystring';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export const termsOfService = (
|
|
||||||
<ul>
|
|
||||||
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
|
|
||||||
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
|
|
||||||
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
|
|
||||||
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
|
|
||||||
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const postToServer = (query, isTestnet = false) => {
|
export const postToServer = (query, isTestnet = false) => {
|
||||||
const port = isTestnet ? 8443 : 443;
|
const port = isTestnet ? 8443 : 443;
|
||||||
|
27
js/src/3rdparty/sms-verification/terms-of-service.js
vendored
Normal file
27
js/src/3rdparty/sms-verification/terms-of-service.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
<ul>
|
||||||
|
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
|
||||||
|
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
|
||||||
|
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
|
||||||
|
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
|
||||||
|
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
|
||||||
|
</ul>
|
||||||
|
);
|
@ -342,7 +342,8 @@ export default class Contract {
|
|||||||
options: _options,
|
options: _options,
|
||||||
autoRemove,
|
autoRemove,
|
||||||
callback,
|
callback,
|
||||||
filterId
|
filterId,
|
||||||
|
id: subscriptionId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (skipInitFetch) {
|
if (skipInitFetch) {
|
||||||
@ -452,13 +453,13 @@ export default class Contract {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then((logsArray) => {
|
.then((logsArray) => {
|
||||||
logsArray.forEach((logs, subscriptionId) => {
|
logsArray.forEach((logs, index) => {
|
||||||
if (!logs || !logs.length) {
|
if (!logs || !logs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.sendData(subscriptionId, null, this.parseEventLogs(logs));
|
this._sendData(subscriptions[index].id, null, this.parseEventLogs(logs));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('_sendSubscriptionChanges', error);
|
console.error('_sendSubscriptionChanges', error);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,28 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import blockies from 'blockies';
|
import blockies from 'blockies';
|
||||||
|
|
||||||
|
// jsdom doesn't have all the browser features, blockies fail
|
||||||
|
const TEST_ENV = process.env.NODE_ENV === 'test';
|
||||||
|
|
||||||
export function createIdentityImg (address, scale = 8) {
|
export function createIdentityImg (address, scale = 8) {
|
||||||
return blockies({
|
return TEST_ENV
|
||||||
|
? ''
|
||||||
|
: blockies({
|
||||||
seed: (address || '').toLowerCase(),
|
seed: (address || '').toLowerCase(),
|
||||||
size: 8,
|
size: 8,
|
||||||
scale
|
scale
|
||||||
|
1
js/src/contracts/abi/email-verification.json
Normal file
1
js/src/contracts/abi/email-verification.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","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":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"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":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"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"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]
|
@ -19,6 +19,7 @@ import basiccoin from './basiccoin.json';
|
|||||||
import basiccoinmanager from './basiccoinmanager.json';
|
import basiccoinmanager from './basiccoinmanager.json';
|
||||||
import dappreg from './dappreg.json';
|
import dappreg from './dappreg.json';
|
||||||
import eip20 from './eip20.json';
|
import eip20 from './eip20.json';
|
||||||
|
import emailverification from './email-verification.json';
|
||||||
import gavcoin from './gavcoin.json';
|
import gavcoin from './gavcoin.json';
|
||||||
import githubhint from './githubhint.json';
|
import githubhint from './githubhint.json';
|
||||||
import owned from './owned.json';
|
import owned from './owned.json';
|
||||||
@ -34,6 +35,7 @@ export {
|
|||||||
basiccoinmanager,
|
basiccoinmanager,
|
||||||
dappreg,
|
dappreg,
|
||||||
eip20,
|
eip20,
|
||||||
|
emailverification,
|
||||||
gavcoin,
|
gavcoin,
|
||||||
githubhint,
|
githubhint,
|
||||||
owned,
|
owned,
|
||||||
|
@ -18,7 +18,8 @@ import { bytesToHex, hex2Ascii } from '~/api/util/format';
|
|||||||
|
|
||||||
import ABI from './abi/certifier.json';
|
import ABI from './abi/certifier.json';
|
||||||
|
|
||||||
const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
const ZERO20 = '0x0000000000000000000000000000000000000000';
|
||||||
|
const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
|
||||||
export default class BadgeReg {
|
export default class BadgeReg {
|
||||||
constructor (api, registry) {
|
constructor (api, registry) {
|
||||||
@ -26,32 +27,57 @@ export default class BadgeReg {
|
|||||||
this._registry = registry;
|
this._registry = registry;
|
||||||
|
|
||||||
registry.getContract('badgereg');
|
registry.getContract('badgereg');
|
||||||
this.certifiers = {}; // by name
|
this.certifiers = []; // by id
|
||||||
this.contracts = {}; // by name
|
this.contracts = {}; // by name
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCertifier (name) {
|
certifierCount () {
|
||||||
if (this.certifiers[name]) {
|
return this._registry.getContract('badgereg')
|
||||||
return Promise.resolve(this.certifiers[name]);
|
.then((badgeReg) => {
|
||||||
|
return badgeReg.instance.badgeCount.call({}, [])
|
||||||
|
.then((count) => count.valueOf());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCertifier (id) {
|
||||||
|
if (this.certifiers[id]) {
|
||||||
|
return Promise.resolve(this.certifiers[id]);
|
||||||
}
|
}
|
||||||
return this._registry.getContract('badgereg')
|
return this._registry.getContract('badgereg')
|
||||||
.then((badgeReg) => {
|
.then((badgeReg) => {
|
||||||
return badgeReg.instance.fromName.call({}, [name])
|
return badgeReg.instance.badge.call({}, [ id ]);
|
||||||
.then(([ id, address ]) => {
|
})
|
||||||
return Promise.all([
|
.then(([ address, name ]) => {
|
||||||
badgeReg.instance.meta.call({}, [id, 'TITLE']),
|
if (address === ZERO20) {
|
||||||
badgeReg.instance.meta.call({}, [id, 'IMG'])
|
throw new Error(`Certifier ${id} does not exist.`);
|
||||||
])
|
}
|
||||||
.then(([ title, img ]) => {
|
|
||||||
title = bytesToHex(title);
|
|
||||||
title = title === ZERO ? null : hex2Ascii(title);
|
|
||||||
if (bytesToHex(img) === ZERO) img = null;
|
|
||||||
|
|
||||||
const data = { address, name, title, icon: img };
|
name = bytesToHex(name);
|
||||||
this.certifiers[name] = data;
|
name = name === ZERO32
|
||||||
|
? null
|
||||||
|
: hex2Ascii(name);
|
||||||
|
return this.fetchMeta(id)
|
||||||
|
.then(({ title, icon }) => {
|
||||||
|
const data = { address, id, name, title, icon };
|
||||||
|
this.certifiers[id] = data;
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMeta (id) {
|
||||||
|
return this._registry.getContract('badgereg')
|
||||||
|
.then((badgeReg) => {
|
||||||
|
return Promise.all([
|
||||||
|
badgeReg.instance.meta.call({}, [id, 'TITLE']),
|
||||||
|
badgeReg.instance.meta.call({}, [id, 'IMG'])
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
.then(([ title, icon ]) => {
|
||||||
|
title = bytesToHex(title);
|
||||||
|
title = title === ZERO32 ? null : hex2Ascii(title);
|
||||||
|
if (bytesToHex(icon) === ZERO32) icon = null;
|
||||||
|
return { title, icon };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import Registry from './registry';
|
|||||||
import SignatureReg from './signaturereg';
|
import SignatureReg from './signaturereg';
|
||||||
import TokenReg from './tokenreg';
|
import TokenReg from './tokenreg';
|
||||||
import GithubHint from './githubhint';
|
import GithubHint from './githubhint';
|
||||||
import * as smsVerification from './sms-verification';
|
import * as verification from './verification';
|
||||||
import BadgeReg from './badgereg';
|
import BadgeReg from './badgereg';
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
@ -58,7 +58,11 @@ export default class Contracts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get smsVerification () {
|
get smsVerification () {
|
||||||
return smsVerification;
|
return verification;
|
||||||
|
}
|
||||||
|
|
||||||
|
get emailVerification () {
|
||||||
|
return verification;
|
||||||
}
|
}
|
||||||
|
|
||||||
static create (api) {
|
static create (api) {
|
||||||
|
@ -14,4 +14,5 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './SMSVerification';
|
import './parity';
|
||||||
|
import './web3';
|
@ -58,23 +58,23 @@ export default class DetailsStep extends Component {
|
|||||||
<Form>
|
<Form>
|
||||||
{ this.renderWarning() }
|
{ this.renderWarning() }
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
label='from account'
|
|
||||||
hint='the account to transact with'
|
|
||||||
value={ fromAddress }
|
|
||||||
error={ fromAddressError }
|
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
onChange={ onFromAddressChange } />
|
error={ fromAddressError }
|
||||||
|
hint='the account to transact with'
|
||||||
|
label='from account'
|
||||||
|
onChange={ onFromAddressChange }
|
||||||
|
value={ fromAddress } />
|
||||||
{ this.renderFunctionSelect() }
|
{ this.renderFunctionSelect() }
|
||||||
{ this.renderParameters() }
|
{ this.renderParameters() }
|
||||||
<div className={ styles.columns }>
|
<div className={ styles.columns }>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label='transaction value (in ETH)'
|
|
||||||
hint='the amount to send to with the transaction'
|
|
||||||
value={ amount }
|
|
||||||
error={ amountError }
|
error={ amountError }
|
||||||
onSubmit={ onAmountChange } />
|
hint='the amount to send to with the transaction'
|
||||||
|
label='transaction value (in ETH)'
|
||||||
|
onSubmit={ onAmountChange }
|
||||||
|
value={ amount } />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import { ContextProvider, muiTheme } from '~/ui';
|
||||||
|
|
||||||
|
import DetailsStep from './';
|
||||||
|
|
||||||
|
import { CONTRACT } from '../executeContract.test.js';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let onAmountChange;
|
||||||
|
let onClose;
|
||||||
|
let onFromAddressChange;
|
||||||
|
let onFuncChange;
|
||||||
|
let onGasEditClick;
|
||||||
|
let onValueChange;
|
||||||
|
|
||||||
|
function render (props) {
|
||||||
|
onAmountChange = sinon.stub();
|
||||||
|
onClose = sinon.stub();
|
||||||
|
onFromAddressChange = sinon.stub();
|
||||||
|
onFuncChange = sinon.stub();
|
||||||
|
onGasEditClick = sinon.stub();
|
||||||
|
onValueChange = sinon.stub();
|
||||||
|
|
||||||
|
component = mount(
|
||||||
|
<ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }>
|
||||||
|
<DetailsStep
|
||||||
|
{ ...props }
|
||||||
|
contract={ CONTRACT }
|
||||||
|
onAmountChange={ onAmountChange }
|
||||||
|
onClose={ onClose }
|
||||||
|
onFromAddressChange={ onFromAddressChange }
|
||||||
|
onFuncChange={ onFuncChange }
|
||||||
|
onGasEditClick={ onGasEditClick }
|
||||||
|
onValueChange={ onValueChange } />
|
||||||
|
</ContextProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/ExecuteContract/DetailsStep', () => {
|
||||||
|
it('renders', () => {
|
||||||
|
expect(render({ accounts: {}, values: [ true ], valuesError: [ null ] })).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parameter values', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render({
|
||||||
|
accounts: {},
|
||||||
|
func: CONTRACT.functions[0],
|
||||||
|
values: [ false ],
|
||||||
|
valuesError: [ null ]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bool parameters', () => {
|
||||||
|
it('toggles from false to true', () => {
|
||||||
|
component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } });
|
||||||
|
|
||||||
|
expect(onValueChange).to.have.been.calledWith(null, 0, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -25,6 +25,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear';
|
|||||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
|
|
||||||
|
import { toWei } from '~/api/util/wei';
|
||||||
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
|
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
|
||||||
import { MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
import { validateAddress, validateUint } from '~/util/validation';
|
import { validateAddress, validateUint } from '~/util/validation';
|
||||||
@ -56,12 +57,12 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isTest: PropTypes.bool,
|
|
||||||
fromAddress: PropTypes.string,
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
contract: PropTypes.object,
|
contract: PropTypes.object.isRequired,
|
||||||
|
fromAddress: PropTypes.string,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
|
isTest: PropTypes.bool,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onFromAddressChange: PropTypes.func.isRequired
|
onFromAddressChange: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
@ -77,11 +78,11 @@ class ExecuteContract extends Component {
|
|||||||
funcError: null,
|
funcError: null,
|
||||||
gasEdit: false,
|
gasEdit: false,
|
||||||
rejected: false,
|
rejected: false,
|
||||||
step: STEP_DETAILS,
|
|
||||||
sending: false,
|
sending: false,
|
||||||
|
step: STEP_DETAILS,
|
||||||
|
txhash: null,
|
||||||
values: [],
|
values: [],
|
||||||
valuesError: [],
|
valuesError: []
|
||||||
txhash: null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -255,10 +256,6 @@ class ExecuteContract extends Component {
|
|||||||
valueError = validateAddress(_value).addressError;
|
valueError = validateAddress(_value).addressError;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'bool':
|
|
||||||
value = _value === 'true';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'uint':
|
case 'uint':
|
||||||
valueError = validateUint(_value).valueError;
|
valueError = validateUint(_value).valueError;
|
||||||
break;
|
break;
|
||||||
@ -278,13 +275,12 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
estimateGas = (_fromAddress) => {
|
estimateGas = (_fromAddress) => {
|
||||||
const { api } = this.context;
|
|
||||||
const { fromAddress } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { amount, func, values } = this.state;
|
const { amount, func, values } = this.state;
|
||||||
const options = {
|
const options = {
|
||||||
gas: MAX_GAS_ESTIMATION,
|
gas: MAX_GAS_ESTIMATION,
|
||||||
from: _fromAddress || fromAddress,
|
from: _fromAddress || fromAddress,
|
||||||
value: api.util.toWei(amount || 0)
|
value: toWei(amount || 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!func) {
|
if (!func) {
|
||||||
|
69
js/src/modals/ExecuteContract/executeContract.spec.js
Normal file
69
js/src/modals/ExecuteContract/executeContract.spec.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import ExecuteContract from './';
|
||||||
|
|
||||||
|
import { CONTRACT, STORE } from './executeContract.test.js';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let onClose;
|
||||||
|
let onFromAddressChange;
|
||||||
|
|
||||||
|
function render (props) {
|
||||||
|
onClose = sinon.stub();
|
||||||
|
onFromAddressChange = sinon.stub();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<ExecuteContract
|
||||||
|
{ ...props }
|
||||||
|
contract={ CONTRACT }
|
||||||
|
onClose={ onClose }
|
||||||
|
onFromAddressChange={ onFromAddressChange } />,
|
||||||
|
{ context: { api: {}, store: STORE } }
|
||||||
|
).find('ExecuteContract').shallow();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/ExecuteContract/DetailsStep', () => {
|
||||||
|
it('renders', () => {
|
||||||
|
expect(render({ accounts: {} })).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance functions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render({
|
||||||
|
accounts: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onValueChange', () => {
|
||||||
|
it('toggles boolean from false to true', () => {
|
||||||
|
component.setState({
|
||||||
|
func: CONTRACT.functions[0],
|
||||||
|
values: [false]
|
||||||
|
});
|
||||||
|
component.instance().onValueChange(null, 0, true);
|
||||||
|
|
||||||
|
expect(component.state().values).to.deep.equal([true]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
64
js/src/modals/ExecuteContract/executeContract.test.js
Normal file
64
js/src/modals/ExecuteContract/executeContract.test.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
const CONTRACT = {
|
||||||
|
functions: [
|
||||||
|
{
|
||||||
|
name: 'test_a',
|
||||||
|
signature: 'test_a',
|
||||||
|
estimateGas: sinon.stub().resolves(new BigNumber(123)),
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'test_bool',
|
||||||
|
kind: {
|
||||||
|
type: 'bool'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
abi: {
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'test_bool',
|
||||||
|
type: 'bool'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const STORE = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
balances: {
|
||||||
|
balances: {}
|
||||||
|
},
|
||||||
|
nodeStatus: {
|
||||||
|
gasLimit: new BigNumber(123)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
CONTRACT,
|
||||||
|
STORE
|
||||||
|
};
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import ActionDone from 'material-ui/svg-icons/action/done';
|
import ActionDone from 'material-ui/svg-icons/action/done';
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
@ -35,14 +36,15 @@ import ParityLogo from '../../../assets/images/parity-logo-black-no-text.svg';
|
|||||||
|
|
||||||
const STAGE_NAMES = ['welcome', 'terms', 'new account', 'recovery', 'completed'];
|
const STAGE_NAMES = ['welcome', 'terms', 'new account', 'recovery', 'completed'];
|
||||||
|
|
||||||
export default class FirstRun extends Component {
|
class FirstRun extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
visible: PropTypes.bool,
|
hasAccounts: PropTypes.bool.isRequired,
|
||||||
|
visible: PropTypes.bool.isRequired,
|
||||||
onClose: PropTypes.func.isRequired
|
onClose: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +111,7 @@ export default class FirstRun extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
|
const { hasAccounts } = this.props;
|
||||||
const { canCreate, stage, hasAcceptedTnc } = this.state;
|
const { canCreate, stage, hasAcceptedTnc } = this.state;
|
||||||
|
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
@ -130,13 +133,26 @@ export default class FirstRun extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
icon={ <ActionDone /> }
|
icon={ <ActionDone /> }
|
||||||
label='Create'
|
label='Create'
|
||||||
|
key='create'
|
||||||
disabled={ !canCreate }
|
disabled={ !canCreate }
|
||||||
onClick={ this.onCreate } />
|
onClick={ this.onCreate }
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
if (hasAccounts) {
|
||||||
|
buttons.unshift(
|
||||||
|
<Button
|
||||||
|
icon={ <NavigationArrowForward /> }
|
||||||
|
label='Skip'
|
||||||
|
key='skip'
|
||||||
|
onClick={ this.skipAccountCreation }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
return [
|
return [
|
||||||
@ -219,6 +235,10 @@ export default class FirstRun extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipAccountCreation = () => {
|
||||||
|
this.setState({ stage: this.state.stage + 2 });
|
||||||
|
}
|
||||||
|
|
||||||
newError = (error) => {
|
newError = (error) => {
|
||||||
const { store } = this.context;
|
const { store } = this.context;
|
||||||
|
|
||||||
@ -232,3 +252,9 @@ export default class FirstRun extends Component {
|
|||||||
print(recoveryPage({ phrase, name, identity, address, logo: ParityLogo }));
|
print(recoveryPage({ phrase, name, identity, address, logo: ParityLogo }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return { hasAccounts: state.personal.hasAccounts };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(FirstRun);
|
||||||
|
@ -15,10 +15,6 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.list li {
|
|
||||||
padding: .1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacing {
|
.spacing {
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
}
|
}
|
@ -25,41 +25,33 @@ import { fromWei } from '~/api/util/wei';
|
|||||||
import { Form, Input } from '~/ui';
|
import { Form, Input } from '~/ui';
|
||||||
import { nullableProptype } from '~/util/proptypes';
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import { termsOfService } from '../../../3rdparty/sms-verification';
|
import smsTermsOfService from '~/3rdparty/sms-verification/terms-of-service';
|
||||||
|
import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service';
|
||||||
|
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
|
||||||
import styles from './gatherData.css';
|
import styles from './gatherData.css';
|
||||||
|
|
||||||
export default class GatherData extends Component {
|
export default class GatherData extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
fee: React.PropTypes.instanceOf(BigNumber),
|
fee: React.PropTypes.instanceOf(BigNumber),
|
||||||
isNumberValid: PropTypes.bool.isRequired,
|
method: PropTypes.string.isRequired,
|
||||||
|
fields: PropTypes.array.isRequired,
|
||||||
isVerified: nullableProptype(PropTypes.bool.isRequired),
|
isVerified: nullableProptype(PropTypes.bool.isRequired),
|
||||||
hasRequested: nullableProptype(PropTypes.bool.isRequired),
|
hasRequested: nullableProptype(PropTypes.bool.isRequired),
|
||||||
setNumber: PropTypes.func.isRequired,
|
|
||||||
setConsentGiven: PropTypes.func.isRequired
|
setConsentGiven: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isNumberValid, isVerified } = this.props;
|
const { method, isVerified } = this.props;
|
||||||
|
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
|
||||||
|
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<p>The following steps will let you prove that you control both an account and a phone number.</p>
|
{ howItWorks }
|
||||||
<ol className={ styles.list }>
|
|
||||||
<li>You send a verification request to a specific contract.</li>
|
|
||||||
<li>Our server puts a puzzle into this contract.</li>
|
|
||||||
<li>The code you receive via SMS is the solution to this puzzle.</li>
|
|
||||||
</ol>
|
|
||||||
{ this.renderFee() }
|
{ this.renderFee() }
|
||||||
{ this.renderCertified() }
|
{ this.renderCertified() }
|
||||||
{ this.renderRequested() }
|
{ this.renderRequested() }
|
||||||
<Input
|
{ this.renderFields() }
|
||||||
label={ 'phone number in international format' }
|
|
||||||
hint={ 'the SMS will be sent to this number' }
|
|
||||||
error={ isNumberValid ? null : 'invalid number' }
|
|
||||||
disabled={ isVerified }
|
|
||||||
onChange={ this.numberOnChange }
|
|
||||||
onSubmit={ this.numberOnSubmit }
|
|
||||||
/>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className={ styles.spacing }
|
className={ styles.spacing }
|
||||||
label={ 'I agree to the terms and conditions below.' }
|
label={ 'I agree to the terms and conditions below.' }
|
||||||
@ -136,12 +128,28 @@ export default class GatherData extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
numberOnSubmit = (value) => {
|
renderFields () {
|
||||||
this.props.setNumber(value);
|
const { isVerified, fields } = this.props;
|
||||||
}
|
|
||||||
|
|
||||||
numberOnChange = (_, value) => {
|
const rendered = fields.map((field) => {
|
||||||
this.props.setNumber(value);
|
const onChange = (_, v) => {
|
||||||
|
field.onChange(v);
|
||||||
|
};
|
||||||
|
const onSubmit = field.onChange;
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key={ field.key }
|
||||||
|
label={ field.label }
|
||||||
|
hint={ field.hint }
|
||||||
|
error={ field.error }
|
||||||
|
disabled={ isVerified }
|
||||||
|
onChange={ onChange }
|
||||||
|
onSubmit={ onSubmit }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<div>{rendered}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
consentOnChange = (_, consentGiven) => {
|
consentOnChange = (_, consentGiven) => {
|
@ -20,20 +20,25 @@ import { Form, Input } from '~/ui';
|
|||||||
|
|
||||||
export default class QueryCode extends Component {
|
export default class QueryCode extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
number: PropTypes.string.isRequired,
|
receiver: PropTypes.string.isRequired,
|
||||||
|
hint: PropTypes.string,
|
||||||
isCodeValid: PropTypes.bool.isRequired,
|
isCodeValid: PropTypes.bool.isRequired,
|
||||||
setCode: PropTypes.func.isRequired
|
setCode: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
hint: 'Enter the code you received.'
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { number, isCodeValid } = this.props;
|
const { receiver, hint, isCodeValid } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<p>The verification code has been sent to { number }.</p>
|
<p>The verification code has been sent to { receiver }.</p>
|
||||||
<Input
|
<Input
|
||||||
label={ 'verification code' }
|
label={ 'verification code' }
|
||||||
hint={ 'Enter the code you received via SMS.' }
|
hint={ hint }
|
||||||
error={ isCodeValid ? null : 'invalid code' }
|
error={ isCodeValid ? null : 'invalid code' }
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
onSubmit={ this.onSubmit }
|
onSubmit={ this.onSubmit }
|
@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { nullableProptype } from '~/util/proptypes';
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
import TxHash from '~/ui/TxHash';
|
import TxHash from '~/ui/TxHash';
|
||||||
import {
|
import {
|
||||||
POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS
|
POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE
|
||||||
} from '../store';
|
} from '../store';
|
||||||
|
|
||||||
import styles from './sendRequest.css';
|
import styles from './sendRequest.css';
|
||||||
@ -45,9 +45,9 @@ export default class SendRequest extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case REQUESTING_SMS:
|
case REQUESTING_CODE:
|
||||||
return (
|
return (
|
||||||
<p>Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.</p>
|
<p>Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
72
js/src/modals/Verification/email-store.js
Normal file
72
js/src/modals/Verification/email-store.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 { observable, computed, action } from 'mobx';
|
||||||
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
|
||||||
|
import EmailVerificationABI from '~/contracts/abi/email-verification.json';
|
||||||
|
import VerificationStore, {
|
||||||
|
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
|
||||||
|
} from './store';
|
||||||
|
import { postToServer } from '../../3rdparty/email-verification';
|
||||||
|
|
||||||
|
const EMAIL_VERIFICATION = 4; // id in the `BadgeReg.sol` contract
|
||||||
|
|
||||||
|
export default class EmailVerificationStore extends VerificationStore {
|
||||||
|
@observable email = '';
|
||||||
|
|
||||||
|
@computed get isEmailValid () {
|
||||||
|
// See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/
|
||||||
|
return this.email && this.email.indexOf('@') >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isStepValid () {
|
||||||
|
if (this.step === DONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.step) {
|
||||||
|
case LOADING:
|
||||||
|
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
|
||||||
|
case QUERY_DATA:
|
||||||
|
return this.isEmailValid && this.consentGiven;
|
||||||
|
case QUERY_CODE:
|
||||||
|
return this.requestTx && this.isCodeValid === true;
|
||||||
|
case POSTED_CONFIRMATION:
|
||||||
|
return !!this.confirmationTx;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (api, account, isTestnet) {
|
||||||
|
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestValues = () => [ sha3(this.email) ]
|
||||||
|
|
||||||
|
@action setEmail = (email) => {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCode = () => {
|
||||||
|
const { email, account, isTestnet } = this;
|
||||||
|
return postToServer({ email, address: account }, isTestnet);
|
||||||
|
}
|
||||||
|
}
|
41
js/src/modals/Verification/how-it-works.js
Normal file
41
js/src/modals/Verification/how-it-works.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './verification.css';
|
||||||
|
|
||||||
|
export const howSMSVerificationWorks = (
|
||||||
|
<div>
|
||||||
|
<p>The following steps will let you prove that you control both an account and a phone number.</p>
|
||||||
|
<ol className={ styles.list }>
|
||||||
|
<li>You send a verification request to a specific contract.</li>
|
||||||
|
<li>Our server puts a puzzle into this contract.</li>
|
||||||
|
<li>The code you receive via SMS is the solution to this puzzle.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const howEmailVerificationWorks = (
|
||||||
|
<div>
|
||||||
|
<p>The following steps will let you prove that you control both an account and an e-mail address.</p>
|
||||||
|
<ol className={ styles.list }>
|
||||||
|
<li>You send a verification request to a specific contract.</li>
|
||||||
|
<li>Our server puts a puzzle into this contract.</li>
|
||||||
|
<li>The code you receive via e-mail is the solution to this puzzle.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
17
js/src/modals/Verification/index.js
Normal file
17
js/src/modals/Verification/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './verification';
|
69
js/src/modals/Verification/sms-store.js
Normal file
69
js/src/modals/Verification/sms-store.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 { observable, computed, action } from 'mobx';
|
||||||
|
import phone from 'phoneformat.js';
|
||||||
|
|
||||||
|
import SMSVerificationABI from '~/contracts/abi/sms-verification.json';
|
||||||
|
import VerificationStore, {
|
||||||
|
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
|
||||||
|
} from './store';
|
||||||
|
import { postToServer } from '../../3rdparty/sms-verification';
|
||||||
|
|
||||||
|
const SMS_VERIFICATION = 0; // id in the `BadgeReg.sol` contract
|
||||||
|
|
||||||
|
export default class SMSVerificationStore extends VerificationStore {
|
||||||
|
@observable number = '';
|
||||||
|
|
||||||
|
@computed get isNumberValid () {
|
||||||
|
return phone.isValidNumber(this.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isStepValid () {
|
||||||
|
if (this.step === DONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.step) {
|
||||||
|
case LOADING:
|
||||||
|
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
|
||||||
|
case QUERY_DATA:
|
||||||
|
return this.isNumberValid && this.consentGiven;
|
||||||
|
case QUERY_CODE:
|
||||||
|
return this.requestTx && this.isCodeValid === true;
|
||||||
|
case POSTED_CONFIRMATION:
|
||||||
|
return !!this.confirmationTx;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (api, account, isTestnet) {
|
||||||
|
super(api, SMSVerificationABI, SMS_VERIFICATION, account, isTestnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNumber = (number) => {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCode = () => {
|
||||||
|
const { number, account, isTestnet } = this;
|
||||||
|
return postToServer({ number, address: account }, isTestnet);
|
||||||
|
}
|
||||||
|
}
|
@ -14,21 +14,19 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { observable, computed, autorun, action } from 'mobx';
|
import { observable, autorun, action } from 'mobx';
|
||||||
import phone from 'phoneformat.js';
|
|
||||||
import { sha3 } from '~/api/util/sha3';
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
import Contract from '~/api/contract';
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
|
|
||||||
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification';
|
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification';
|
||||||
import { postToServer } from '~/3rdparty/sms-verification';
|
|
||||||
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
|
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
|
||||||
|
|
||||||
export const LOADING = 'fetching-contract';
|
export const LOADING = 'fetching-contract';
|
||||||
export const QUERY_DATA = 'query-data';
|
export const QUERY_DATA = 'query-data';
|
||||||
export const POSTING_REQUEST = 'posting-request';
|
export const POSTING_REQUEST = 'posting-request';
|
||||||
export const POSTED_REQUEST = 'posted-request';
|
export const POSTED_REQUEST = 'posted-request';
|
||||||
export const REQUESTING_SMS = 'requesting-sms';
|
export const REQUESTING_CODE = 'requesting-code';
|
||||||
export const QUERY_CODE = 'query-code';
|
export const QUERY_CODE = 'query-code';
|
||||||
export const POSTING_CONFIRMATION = 'posting-confirmation';
|
export const POSTING_CONFIRMATION = 'posting-confirmation';
|
||||||
export const POSTED_CONFIRMATION = 'posted-confirmation';
|
export const POSTED_CONFIRMATION = 'posted-confirmation';
|
||||||
@ -43,56 +41,30 @@ export default class VerificationStore {
|
|||||||
@observable isVerified = null;
|
@observable isVerified = null;
|
||||||
@observable hasRequested = null;
|
@observable hasRequested = null;
|
||||||
@observable consentGiven = false;
|
@observable consentGiven = false;
|
||||||
@observable number = '';
|
|
||||||
@observable requestTx = null;
|
@observable requestTx = null;
|
||||||
@observable code = '';
|
@observable code = '';
|
||||||
@observable isCodeValid = null;
|
@observable isCodeValid = null;
|
||||||
@observable confirmationTx = null;
|
@observable confirmationTx = null;
|
||||||
|
|
||||||
@computed get isNumberValid () {
|
constructor (api, abi, certifierId, account, isTestnet) {
|
||||||
return phone.isValidNumber(this.number);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get isStepValid () {
|
|
||||||
if (this.step === DONE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.step) {
|
|
||||||
case LOADING:
|
|
||||||
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
|
|
||||||
case QUERY_DATA:
|
|
||||||
return this.isNumberValid && this.consentGiven;
|
|
||||||
case QUERY_CODE:
|
|
||||||
return this.requestTx && this.isCodeValid === true;
|
|
||||||
case POSTED_CONFIRMATION:
|
|
||||||
return !!this.confirmationTx;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (api, account, isTestnet) {
|
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.isTestnet = isTestnet;
|
this.isTestnet = isTestnet;
|
||||||
|
|
||||||
this.step = LOADING;
|
this.step = LOADING;
|
||||||
Contracts.create(api).registry.getContract('smsverification')
|
Contracts.get().badgeReg.fetchCertifier(certifierId)
|
||||||
.then((contract) => {
|
.then(({ address }) => {
|
||||||
this.contract = contract;
|
this.contract = new Contract(api, abi).at(address);
|
||||||
this.load();
|
this.load();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
console.error('error', err);
|
||||||
this.error = 'Failed to fetch the contract: ' + err.message;
|
this.error = 'Failed to fetch the contract: ' + err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
if (this.error) {
|
if (this.error) {
|
||||||
console.error('sms verification: ' + this.error);
|
console.error('verification: ' + this.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -135,10 +107,6 @@ export default class VerificationStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setNumber = (number) => {
|
|
||||||
this.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action setConsentGiven = (consentGiven) => {
|
@action setConsentGiven = (consentGiven) => {
|
||||||
this.consentGiven = consentGiven;
|
this.consentGiven = consentGiven;
|
||||||
}
|
}
|
||||||
@ -166,19 +134,22 @@ export default class VerificationStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestValues = () => []
|
||||||
|
|
||||||
@action sendRequest = () => {
|
@action sendRequest = () => {
|
||||||
const { api, account, contract, fee, number, hasRequested } = this;
|
const { api, account, contract, fee, hasRequested } = this;
|
||||||
|
|
||||||
const request = contract.functions.find((fn) => fn.name === 'request');
|
const request = contract.functions.find((fn) => fn.name === 'request');
|
||||||
const options = { from: account, value: fee.toString() };
|
const options = { from: account, value: fee.toString() };
|
||||||
|
const values = this.requestValues();
|
||||||
|
|
||||||
let chain = Promise.resolve();
|
let chain = Promise.resolve();
|
||||||
if (!hasRequested) {
|
if (!hasRequested) {
|
||||||
this.step = POSTING_REQUEST;
|
this.step = POSTING_REQUEST;
|
||||||
chain = request.estimateGas(options, [])
|
chain = request.estimateGas(options, values)
|
||||||
.then((gas) => {
|
.then((gas) => {
|
||||||
options.gas = gas.mul(1.2).toFixed(0);
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
return request.postTransaction(options, []);
|
return request.postTransaction(options, values);
|
||||||
})
|
})
|
||||||
.then((handle) => {
|
.then((handle) => {
|
||||||
// TODO: The "request rejected" error doesn't have any property to
|
// TODO: The "request rejected" error doesn't have any property to
|
||||||
@ -200,18 +171,15 @@ export default class VerificationStore {
|
|||||||
|
|
||||||
chain
|
chain
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return api.parity.netChain();
|
this.step = REQUESTING_CODE;
|
||||||
})
|
return this.requestCode();
|
||||||
.then((chain) => {
|
|
||||||
this.step = REQUESTING_SMS;
|
|
||||||
return postToServer({ number, address: account }, this.isTestnet);
|
|
||||||
})
|
})
|
||||||
.then(() => awaitPuzzle(api, contract, account))
|
.then(() => awaitPuzzle(api, contract, account))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.step = QUERY_CODE;
|
this.step = QUERY_CODE;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.error = 'Failed to request a confirmation SMS: ' + err.message;
|
this.error = 'Failed to request a confirmation code: ' + err.message;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
25
js/src/modals/Verification/verification.css
Normal file
25
js/src/modals/Verification/verification.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.noSpacing {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list li {
|
||||||
|
padding: .1em 0;
|
||||||
|
}
|
@ -20,12 +20,27 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
|||||||
import CancelIcon from 'material-ui/svg-icons/content/clear';
|
import CancelIcon from 'material-ui/svg-icons/content/clear';
|
||||||
|
|
||||||
import { Button, IdentityIcon, Modal } from '~/ui';
|
import { Button, IdentityIcon, Modal } from '~/ui';
|
||||||
|
import RadioButtons from '~/ui/Form/RadioButtons';
|
||||||
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
|
import styles from './verification.css';
|
||||||
|
|
||||||
|
const methods = {
|
||||||
|
sms: {
|
||||||
|
label: 'SMS Verification', key: 0, value: 'sms',
|
||||||
|
description: (<p className={ styles.noSpacing }>It will be stored on the blockchain that you control a phone number (not <em>which</em>).</p>)
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
label: 'E-mail Verification', key: 1, value: 'email',
|
||||||
|
description: (<p className={ styles.noSpacing }>The hash of the e-mail address you prove control over will be stored on the blockchain.</p>)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LOADING,
|
LOADING,
|
||||||
QUERY_DATA,
|
QUERY_DATA,
|
||||||
POSTING_REQUEST, POSTED_REQUEST,
|
POSTING_REQUEST, POSTED_REQUEST,
|
||||||
REQUESTING_SMS, QUERY_CODE,
|
REQUESTING_CODE, QUERY_CODE,
|
||||||
POSTING_CONFIRMATION, POSTED_CONFIRMATION,
|
POSTING_CONFIRMATION, POSTED_CONFIRMATION,
|
||||||
DONE
|
DONE
|
||||||
} from './store';
|
} from './store';
|
||||||
@ -37,34 +52,44 @@ import SendConfirmation from './SendConfirmation';
|
|||||||
import Done from './Done';
|
import Done from './Done';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class SMSVerification extends Component {
|
export default class Verification extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
store: PropTypes.any.isRequired,
|
store: nullableProptype(PropTypes.object.isRequired),
|
||||||
account: PropTypes.string.isRequired,
|
account: PropTypes.string.isRequired,
|
||||||
|
onSelectMethod: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired
|
onClose: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
static phases = { // mapping (store steps -> steps)
|
static phases = { // mapping (store steps -> steps)
|
||||||
[LOADING]: 0,
|
[LOADING]: 1, [QUERY_DATA]: 1,
|
||||||
[QUERY_DATA]: 1,
|
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2,
|
||||||
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2,
|
|
||||||
[QUERY_CODE]: 3,
|
[QUERY_CODE]: 3,
|
||||||
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
|
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
|
||||||
[DONE]: 5
|
[DONE]: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
method: 'sms'
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const phase = SMSVerification.phases[this.props.store.step];
|
const { store } = this.props;
|
||||||
const { error, isStepValid } = this.props.store;
|
let phase = 0; let error = false; let isStepValid = true;
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
phase = Verification.phases[store.step];
|
||||||
|
error = store.error;
|
||||||
|
isStepValid = store.isStepValid;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
actions={ this.renderDialogActions(phase, error, isStepValid) }
|
actions={ this.renderDialogActions(phase, error, isStepValid) }
|
||||||
title='verify your account via SMS'
|
title='verify your account'
|
||||||
visible
|
visible
|
||||||
current={ phase }
|
current={ phase }
|
||||||
steps={ ['Prepare', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
|
steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
|
||||||
waiting={ error ? [] : [ 0, 2, 4 ] }
|
waiting={ error ? [] : [ 2, 4 ] }
|
||||||
>
|
>
|
||||||
{ this.renderStep(phase, error) }
|
{ this.renderStep(phase, error) }
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -101,6 +126,13 @@ export default class SMSVerification extends Component {
|
|||||||
|
|
||||||
let action = () => {};
|
let action = () => {};
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
|
case 0:
|
||||||
|
action = () => {
|
||||||
|
const { onSelectMethod } = this.props;
|
||||||
|
const { method } = this.state;
|
||||||
|
onSelectMethod(method);
|
||||||
|
};
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
action = store.sendRequest;
|
action = store.sendRequest;
|
||||||
break;
|
break;
|
||||||
@ -133,26 +165,58 @@ export default class SMSVerification extends Component {
|
|||||||
return (<p>{ error }</p>);
|
return (<p>{ error }</p>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { method } = this.state;
|
||||||
|
if (phase === 0) {
|
||||||
|
const values = Object.values(methods);
|
||||||
|
const value = values.findIndex((v) => v.value === method);
|
||||||
|
return (
|
||||||
|
<RadioButtons
|
||||||
|
value={ value < 0 ? 0 : value }
|
||||||
|
values={ values }
|
||||||
|
onChange={ this.selectMethod }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
step,
|
step,
|
||||||
fee, number, isNumberValid, isVerified, hasRequested,
|
fee, isVerified, hasRequested,
|
||||||
requestTx, isCodeValid, confirmationTx,
|
requestTx, isCodeValid, confirmationTx,
|
||||||
setCode
|
setCode
|
||||||
} = this.props.store;
|
} = this.props.store;
|
||||||
|
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case 0:
|
|
||||||
return (
|
|
||||||
<p>Loading SMS Verification.</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
const { setNumber, setConsentGiven } = this.props.store;
|
if (step === LOADING) {
|
||||||
|
return (<p>Loading verification data.</p>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { setConsentGiven } = this.props.store;
|
||||||
|
|
||||||
|
const fields = [];
|
||||||
|
if (method === 'sms') {
|
||||||
|
fields.push({
|
||||||
|
key: 'number',
|
||||||
|
label: 'phone number in international format',
|
||||||
|
hint: 'the SMS will be sent to this number',
|
||||||
|
error: this.props.store.isNumberValid ? null : 'invalid number',
|
||||||
|
onChange: this.props.store.setNumber
|
||||||
|
});
|
||||||
|
} else if (method === 'email') {
|
||||||
|
fields.push({
|
||||||
|
key: 'email',
|
||||||
|
label: 'email address',
|
||||||
|
hint: 'the code will be sent to this address',
|
||||||
|
error: this.props.store.isEmailValid ? null : 'invalid email',
|
||||||
|
onChange: this.props.store.setEmail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GatherData
|
<GatherData
|
||||||
fee={ fee } isNumberValid={ isNumberValid }
|
method={ method } fields={ fields }
|
||||||
isVerified={ isVerified } hasRequested={ hasRequested }
|
fee={ fee } isVerified={ isVerified } hasRequested={ hasRequested }
|
||||||
setNumber={ setNumber } setConsentGiven={ setConsentGiven }
|
setConsentGiven={ setConsentGiven }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -162,9 +226,19 @@ export default class SMSVerification extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
|
let receiver, hint;
|
||||||
|
if (method === 'sms') {
|
||||||
|
receiver = this.props.store.number;
|
||||||
|
hint = 'Enter the code you received via SMS.';
|
||||||
|
} else if (method === 'email') {
|
||||||
|
receiver = this.props.store.email;
|
||||||
|
hint = 'Enter the code you received via e-mail.';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<QueryCode
|
<QueryCode
|
||||||
number={ number } fee={ fee } isCodeValid={ isCodeValid }
|
receiver={ receiver }
|
||||||
|
hint={ hint }
|
||||||
|
isCodeValid={ isCodeValid }
|
||||||
setCode={ setCode }
|
setCode={ setCode }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -183,4 +257,8 @@ export default class SMSVerification extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectMethod = (choice, i) => {
|
||||||
|
this.setState({ method: choice.value });
|
||||||
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ import EditMeta from './EditMeta';
|
|||||||
import ExecuteContract from './ExecuteContract';
|
import ExecuteContract from './ExecuteContract';
|
||||||
import FirstRun from './FirstRun';
|
import FirstRun from './FirstRun';
|
||||||
import Shapeshift from './Shapeshift';
|
import Shapeshift from './Shapeshift';
|
||||||
import SMSVerification from './SMSVerification';
|
import Verification from './Verification';
|
||||||
import Transfer from './Transfer';
|
import Transfer from './Transfer';
|
||||||
import PasswordManager from './PasswordManager';
|
import PasswordManager from './PasswordManager';
|
||||||
import SaveContract from './SaveContract';
|
import SaveContract from './SaveContract';
|
||||||
@ -42,7 +42,7 @@ export {
|
|||||||
ExecuteContract,
|
ExecuteContract,
|
||||||
FirstRun,
|
FirstRun,
|
||||||
Shapeshift,
|
Shapeshift,
|
||||||
SMSVerification,
|
Verification,
|
||||||
Transfer,
|
Transfer,
|
||||||
PasswordManager,
|
PasswordManager,
|
||||||
LoadContract,
|
LoadContract,
|
||||||
|
@ -14,10 +14,18 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export const fetchCertifiers = () => ({
|
||||||
|
type: 'fetchCertifiers'
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchCertifications = (address) => ({
|
export const fetchCertifications = (address) => ({
|
||||||
type: 'fetchCertifications', address
|
type: 'fetchCertifications', address
|
||||||
});
|
});
|
||||||
|
|
||||||
export const addCertification = (address, name, title, icon) => ({
|
export const addCertification = (address, id, name, title, icon) => ({
|
||||||
type: 'addCertification', address, name, title, icon
|
type: 'addCertification', address, id, name, title, icon
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeCertification = (address, id) => ({
|
||||||
|
type: 'removeCertification', address, id
|
||||||
});
|
});
|
||||||
|
@ -14,38 +14,90 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Contracts from '~/contracts';
|
import { uniq } from 'lodash';
|
||||||
import { addCertification } from './actions';
|
|
||||||
|
|
||||||
const knownCertifiers = [ 'smsverification' ];
|
import ABI from '~/contracts/abi/certifier.json';
|
||||||
|
import Contract from '~/api/contract';
|
||||||
|
import Contracts from '~/contracts';
|
||||||
|
import { addCertification, removeCertification } from './actions';
|
||||||
|
|
||||||
export default class CertificationsMiddleware {
|
export default class CertificationsMiddleware {
|
||||||
toMiddleware () {
|
toMiddleware () {
|
||||||
return (store) => (next) => (action) => {
|
const api = Contracts.get()._api;
|
||||||
if (action.type !== 'fetchCertifications') {
|
|
||||||
return next(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { address } = action;
|
|
||||||
const badgeReg = Contracts.get().badgeReg;
|
const badgeReg = Contracts.get().badgeReg;
|
||||||
|
const contract = new Contract(api, ABI);
|
||||||
|
const Confirmed = contract.events.find((e) => e.name === 'Confirmed');
|
||||||
|
const Revoked = contract.events.find((e) => e.name === 'Revoked');
|
||||||
|
|
||||||
knownCertifiers.forEach((name) => {
|
let certifiers = [];
|
||||||
badgeReg.fetchCertifier(name)
|
let accounts = []; // these are addresses
|
||||||
.then((cert) => {
|
|
||||||
return badgeReg.checkIfCertified(cert.address, address)
|
const fetchConfirmedEvents = (dispatch) => {
|
||||||
.then((isCertified) => {
|
if (certifiers.length === 0 || accounts.length === 0) return;
|
||||||
if (isCertified) {
|
api.eth.getLogs({
|
||||||
const { name, title, icon } = cert;
|
fromBlock: 0,
|
||||||
store.dispatch(addCertification(address, name, title, icon));
|
toBlock: 'latest',
|
||||||
|
address: certifiers.map((c) => c.address),
|
||||||
|
topics: [ [ Confirmed.signature, Revoked.signature ], accounts ]
|
||||||
|
})
|
||||||
|
.then((logs) => contract.parseEventLogs(logs))
|
||||||
|
.then((logs) => {
|
||||||
|
logs.forEach((log) => {
|
||||||
|
const certifier = certifiers.find((c) => c.address === log.address);
|
||||||
|
if (!certifier) {
|
||||||
|
throw new Error(`Could not find certifier at ${log.address}.`);
|
||||||
|
}
|
||||||
|
const { id, name, title, icon } = certifier;
|
||||||
|
|
||||||
|
if (log.event === 'Revoked') {
|
||||||
|
dispatch(removeCertification(log.params.who.value, id));
|
||||||
|
} else {
|
||||||
|
dispatch(addCertification(log.params.who.value, id, name, title, icon));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err) {
|
console.error('Failed to fetch Confirmed events:', err);
|
||||||
console.error(`Failed to check if ${address} certified by ${name}:`, err);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (store) => (next) => (action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'fetchCertifiers':
|
||||||
|
badgeReg.certifierCount().then((count) => {
|
||||||
|
new Array(+count).fill(null).forEach((_, id) => {
|
||||||
|
badgeReg.fetchCertifier(id)
|
||||||
|
.then((cert) => {
|
||||||
|
if (!certifiers.some((c) => c.id === cert.id)) {
|
||||||
|
certifiers = certifiers.concat(cert);
|
||||||
|
fetchConfirmedEvents(store.dispatch);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(`Could not fetch certifier ${id}:`, err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'fetchCertifications':
|
||||||
|
const { address } = action;
|
||||||
|
|
||||||
|
if (!accounts.includes(address)) {
|
||||||
|
accounts = accounts.concat(address);
|
||||||
|
fetchConfirmedEvents(store.dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'setVisibleAccounts':
|
||||||
|
const { addresses } = action;
|
||||||
|
accounts = uniq(accounts.concat(addresses));
|
||||||
|
fetchConfirmedEvents(store.dispatch);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
next(action);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,27 @@
|
|||||||
const initialState = {};
|
const initialState = {};
|
||||||
|
|
||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
if (action.type !== 'addCertification') {
|
if (action.type === 'addCertification') {
|
||||||
return state;
|
const { address, id, name, icon, title } = action;
|
||||||
}
|
|
||||||
|
|
||||||
const { address, name, icon, title } = action;
|
|
||||||
const certifications = state[address] || [];
|
const certifications = state[address] || [];
|
||||||
|
|
||||||
if (certifications.some((c) => c.name === name)) {
|
if (certifications.some((c) => c.id === id)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const newCertifications = certifications.concat({ name, icon, title });
|
|
||||||
|
|
||||||
|
const newCertifications = certifications.concat({
|
||||||
|
id, name, icon, title
|
||||||
|
});
|
||||||
return { ...state, [address]: newCertifications };
|
return { ...state, [address]: newCertifications };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === 'removeCertification') {
|
||||||
|
const { address, id } = action;
|
||||||
|
const certifications = state[address] || [];
|
||||||
|
|
||||||
|
const newCertifications = certifications.filter((c) => c.id !== id);
|
||||||
|
return { ...state, [address]: newCertifications };
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
};
|
};
|
||||||
|
@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
|
|
||||||
import { hashToImageUrl } from '~/redux/providers/imagesReducer';
|
import { hashToImageUrl } from '~/redux/providers/imagesReducer';
|
||||||
import { fetchCertifications } from '~/redux/providers/certifications/actions';
|
|
||||||
|
|
||||||
import defaultIcon from '../../../assets/images/certifications/unknown.svg';
|
import defaultIcon from '../../../assets/images/certifications/unknown.svg';
|
||||||
|
|
||||||
@ -29,14 +27,7 @@ class Certifications extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.string.isRequired,
|
account: PropTypes.string.isRequired,
|
||||||
certifications: PropTypes.array.isRequired,
|
certifications: PropTypes.array.isRequired,
|
||||||
dappsUrl: PropTypes.string.isRequired,
|
dappsUrl: PropTypes.string.isRequired
|
||||||
|
|
||||||
fetchCertifications: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
const { account, fetchCertifications } = this.props;
|
|
||||||
fetchCertifications(account);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -73,15 +64,13 @@ function mapStateToProps (_, initProps) {
|
|||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const certifications = state.certifications[account] || [];
|
const certifications = state.certifications[account] || [];
|
||||||
return { certifications };
|
const dappsUrl = state.api.dappsUrl;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
return { certifications, dappsUrl };
|
||||||
return bindActionCreators({ fetchCertifications }, dispatch);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
null
|
||||||
)(Certifications);
|
)(Certifications);
|
||||||
|
@ -46,26 +46,26 @@ export default class Select extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { disabled, error, label, hint, value, children, className, onBlur, onChange, onKeyDown } = this.props;
|
const { children, className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectField
|
<SelectField
|
||||||
className={ className }
|
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
|
className={ className }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
errorText={ error }
|
errorText={ error }
|
||||||
floatingLabelFixed
|
floatingLabelFixed
|
||||||
floatingLabelText={ label }
|
floatingLabelText={ label }
|
||||||
fullWidth
|
fullWidth
|
||||||
hintText={ hint }
|
hintText={ hint }
|
||||||
name={ NAME_ID }
|
|
||||||
id={ NAME_ID }
|
id={ NAME_ID }
|
||||||
underlineDisabledStyle={ UNDERLINE_DISABLED }
|
name={ NAME_ID }
|
||||||
underlineStyle={ UNDERLINE_NORMAL }
|
|
||||||
value={ value }
|
|
||||||
onBlur={ onBlur }
|
onBlur={ onBlur }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
onKeyDown={ onKeyDown }>
|
onKeyDown={ onKeyDown }
|
||||||
|
underlineDisabledStyle={ UNDERLINE_DISABLED }
|
||||||
|
underlineStyle={ UNDERLINE_NORMAL }
|
||||||
|
value={ value }>
|
||||||
{ children }
|
{ children }
|
||||||
</SelectField>
|
</SelectField>
|
||||||
);
|
);
|
||||||
|
@ -289,9 +289,8 @@ export default class TypedInput extends Component {
|
|||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={ bool }
|
key={ bool }
|
||||||
value={ bool }
|
|
||||||
label={ bool }
|
label={ bool }
|
||||||
>
|
value={ bool }>
|
||||||
{ bool }
|
{ bool }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
@ -299,19 +298,23 @@ export default class TypedInput extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
label={ label }
|
|
||||||
hint={ hint }
|
|
||||||
value={ value ? 'true' : 'false' }
|
|
||||||
error={ error }
|
error={ error }
|
||||||
|
hint={ hint }
|
||||||
|
label={ label }
|
||||||
onChange={ this.onChangeBool }
|
onChange={ this.onChangeBool }
|
||||||
>
|
value={
|
||||||
|
value
|
||||||
|
? 'true'
|
||||||
|
: 'false'
|
||||||
|
}>
|
||||||
{ boolitems }
|
{ boolitems }
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeBool = (event, _index, value) => {
|
onChangeBool = (event, _index, value) => {
|
||||||
this.props.onChange(value === 'true');
|
// NOTE: event.target.value added for enzyme simulated event testing
|
||||||
|
this.props.onChange((value || event.target.value) === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
onEthTypeChange = () => {
|
onEthTypeChange = () => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user