Merge branch 'master' into lightrpc
This commit is contained in:
commit
d8b1cfe082
@ -22,18 +22,20 @@ linux-stable:
|
|||||||
- triggers
|
- triggers
|
||||||
script:
|
script:
|
||||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||||
- cargo build -j $(nproc) --release -p evmbin ethstore ethkey
|
- cargo build -j $(nproc) --release -p evmbin
|
||||||
|
- cargo build -j $(nproc) --release -p ethstore
|
||||||
|
- cargo build -j $(nproc) --release -p ethkey
|
||||||
- strip target/release/parity
|
- strip target/release/parity
|
||||||
- strip target/release/evmbin
|
- strip target/release/evm
|
||||||
- strip target/release/ethstore
|
- strip target/release/ethstore
|
||||||
- strip target/release/ethkey
|
- strip target/release/ethkey
|
||||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||||
- md5sum target/release/parity > parity.md5
|
- md5sum target/release/parity > parity.md5
|
||||||
- sh scripts/deb-build.sh amd64
|
- sh scripts/deb-build.sh amd64
|
||||||
- cp target/release/parity deb/usr/bin/parity
|
- cp target/release/parity deb/usr/bin/parity
|
||||||
- cp target/release/evmbin deb/usr/bin/evmbin
|
- cp target/release/parity/evm deb/usr/bin/evm
|
||||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
- cp target/release/parity/ethstore deb/usr/bin/ethstore
|
||||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
- cp target/release/parity/ethkey deb/usr/bin/ethkey
|
||||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||||
@ -53,6 +55,9 @@ linux-stable:
|
|||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- target/release/parity
|
- target/release/parity
|
||||||
|
- target/release/parity/evmbin
|
||||||
|
- target/release/parity/ethstore
|
||||||
|
- target/release/parity/ethkey
|
||||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||||
linux-stable-debian:
|
linux-stable-debian:
|
||||||
stage: build
|
stage: build
|
||||||
@ -485,9 +490,10 @@ docker-build:
|
|||||||
- docker info
|
- docker info
|
||||||
script:
|
script:
|
||||||
- cd docker/hub
|
- cd docker/hub
|
||||||
|
- if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
|
||||||
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
|
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
|
||||||
- docker build --tag ethcore/parity:$CI_BUILD_REF_NAME .
|
- docker build --no-cache=true --tag ethcore/parity:$DOCKER_TAG .
|
||||||
- docker push ethcore/parity:$CI_BUILD_REF_NAME
|
- docker push ethcore/parity:$DOCKER_TAG
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
test-darwin:
|
test-darwin:
|
||||||
|
108
Cargo.lock
generated
108
Cargo.lock
generated
@ -20,6 +20,7 @@ dependencies = [
|
|||||||
"ethcore-light 1.6.0",
|
"ethcore-light 1.6.0",
|
||||||
"ethcore-logger 1.6.0",
|
"ethcore-logger 1.6.0",
|
||||||
"ethcore-rpc 1.6.0",
|
"ethcore-rpc 1.6.0",
|
||||||
|
"ethcore-secretstore 1.0.0",
|
||||||
"ethcore-signer 1.6.0",
|
"ethcore-signer 1.6.0",
|
||||||
"ethcore-stratum 1.6.0",
|
"ethcore-stratum 1.6.0",
|
||||||
"ethcore-util 1.6.0",
|
"ethcore-util 1.6.0",
|
||||||
@ -33,6 +34,8 @@ dependencies = [
|
|||||||
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-hash-fetch 1.6.0",
|
"parity-hash-fetch 1.6.0",
|
||||||
|
"parity-ipfs-api 1.6.0",
|
||||||
|
"parity-local-store 0.1.0",
|
||||||
"parity-reactor 0.1.0",
|
"parity-reactor 0.1.0",
|
||||||
"parity-rpc-client 1.4.0",
|
"parity-rpc-client 1.4.0",
|
||||||
"parity-updater 1.6.0",
|
"parity-updater 1.6.0",
|
||||||
@ -104,6 +107,11 @@ 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 = "base-x"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base32"
|
name = "base32"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -189,6 +197,16 @@ name = "cfg-if"
|
|||||||
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 = "cid"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"integer-encoding 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clippy"
|
name = "clippy"
|
||||||
version = "0.0.103"
|
version = "0.0.103"
|
||||||
@ -622,6 +640,23 @@ dependencies = [
|
|||||||
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ethcore-secretstore"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore-devtools 1.6.0",
|
||||||
|
"ethcore-ipc 1.6.0",
|
||||||
|
"ethcore-ipc-codegen 1.6.0",
|
||||||
|
"ethcore-ipc-nano 1.6.0",
|
||||||
|
"ethcore-util 1.6.0",
|
||||||
|
"ethcrypto 0.1.0",
|
||||||
|
"ethkey 0.2.0",
|
||||||
|
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethcore-signer"
|
name = "ethcore-signer"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1008,6 +1043,11 @@ dependencies = [
|
|||||||
"xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "integer-encoding"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipc-common-types"
|
name = "ipc-common-types"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1319,6 +1359,23 @@ dependencies = [
|
|||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multibase"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multihash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nanomsg"
|
name = "nanomsg"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -1575,6 +1632,34 @@ dependencies = [
|
|||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parity-ipfs-api"
|
||||||
|
version = "1.6.0"
|
||||||
|
dependencies = [
|
||||||
|
"cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ethcore 1.6.0",
|
||||||
|
"ethcore-util 1.6.0",
|
||||||
|
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||||
|
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rlp 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parity-local-store"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethcore 1.6.0",
|
||||||
|
"ethcore-io 1.6.0",
|
||||||
|
"ethcore-util 1.6.0",
|
||||||
|
"ethkey 0.2.0",
|
||||||
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rlp 0.1.0",
|
||||||
|
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-reactor"
|
name = "parity-reactor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1620,7 +1705,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#8cfa973a48243b57279f2f0b2cbe3f010c6c5a34"
|
source = "git+https://github.com/ethcore/js-precompiled.git#9fb4ab9d8ffaca9cd9f07270bf69681c2081050f"
|
||||||
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)",
|
||||||
]
|
]
|
||||||
@ -1830,6 +1915,15 @@ dependencies = [
|
|||||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rlp"
|
name = "rlp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2346,6 +2440,11 @@ name = "unicode-xid"
|
|||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -2470,6 +2569,7 @@ dependencies = [
|
|||||||
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
|
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
|
||||||
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
||||||
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
||||||
|
"checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1"
|
||||||
"checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c"
|
"checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c"
|
||||||
"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae"
|
"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae"
|
||||||
"checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da"
|
"checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da"
|
||||||
@ -2484,6 +2584,7 @@ dependencies = [
|
|||||||
"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
|
"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
|
||||||
"checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>"
|
"checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>"
|
||||||
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
|
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
|
||||||
|
"checksum cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e53e6cdfa5ca294863e8c8a32a7cdb4dc0a442c8971d47a0e75b6c27ea268a6a"
|
||||||
"checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32"
|
"checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32"
|
||||||
"checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a"
|
"checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a"
|
||||||
"checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245"
|
"checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245"
|
||||||
@ -2520,6 +2621,7 @@ dependencies = [
|
|||||||
"checksum hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afe68f772f0497a7205e751626bb8e1718568b58534b6108c73a74ef80483409"
|
"checksum hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afe68f772f0497a7205e751626bb8e1718568b58534b6108c73a74ef80483409"
|
||||||
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
|
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
|
||||||
"checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484"
|
"checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484"
|
||||||
|
"checksum integer-encoding 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a053c9c7dcb7db1f2aa012c37dc176c62e4cdf14898dee0eecc606de835b8acb"
|
||||||
"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.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a"
|
"checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a"
|
||||||
"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5"
|
"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5"
|
||||||
@ -2551,6 +2653,8 @@ dependencies = [
|
|||||||
"checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67"
|
"checksum mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "410a1a0ff76f5a226f1e4e3ff1756128e65cd30166e39c3892283e2ac09d5b67"
|
||||||
"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a"
|
"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a"
|
||||||
"checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8"
|
"checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8"
|
||||||
|
"checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6"
|
||||||
|
"checksum multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "755d5a39bee3faaf649437e873beab334990221b2faf1f2e56ca10a9e4600235"
|
||||||
"checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
"checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
||||||
"checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
"checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
||||||
"checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09"
|
"checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09"
|
||||||
@ -2601,6 +2705,7 @@ dependencies = [
|
|||||||
"checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29"
|
"checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29"
|
||||||
"checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9"
|
"checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9"
|
||||||
"checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6"
|
"checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6"
|
||||||
|
"checksum ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87ac4fce2ee4bb10dd106788e90fdfa4c5a7f3f9f6aae29824db77dc57e2767d"
|
||||||
"checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
"checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||||
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
||||||
@ -2662,6 +2767,7 @@ dependencies = [
|
|||||||
"checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172"
|
"checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172"
|
||||||
"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb"
|
"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb"
|
||||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||||
|
"checksum untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193df64312e3515fd983ded55ad5bcaa7647a035804828ed757e832ce6029ef3"
|
||||||
"checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119"
|
"checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119"
|
||||||
"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
|
"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
|
||||||
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
||||||
|
@ -44,10 +44,13 @@ rlp = { path = "util/rlp" }
|
|||||||
rpc-cli = { path = "rpc_cli" }
|
rpc-cli = { path = "rpc_cli" }
|
||||||
parity-rpc-client = { path = "rpc_client" }
|
parity-rpc-client = { path = "rpc_client" }
|
||||||
parity-hash-fetch = { path = "hash-fetch" }
|
parity-hash-fetch = { path = "hash-fetch" }
|
||||||
|
parity-ipfs-api = { path = "ipfs" }
|
||||||
parity-updater = { path = "updater" }
|
parity-updater = { path = "updater" }
|
||||||
parity-reactor = { path = "util/reactor" }
|
parity-reactor = { path = "util/reactor" }
|
||||||
|
parity-local-store = { path = "local-store" }
|
||||||
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}
|
||||||
|
ethcore-secretstore = { path = "secret_store", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||||
@ -82,6 +85,7 @@ evm-debug = ["ethcore/evm-debug"]
|
|||||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||||
slow-blocks = ["ethcore/slow-blocks"]
|
slow-blocks = ["ethcore/slow-blocks"]
|
||||||
final = ["ethcore-util/final"]
|
final = ["ethcore-util/final"]
|
||||||
|
secretstore = ["ethcore-secretstore"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "parity/main.rs"
|
path = "parity/main.rs"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
FROM ubuntu:14.04
|
FROM ubuntu:14.04
|
||||||
|
MAINTAINER Parity Technologies <devops@parity.io>
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
# install tools and dependencies
|
# install tools and dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
@ -25,46 +26,54 @@ RUN apt-get update && \
|
|||||||
# evmjit dependencies
|
# evmjit dependencies
|
||||||
zlib1g-dev \
|
zlib1g-dev \
|
||||||
libedit-dev \
|
libedit-dev \
|
||||||
libudev-dev
|
libudev-dev &&\
|
||||||
|
# cmake and llvm ppa's. then update ppa's
|
||||||
# cmake and llvm ppas. then update ppas
|
add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
|
||||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
apt-get install -y --force-yes cmake llvm-3.7-dev && \
|
||||||
|
|
||||||
# install evmjit
|
# install evmjit
|
||||||
RUN git clone https://github.com/debris/evmjit && \
|
git clone https://github.com/debris/evmjit && \
|
||||||
cd evmjit && \
|
cd evmjit && \
|
||||||
mkdir build && cd build && \
|
mkdir build && cd build && \
|
||||||
cmake .. && make && make install && cd
|
cmake .. && make && make install && cd && \
|
||||||
|
|
||||||
# install rustup
|
# install rustup
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||||
|
|
||||||
# rustup directory
|
# rustup directory
|
||||||
ENV PATH /root/.cargo/bin:$PATH
|
PATH=/root/.cargo/bin:$PATH && \
|
||||||
|
|
||||||
# show backtraces
|
# show backtraces
|
||||||
ENV RUST_BACKTRACE 1
|
RUST_BACKTRACE=1 && \
|
||||||
|
|
||||||
# show tools
|
|
||||||
RUN rustc -vV && \
|
|
||||||
cargo -V && \
|
|
||||||
gcc -v &&\
|
|
||||||
g++ -v
|
|
||||||
|
|
||||||
# build parity
|
# build parity
|
||||||
RUN git clone https://github.com/ethcore/parity && \
|
cd /build&&git clone https://github.com/ethcore/parity && \
|
||||||
cd parity && \
|
cd parity && \
|
||||||
git pull && \
|
git pull&& \
|
||||||
cargo build --release --features final && \
|
git checkout $CI_BUILD_REF_NAME && \
|
||||||
ls /build/parity/target/release/parity && \
|
cargo build --verbose --release --features final && \
|
||||||
strip /build/parity/target/release/parity
|
#ls /build/parity/target/release/parity && \
|
||||||
|
strip /build/parity/target/release/parity && \
|
||||||
RUN file /build/parity/target/release/parity&&cp /build/parity/target/release/parity /parity
|
file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\
|
||||||
#cleanup Docker image
|
#cleanup Docker image
|
||||||
RUN rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build
|
rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\
|
||||||
|
apt-get purge -y \
|
||||||
|
# make
|
||||||
|
build-essential \
|
||||||
|
# add-apt-repository
|
||||||
|
software-properties-common \
|
||||||
|
make \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
git \
|
||||||
|
g++ \
|
||||||
|
gcc \
|
||||||
|
binutils \
|
||||||
|
file \
|
||||||
|
pkg-config \
|
||||||
|
dpkg-dev \
|
||||||
|
# evmjit dependencies
|
||||||
|
zlib1g-dev \
|
||||||
|
libedit-dev \
|
||||||
|
cmake llvm-3.7-dev&&\
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
# setup ENTRYPOINT
|
||||||
EXPOSE 8080 8545 8180
|
EXPOSE 8080 8545 8180
|
||||||
ENTRYPOINT ["/parity"]
|
ENTRYPOINT ["/parity/parity"]
|
||||||
|
@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
use util::RwLock;
|
use util::{FixedHash, RwLock};
|
||||||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
||||||
random_string, SecretVaultRef, StoreAccountRef};
|
random_string, SecretVaultRef, StoreAccountRef};
|
||||||
use ethstore::dir::MemoryDirectory;
|
use ethstore::dir::MemoryDirectory;
|
||||||
@ -241,25 +241,88 @@ impl AccountProvider {
|
|||||||
Ok(accounts.into_iter().map(|a| a.address).collect())
|
Ok(accounts.into_iter().map(|a| a.address).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a whitelist of accounts exposed for unknown dapps.
|
/// Sets addresses of accounts exposed for unknown dapps.
|
||||||
/// `None` means that all accounts will be visible.
|
/// `None` means that all accounts will be visible.
|
||||||
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
/// If not `None` or empty it will also override default account.
|
||||||
|
pub fn set_new_dapps_addresses(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
||||||
|
let current_default = self.new_dapps_default_address()?;
|
||||||
|
|
||||||
self.dapps_settings.write().set_policy(match accounts {
|
self.dapps_settings.write().set_policy(match accounts {
|
||||||
None => NewDappsPolicy::AllAccounts,
|
None => NewDappsPolicy::AllAccounts {
|
||||||
|
default: current_default,
|
||||||
|
},
|
||||||
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a whitelist of accounts exposed for unknown dapps.
|
/// Gets addresses of accounts exposed for unknown dapps.
|
||||||
/// `None` means that all accounts will be visible.
|
/// `None` means that all accounts will be visible.
|
||||||
pub fn new_dapps_whitelist(&self) -> Result<Option<Vec<Address>>, Error> {
|
pub fn new_dapps_addresses(&self) -> Result<Option<Vec<Address>>, Error> {
|
||||||
Ok(match self.dapps_settings.read().policy() {
|
Ok(match self.dapps_settings.read().policy() {
|
||||||
NewDappsPolicy::AllAccounts => None,
|
NewDappsPolicy::AllAccounts { .. } => None,
|
||||||
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
|
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a default account for unknown dapps.
|
||||||
|
/// This account will always be returned as the first one.
|
||||||
|
pub fn set_new_dapps_default_address(&self, address: Address) -> Result<(), Error> {
|
||||||
|
if !self.valid_addresses()?.contains(&address) {
|
||||||
|
return Err(SSError::InvalidAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut settings = self.dapps_settings.write();
|
||||||
|
let new_policy = match settings.policy() {
|
||||||
|
NewDappsPolicy::AllAccounts { .. } => NewDappsPolicy::AllAccounts { default: address },
|
||||||
|
NewDappsPolicy::Whitelist(list) => NewDappsPolicy::Whitelist(Self::insert_default(list, address)),
|
||||||
|
};
|
||||||
|
settings.set_policy(new_policy);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts given address as first in the vector, preventing duplicates.
|
||||||
|
fn insert_default(mut addresses: Vec<Address>, default: Address) -> Vec<Address> {
|
||||||
|
if let Some(position) = addresses.iter().position(|address| address == &default) {
|
||||||
|
addresses.swap(0, position);
|
||||||
|
} else {
|
||||||
|
addresses.insert(0, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of accounts that new dapp should see.
|
||||||
|
/// First account is always the default account.
|
||||||
|
fn new_dapps_addresses_list(&self) -> Result<Vec<Address>, Error> {
|
||||||
|
match self.dapps_settings.read().policy() {
|
||||||
|
NewDappsPolicy::AllAccounts { default } => if default.is_zero() {
|
||||||
|
self.accounts()
|
||||||
|
} else {
|
||||||
|
Ok(Self::insert_default(self.accounts()?, default))
|
||||||
|
},
|
||||||
|
NewDappsPolicy::Whitelist(accounts) => {
|
||||||
|
let addresses = self.filter_addresses(accounts)?;
|
||||||
|
if addresses.is_empty() {
|
||||||
|
Ok(vec![self.accounts()?.get(0).cloned().unwrap_or(0.into())])
|
||||||
|
} else {
|
||||||
|
Ok(addresses)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a default account for new dapps
|
||||||
|
/// Will return zero address in case the default is not set and there are no accounts configured.
|
||||||
|
pub fn new_dapps_default_address(&self) -> Result<Address, Error> {
|
||||||
|
Ok(self.new_dapps_addresses_list()?
|
||||||
|
.get(0)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(0.into())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a list of dapps recently requesting accounts.
|
/// Gets a list of dapps recently requesting accounts.
|
||||||
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
|
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
|
||||||
Ok(self.dapps_settings.read().recent_dapps())
|
Ok(self.dapps_settings.read().recent_dapps())
|
||||||
@ -272,41 +335,74 @@ impl AccountProvider {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets addresses visile for dapp.
|
/// Gets addresses visible for given dapp.
|
||||||
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
pub fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
||||||
let dapps = self.dapps_settings.read();
|
let accounts = self.dapps_settings.read().settings().get(&dapp).map(|settings| {
|
||||||
|
(settings.accounts.clone(), settings.default.clone())
|
||||||
|
});
|
||||||
|
|
||||||
let accounts = dapps.settings().get(&dapp).map(|settings| settings.accounts.clone());
|
|
||||||
match accounts {
|
match accounts {
|
||||||
Some(accounts) => Ok(accounts),
|
Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)),
|
||||||
None => match dapps.policy() {
|
Some((Some(accounts), None)) => self.filter_addresses(accounts),
|
||||||
NewDappsPolicy::AllAccounts => self.accounts(),
|
Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)),
|
||||||
NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts),
|
_ => self.new_dapps_addresses_list(),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns default account for particular dapp falling back to other allowed accounts if necessary.
|
/// Returns default account for particular dapp falling back to other allowed accounts if necessary.
|
||||||
pub fn default_address(&self, dapp: DappId) -> Result<Address, Error> {
|
pub fn dapp_default_address(&self, dapp: DappId) -> Result<Address, Error> {
|
||||||
self.dapps_addresses(dapp)?
|
let dapp_default = self.dapp_addresses(dapp)?
|
||||||
.get(0)
|
.get(0)
|
||||||
.cloned()
|
.cloned();
|
||||||
.ok_or(SSError::InvalidAccount)
|
|
||||||
|
match dapp_default {
|
||||||
|
Some(default) => Ok(default),
|
||||||
|
None => self.new_dapps_default_address(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets addresses visile for dapp.
|
/// Sets default address for given dapp.
|
||||||
pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> {
|
/// Does not alter dapp addresses, but this account will always be returned as the first one.
|
||||||
let addresses = self.filter_addresses(addresses)?;
|
pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> {
|
||||||
self.dapps_settings.write().set_accounts(dapp, addresses);
|
if !self.valid_addresses()?.contains(&address) {
|
||||||
|
return Err(SSError::InvalidAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dapps_settings.write().set_default(dapp, address);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets addresses visible for given dapp.
|
||||||
|
/// If `None` - falls back to dapps addresses
|
||||||
|
/// If not `None` and not empty it will also override default account.
|
||||||
|
pub fn set_dapp_addresses(&self, dapp: DappId, addresses: Option<Vec<Address>>) -> Result<(), Error> {
|
||||||
|
let (addresses, default) = match addresses {
|
||||||
|
Some(addresses) => {
|
||||||
|
let addresses = self.filter_addresses(addresses)?;
|
||||||
|
let default = addresses.get(0).cloned();
|
||||||
|
(Some(addresses), default)
|
||||||
|
},
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut settings = self.dapps_settings.write();
|
||||||
|
if let Some(default) = default {
|
||||||
|
settings.set_default(dapp.clone(), default);
|
||||||
|
}
|
||||||
|
settings.set_accounts(dapp, addresses);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_addresses(&self) -> Result<HashSet<Address>, Error> {
|
||||||
|
Ok(self.addresses_info().into_iter()
|
||||||
|
.map(|(address, _)| address)
|
||||||
|
.chain(self.accounts()?)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes addresses that are neither accounts nor in address book.
|
/// Removes addresses that are neither accounts nor in address book.
|
||||||
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
|
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
|
||||||
let valid = self.addresses_info().into_iter()
|
let valid = self.valid_addresses()?;
|
||||||
.map(|(address, _)| address)
|
|
||||||
.chain(self.accounts()?)
|
|
||||||
.collect::<HashSet<_>>();
|
|
||||||
|
|
||||||
Ok(addresses.into_iter()
|
Ok(addresses.into_iter()
|
||||||
.filter(|a| valid.contains(&a))
|
.filter(|a| valid.contains(&a))
|
||||||
@ -743,44 +839,92 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_set_dapps_addresses() {
|
fn should_reset_dapp_addresses_to_default() {
|
||||||
|
// given
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
let app = DappId("app1".into());
|
||||||
|
// add accounts to address book
|
||||||
|
ap.set_address_name(1.into(), "1".into());
|
||||||
|
ap.set_address_name(2.into(), "2".into());
|
||||||
|
// set `AllAccounts` policy
|
||||||
|
ap.set_new_dapps_addresses(Some(vec![1.into(), 2.into()])).unwrap();
|
||||||
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||||
|
|
||||||
|
// Alter and check
|
||||||
|
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 3.into()])).unwrap();
|
||||||
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
|
||||||
|
|
||||||
|
// Reset back to default
|
||||||
|
ap.set_dapp_addresses(app.clone(), None).unwrap();
|
||||||
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_dapps_default_address() {
|
||||||
// given
|
// given
|
||||||
let ap = AccountProvider::transient_provider();
|
let ap = AccountProvider::transient_provider();
|
||||||
let app = DappId("app1".into());
|
let app = DappId("app1".into());
|
||||||
// set `AllAccounts` policy
|
// set `AllAccounts` policy
|
||||||
ap.set_new_dapps_whitelist(None).unwrap();
|
ap.set_new_dapps_addresses(None).unwrap();
|
||||||
// add accounts to address book
|
// add accounts to address book
|
||||||
ap.set_address_name(1.into(), "1".into());
|
ap.set_address_name(1.into(), "1".into());
|
||||||
ap.set_address_name(2.into(), "2".into());
|
ap.set_address_name(2.into(), "2".into());
|
||||||
|
|
||||||
// when
|
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 2.into(), 3.into()])).unwrap();
|
||||||
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap();
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||||
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
||||||
|
|
||||||
// then
|
// when setting empty list
|
||||||
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
ap.set_dapp_addresses(app.clone(), Some(vec![])).unwrap();
|
||||||
|
|
||||||
|
// then default account is intact
|
||||||
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
|
||||||
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
||||||
|
|
||||||
|
// alter default account
|
||||||
|
ap.set_dapp_default_address("app1".into(), 2.into()).unwrap();
|
||||||
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![2.into()]);
|
||||||
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 2.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_set_dapps_policy() {
|
fn should_set_dapps_policy_and_default_account() {
|
||||||
// given
|
// given
|
||||||
let ap = AccountProvider::transient_provider();
|
let ap = AccountProvider::transient_provider();
|
||||||
|
|
||||||
|
// default_account should be always available
|
||||||
|
assert_eq!(ap.new_dapps_default_address().unwrap(), 0.into());
|
||||||
|
|
||||||
let address = ap.new_account("test").unwrap();
|
let address = ap.new_account("test").unwrap();
|
||||||
ap.set_address_name(1.into(), "1".into());
|
ap.set_address_name(1.into(), "1".into());
|
||||||
|
|
||||||
// When returning nothing
|
// Default account set to first account by default
|
||||||
ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
|
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
|
||||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
|
||||||
|
|
||||||
|
// Even when returning nothing
|
||||||
|
ap.set_new_dapps_addresses(Some(vec![])).unwrap();
|
||||||
|
// Default account is still returned
|
||||||
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
||||||
|
|
||||||
// change to all
|
// change to all
|
||||||
ap.set_new_dapps_whitelist(None).unwrap();
|
ap.set_new_dapps_addresses(None).unwrap();
|
||||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
||||||
|
|
||||||
// change to non-existent account
|
// change to non-existent account
|
||||||
ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap();
|
ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap();
|
||||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
||||||
|
|
||||||
// change to a whitelist
|
// change to a addresses
|
||||||
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
|
ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap();
|
||||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![1.into()]);
|
||||||
|
|
||||||
|
// it overrides default account
|
||||||
|
assert_eq!(ap.new_dapps_default_address().unwrap(), 1.into());
|
||||||
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
||||||
|
|
||||||
|
ap.set_new_dapps_default_address(address).unwrap();
|
||||||
|
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
|
||||||
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,13 +92,16 @@ impl AddressBook {
|
|||||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||||
pub struct DappsSettings {
|
pub struct DappsSettings {
|
||||||
/// A list of visible accounts
|
/// A list of visible accounts
|
||||||
pub accounts: Vec<Address>,
|
pub accounts: Option<Vec<Address>>,
|
||||||
|
/// Default account
|
||||||
|
pub default: Option<Address>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<JsonSettings> for DappsSettings {
|
impl From<JsonSettings> for DappsSettings {
|
||||||
fn from(s: JsonSettings) -> Self {
|
fn from(s: JsonSettings) -> Self {
|
||||||
DappsSettings {
|
DappsSettings {
|
||||||
accounts: s.accounts.into_iter().map(Into::into).collect(),
|
accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()),
|
||||||
|
default: s.default.map(Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +109,8 @@ impl From<JsonSettings> for DappsSettings {
|
|||||||
impl From<DappsSettings> for JsonSettings {
|
impl From<DappsSettings> for JsonSettings {
|
||||||
fn from(s: DappsSettings) -> Self {
|
fn from(s: DappsSettings) -> Self {
|
||||||
JsonSettings {
|
JsonSettings {
|
||||||
accounts: s.accounts.into_iter().map(Into::into).collect(),
|
accounts: s.accounts.map(|accounts| accounts.into_iter().map(Into::into).collect()),
|
||||||
|
default: s.default.map(Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,14 +118,18 @@ impl From<DappsSettings> for JsonSettings {
|
|||||||
/// Dapps user settings
|
/// Dapps user settings
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum NewDappsPolicy {
|
pub enum NewDappsPolicy {
|
||||||
AllAccounts,
|
AllAccounts {
|
||||||
|
default: Address,
|
||||||
|
},
|
||||||
Whitelist(Vec<Address>),
|
Whitelist(Vec<Address>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
||||||
fn from(s: JsonNewDappsPolicy) -> Self {
|
fn from(s: JsonNewDappsPolicy) -> Self {
|
||||||
match s {
|
match s {
|
||||||
JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts,
|
JsonNewDappsPolicy::AllAccounts { default } => NewDappsPolicy::AllAccounts {
|
||||||
|
default: default.into(),
|
||||||
|
},
|
||||||
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
|
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
|
||||||
accounts.into_iter().map(Into::into).collect()
|
accounts.into_iter().map(Into::into).collect()
|
||||||
),
|
),
|
||||||
@ -132,7 +140,9 @@ impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
|||||||
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
|
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
|
||||||
fn from(s: NewDappsPolicy) -> Self {
|
fn from(s: NewDappsPolicy) -> Self {
|
||||||
match s {
|
match s {
|
||||||
NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts,
|
NewDappsPolicy::AllAccounts { default } => JsonNewDappsPolicy::AllAccounts {
|
||||||
|
default: default.into(),
|
||||||
|
},
|
||||||
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
|
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
|
||||||
accounts.into_iter().map(Into::into).collect()
|
accounts.into_iter().map(Into::into).collect()
|
||||||
),
|
),
|
||||||
@ -230,7 +240,9 @@ impl DappsSettingsStore {
|
|||||||
|
|
||||||
/// Returns current new dapps policy
|
/// Returns current new dapps policy
|
||||||
pub fn policy(&self) -> NewDappsPolicy {
|
pub fn policy(&self) -> NewDappsPolicy {
|
||||||
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts)
|
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts {
|
||||||
|
default: 0.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns recent dapps with last accessed timestamp
|
/// Returns recent dapps with last accessed timestamp
|
||||||
@ -266,13 +278,22 @@ impl DappsSettingsStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets accounts for specific dapp.
|
/// 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: Option<Vec<Address>>) {
|
||||||
{
|
{
|
||||||
let mut settings = self.settings.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.settings.save(JsonSettings::write);
|
self.settings.save(JsonSettings::write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a default account for specific dapp.
|
||||||
|
pub fn set_default(&mut self, id: DappId, default: Address) {
|
||||||
|
{
|
||||||
|
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
|
||||||
|
settings.default = Some(default);
|
||||||
|
}
|
||||||
|
self.settings.save(JsonSettings::write);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disk-serializable HashMap
|
/// Disk-serializable HashMap
|
||||||
@ -385,13 +406,14 @@ mod tests {
|
|||||||
let mut b = DappsSettingsStore::new(&path);
|
let mut b = DappsSettingsStore::new(&path);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
b.set_accounts("dappOne".into(), Some(vec![1.into(), 2.into()]));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
let b = DappsSettingsStore::new(&path);
|
let b = DappsSettingsStore::new(&path);
|
||||||
assert_eq!(b.settings(), hash_map![
|
assert_eq!(b.settings(), hash_map![
|
||||||
"dappOne".into() => DappsSettings {
|
"dappOne".into() => DappsSettings {
|
||||||
accounts: vec![1.into(), 2.into()],
|
accounts: Some(vec![1.into(), 2.into()]),
|
||||||
|
default: None,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -422,7 +444,9 @@ mod tests {
|
|||||||
let mut store = DappsSettingsStore::new(&path);
|
let mut store = DappsSettingsStore::new(&path);
|
||||||
|
|
||||||
// Test default policy
|
// Test default policy
|
||||||
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
|
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts {
|
||||||
|
default: 0.into(),
|
||||||
|
});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
|
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
|
||||||
|
@ -191,7 +191,7 @@ pub struct BlockChain {
|
|||||||
blocks_blooms: RwLock<HashMap<LogGroupPosition, BloomGroup>>,
|
blocks_blooms: RwLock<HashMap<LogGroupPosition, BloomGroup>>,
|
||||||
block_receipts: RwLock<HashMap<H256, BlockReceipts>>,
|
block_receipts: RwLock<HashMap<H256, BlockReceipts>>,
|
||||||
|
|
||||||
db: Arc<Database>,
|
db: Arc<KeyValueDB>,
|
||||||
|
|
||||||
cache_man: Mutex<CacheManager<CacheId>>,
|
cache_man: Mutex<CacheManager<CacheId>>,
|
||||||
|
|
||||||
@ -421,7 +421,7 @@ impl<'a> Iterator for AncestryIter<'a> {
|
|||||||
|
|
||||||
impl BlockChain {
|
impl BlockChain {
|
||||||
/// Create new instance of blockchain from given Genesis.
|
/// Create new instance of blockchain from given Genesis.
|
||||||
pub fn new(config: Config, genesis: &[u8], db: Arc<Database>) -> BlockChain {
|
pub fn new(config: Config, genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain {
|
||||||
// 400 is the avarage size of the key
|
// 400 is the avarage size of the key
|
||||||
let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400);
|
let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400);
|
||||||
|
|
||||||
@ -467,7 +467,7 @@ impl BlockChain {
|
|||||||
children: vec![]
|
children: vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
batch.put(db::COL_HEADERS, &hash, block.header_rlp().as_raw());
|
batch.put(db::COL_HEADERS, &hash, block.header_rlp().as_raw());
|
||||||
batch.put(db::COL_BODIES, &hash, &Self::block_to_body(genesis));
|
batch.put(db::COL_BODIES, &hash, &Self::block_to_body(genesis));
|
||||||
|
|
||||||
@ -1314,7 +1314,7 @@ impl BlockChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn db(&self) -> &Arc<Database> {
|
pub fn db(&self) -> &Arc<KeyValueDB> {
|
||||||
&self.db
|
&self.db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1324,13 +1324,12 @@ mod tests {
|
|||||||
#![cfg_attr(feature="dev", allow(similar_names))]
|
#![cfg_attr(feature="dev", allow(similar_names))]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use rustc_serialize::hex::FromHex;
|
use rustc_serialize::hex::FromHex;
|
||||||
use util::{Database, DatabaseConfig};
|
use util::kvdb::KeyValueDB;
|
||||||
use util::hash::*;
|
use util::hash::*;
|
||||||
use util::sha3::Hashable;
|
use util::sha3::Hashable;
|
||||||
use receipt::Receipt;
|
use receipt::Receipt;
|
||||||
use blockchain::{BlockProvider, BlockChain, Config, ImportRoute};
|
use blockchain::{BlockProvider, BlockChain, Config, ImportRoute};
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use devtools::*;
|
|
||||||
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
||||||
use blockchain::extras::TransactionAddress;
|
use blockchain::extras::TransactionAddress;
|
||||||
use views::BlockView;
|
use views::BlockView;
|
||||||
@ -1339,11 +1338,11 @@ mod tests {
|
|||||||
use ethkey::Secret;
|
use ethkey::Secret;
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
|
|
||||||
fn new_db(path: &str) -> Arc<Database> {
|
fn new_db() -> Arc<KeyValueDB> {
|
||||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
Arc::new(::util::kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_chain(genesis: &[u8], db: Arc<Database>) -> BlockChain {
|
fn new_chain(genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain {
|
||||||
BlockChain::new(Config::default(), genesis, db)
|
BlockChain::new(Config::default(), genesis, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1355,13 +1354,12 @@ mod tests {
|
|||||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
let first = canon_chain.generate(&mut finalizer).unwrap();
|
let first = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
assert_eq!(bc.best_block_number(), 0);
|
assert_eq!(bc.best_block_number(), 0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let mut batch =db.transaction();
|
let mut batch = db.transaction();
|
||||||
bc.insert_block(&mut batch, &first, vec![]);
|
bc.insert_block(&mut batch, &first, vec![]);
|
||||||
assert_eq!(bc.best_block_number(), 0);
|
assert_eq!(bc.best_block_number(), 0);
|
||||||
bc.commit();
|
bc.commit();
|
||||||
@ -1381,8 +1379,7 @@ mod tests {
|
|||||||
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
||||||
let first_hash = BlockView::new(&first).header_view().sha3();
|
let first_hash = BlockView::new(&first).header_view().sha3();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
assert_eq!(bc.genesis_hash(), genesis_hash.clone());
|
assert_eq!(bc.genesis_hash(), genesis_hash.clone());
|
||||||
@ -1391,7 +1388,7 @@ mod tests {
|
|||||||
assert_eq!(bc.block_hash(1), None);
|
assert_eq!(bc.block_hash(1), None);
|
||||||
assert_eq!(bc.block_details(&genesis_hash).unwrap().children, vec![]);
|
assert_eq!(bc.block_details(&genesis_hash).unwrap().children, vec![]);
|
||||||
|
|
||||||
let mut batch =db.transaction();
|
let mut batch = db.transaction();
|
||||||
bc.insert_block(&mut batch, &first, vec![]);
|
bc.insert_block(&mut batch, &first, vec![]);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
bc.commit();
|
bc.commit();
|
||||||
@ -1412,8 +1409,7 @@ mod tests {
|
|||||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut block_hashes = vec![genesis_hash.clone()];
|
let mut block_hashes = vec![genesis_hash.clone()];
|
||||||
@ -1448,8 +1444,7 @@ mod tests {
|
|||||||
let b5b = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
let b5b = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
||||||
let b5a = canon_chain.generate(&mut finalizer).unwrap();
|
let b5a = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut batch =db.transaction();
|
let mut batch =db.transaction();
|
||||||
@ -1514,8 +1509,7 @@ mod tests {
|
|||||||
|
|
||||||
let t1_hash = t1.hash();
|
let t1_hash = t1.hash();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut batch = db.transaction();
|
let mut batch = db.transaction();
|
||||||
@ -1602,8 +1596,7 @@ mod tests {
|
|||||||
let t2_hash = t2.hash();
|
let t2_hash = t2.hash();
|
||||||
let t3_hash = t3.hash();
|
let t3_hash = t3.hash();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut batch = db.transaction();
|
let mut batch = db.transaction();
|
||||||
@ -1664,8 +1657,7 @@ mod tests {
|
|||||||
// b3a is a part of canon chain, whereas b3b is part of sidechain
|
// b3a is a part of canon chain, whereas b3b is part of sidechain
|
||||||
let best_block_hash = b3a_hash.clone();
|
let best_block_hash = b3a_hash.clone();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut batch = db.transaction();
|
let mut batch = db.transaction();
|
||||||
@ -1778,10 +1770,9 @@ mod tests {
|
|||||||
let first = canon_chain.generate(&mut finalizer).unwrap();
|
let first = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
||||||
let first_hash = BlockView::new(&first).header_view().sha3();
|
let first_hash = BlockView::new(&first).header_view().sha3();
|
||||||
|
let db = new_db();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
|
||||||
{
|
{
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
assert_eq!(bc.best_block_hash(), genesis_hash);
|
assert_eq!(bc.best_block_hash(), genesis_hash);
|
||||||
let mut batch =db.transaction();
|
let mut batch =db.transaction();
|
||||||
@ -1792,7 +1783,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
assert_eq!(bc.best_block_hash(), first_hash);
|
assert_eq!(bc.best_block_hash(), first_hash);
|
||||||
@ -1846,8 +1836,7 @@ mod tests {
|
|||||||
let b1 = "f904a8f901faa0ce1f26f798dd03c8782d63b3e42e79a64eaea5694ea686ac5d7ce3df5171d1aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0a65c2364cd0f1542d761823dc0109c6b072f14c20459598c5455c274601438f4a070616ebd7ad2ed6fb7860cf7e9df00163842351c38a87cac2c1cb193895035a2a05c5b4fc43c2d45787f54e1ae7d27afdb4ad16dfc567c5692070d5c4556e0b1d7b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000183023ec683021536845685109780a029f07836e4e59229b3a065913afc27702642c683bba689910b2b2fd45db310d3888957e6d004a31802f902a7f85f800a8255f094aaaf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca0575da4e21b66fa764be5f74da9389e67693d066fb0d1312e19e17e501da00ecda06baf5a5327595f6619dfc2fcb3f2e6fb410b5810af3cb52d0e7508038e91a188f85f010a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba04fa966bf34b93abc1bcd665554b7f316b50f928477b50be0f3285ead29d18c5ba017bba0eeec1625ab433746955e125d46d80b7fdc97386c51266f842d8e02192ef85f020a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca004377418ae981cc32b1312b4a427a1d69a821b28db8584f5f2bd8c6d42458adaa053a1dba1af177fac92f3b6af0a9fa46a22adf56e686c93794b6a012bf254abf5f85f030a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca04fe13febd28a05f4fcb2f451d7ddc2dda56486d9f8c79a62b0ba4da775122615a0651b2382dd402df9ebc27f8cb4b2e0f3cea68dda2dca0ee9603608f0b6f51668f85f040a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba078e6a0ba086a08f8450e208a399bb2f2d2a0d984acd2517c7c7df66ccfab567da013254002cd45a97fac049ae00afbc43ed0d9961d0c56a3b2382c80ce41c198ddf85f050a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba0a7174d8f43ea71c8e3ca9477691add8d80ac8e0ed89d8d8b572041eef81f4a54a0534ea2e28ec4da3b5b944b18c51ec84a5cf35f5b3343c5fb86521fd2d388f506f85f060a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba034bd04065833536a10c77ee2a43a5371bc6d34837088b861dd9d4b7f44074b59a078807715786a13876d3455716a6b9cb2186b7a4887a5c31160fc877454958616c0".from_hex().unwrap();
|
let b1 = "f904a8f901faa0ce1f26f798dd03c8782d63b3e42e79a64eaea5694ea686ac5d7ce3df5171d1aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0a65c2364cd0f1542d761823dc0109c6b072f14c20459598c5455c274601438f4a070616ebd7ad2ed6fb7860cf7e9df00163842351c38a87cac2c1cb193895035a2a05c5b4fc43c2d45787f54e1ae7d27afdb4ad16dfc567c5692070d5c4556e0b1d7b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000183023ec683021536845685109780a029f07836e4e59229b3a065913afc27702642c683bba689910b2b2fd45db310d3888957e6d004a31802f902a7f85f800a8255f094aaaf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca0575da4e21b66fa764be5f74da9389e67693d066fb0d1312e19e17e501da00ecda06baf5a5327595f6619dfc2fcb3f2e6fb410b5810af3cb52d0e7508038e91a188f85f010a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba04fa966bf34b93abc1bcd665554b7f316b50f928477b50be0f3285ead29d18c5ba017bba0eeec1625ab433746955e125d46d80b7fdc97386c51266f842d8e02192ef85f020a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca004377418ae981cc32b1312b4a427a1d69a821b28db8584f5f2bd8c6d42458adaa053a1dba1af177fac92f3b6af0a9fa46a22adf56e686c93794b6a012bf254abf5f85f030a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca04fe13febd28a05f4fcb2f451d7ddc2dda56486d9f8c79a62b0ba4da775122615a0651b2382dd402df9ebc27f8cb4b2e0f3cea68dda2dca0ee9603608f0b6f51668f85f040a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba078e6a0ba086a08f8450e208a399bb2f2d2a0d984acd2517c7c7df66ccfab567da013254002cd45a97fac049ae00afbc43ed0d9961d0c56a3b2382c80ce41c198ddf85f050a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba0a7174d8f43ea71c8e3ca9477691add8d80ac8e0ed89d8d8b572041eef81f4a54a0534ea2e28ec4da3b5b944b18c51ec84a5cf35f5b3343c5fb86521fd2d388f506f85f060a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba034bd04065833536a10c77ee2a43a5371bc6d34837088b861dd9d4b7f44074b59a078807715786a13876d3455716a6b9cb2186b7a4887a5c31160fc877454958616c0".from_hex().unwrap();
|
||||||
let b1_hash: H256 = "f53f268d23a71e85c7d6d83a9504298712b84c1a2ba220441c86eeda0bf0b6e3".into();
|
let b1_hash: H256 = "f53f268d23a71e85c7d6d83a9504298712b84c1a2ba220441c86eeda0bf0b6e3".into();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
let mut batch =db.transaction();
|
let mut batch =db.transaction();
|
||||||
bc.insert_block(&mut batch, &b1, vec![]);
|
bc.insert_block(&mut batch, &b1, vec![]);
|
||||||
@ -1861,7 +1850,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_block(db: &Arc<Database>, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
|
fn insert_block(db: &Arc<KeyValueDB>, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
|
||||||
let mut batch = db.transaction();
|
let mut batch = db.transaction();
|
||||||
let res = bc.insert_block(&mut batch, bytes, receipts);
|
let res = bc.insert_block(&mut batch, bytes, receipts);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
@ -1906,8 +1895,7 @@ mod tests {
|
|||||||
let b1 = canon_chain.with_transaction(t1).with_transaction(t2).generate(&mut finalizer).unwrap();
|
let b1 = canon_chain.with_transaction(t1).with_transaction(t2).generate(&mut finalizer).unwrap();
|
||||||
let b2 = canon_chain.with_transaction(t3).generate(&mut finalizer).unwrap();
|
let b2 = canon_chain.with_transaction(t3).generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
insert_block(&db, &bc, &b1, vec![Receipt {
|
insert_block(&db, &bc, &b1, vec![Receipt {
|
||||||
state_root: Some(H256::default()),
|
state_root: Some(H256::default()),
|
||||||
@ -2015,8 +2003,7 @@ mod tests {
|
|||||||
let b1a = canon_chain.with_bloom(bloom_ba.clone()).generate(&mut finalizer).unwrap();
|
let b1a = canon_chain.with_bloom(bloom_ba.clone()).generate(&mut finalizer).unwrap();
|
||||||
let b2a = canon_chain.with_bloom(bloom_ba.clone()).generate(&mut finalizer).unwrap();
|
let b2a = canon_chain.with_bloom(bloom_ba.clone()).generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5);
|
let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5);
|
||||||
@ -2070,14 +2057,12 @@ mod tests {
|
|||||||
let mut finalizer = BlockFinalizer::default();
|
let mut finalizer = BlockFinalizer::default();
|
||||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
|
|
||||||
{
|
{
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
||||||
|
|
||||||
let mut batch =db.transaction();
|
let mut batch = db.transaction();
|
||||||
// create a longer fork
|
// create a longer fork
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let canon_block = canon_chain.generate(&mut finalizer).unwrap();
|
let canon_block = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
@ -2092,8 +2077,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// re-loading the blockchain should load the correct best block.
|
// re-loading the blockchain should load the correct best block.
|
||||||
let db = new_db(temp.as_str());
|
let bc = new_chain(&genesis, db);
|
||||||
let bc = new_chain(&genesis, db.clone());
|
|
||||||
assert_eq!(bc.best_block_number(), 5);
|
assert_eq!(bc.best_block_number(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2108,8 +2092,7 @@ mod tests {
|
|||||||
let first_hash = BlockView::new(&first).header_view().sha3();
|
let first_hash = BlockView::new(&first).header_view().sha3();
|
||||||
let second_hash = BlockView::new(&second).header_view().sha3();
|
let second_hash = BlockView::new(&second).header_view().sha3();
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let bc = new_chain(&genesis, db.clone());
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
|
||||||
let mut batch =db.transaction();
|
let mut batch =db.transaction();
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
use std::collections::{HashSet, HashMap, BTreeMap, VecDeque};
|
use std::collections::{HashSet, HashMap, BTreeMap, VecDeque};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::path::{Path};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
||||||
use std::time::{Instant};
|
use std::time::{Instant};
|
||||||
@ -135,7 +134,7 @@ pub struct Client {
|
|||||||
engine: Arc<Engine>,
|
engine: Arc<Engine>,
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
pruning: journaldb::Algorithm,
|
pruning: journaldb::Algorithm,
|
||||||
db: RwLock<Arc<Database>>,
|
db: RwLock<Arc<KeyValueDB>>,
|
||||||
state_db: Mutex<StateDB>,
|
state_db: Mutex<StateDB>,
|
||||||
block_queue: BlockQueue,
|
block_queue: BlockQueue,
|
||||||
report: RwLock<ClientReport>,
|
report: RwLock<ClientReport>,
|
||||||
@ -157,18 +156,16 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Create a new client with given spec and DB path and custom verifier.
|
/// Create a new client with given parameters.
|
||||||
|
/// The database is assumed to have been initialized with the correct columns.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
spec: &Spec,
|
spec: &Spec,
|
||||||
path: &Path,
|
db: Arc<KeyValueDB>,
|
||||||
miner: Arc<Miner>,
|
miner: Arc<Miner>,
|
||||||
message_channel: IoChannel<ClientIoMessage>,
|
message_channel: IoChannel<ClientIoMessage>,
|
||||||
db_config: &DatabaseConfig,
|
|
||||||
) -> Result<Arc<Client>, ClientError> {
|
) -> Result<Arc<Client>, ClientError> {
|
||||||
|
|
||||||
let path = path.to_path_buf();
|
|
||||||
let db = Arc::new(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database)?);
|
|
||||||
let trie_spec = match config.fat_db {
|
let trie_spec = match config.fat_db {
|
||||||
true => TrieSpec::Fat,
|
true => TrieSpec::Fat,
|
||||||
false => TrieSpec::Secure,
|
false => TrieSpec::Secure,
|
||||||
@ -186,7 +183,7 @@ impl Client {
|
|||||||
if state_db.journal_db().is_empty() {
|
if state_db.journal_db().is_empty() {
|
||||||
// Sets the correct state root.
|
// Sets the correct state root.
|
||||||
state_db = spec.ensure_db_good(state_db, &factories)?;
|
state_db = spec.ensure_db_good(state_db, &factories)?;
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash())?;
|
state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash())?;
|
||||||
db.write(batch).map_err(ClientError::Database)?;
|
db.write(batch).map_err(ClientError::Database)?;
|
||||||
}
|
}
|
||||||
@ -530,7 +527,7 @@ impl Client {
|
|||||||
|
|
||||||
// Commit results
|
// Commit results
|
||||||
let receipts = ::rlp::decode(&receipts_bytes);
|
let receipts = ::rlp::decode(&receipts_bytes);
|
||||||
let mut batch = DBTransaction::new(&self.db.read());
|
let mut batch = DBTransaction::new();
|
||||||
chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true);
|
chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true);
|
||||||
// Final commit to the DB
|
// Final commit to the DB
|
||||||
self.db.read().write_buffered(batch);
|
self.db.read().write_buffered(batch);
|
||||||
@ -554,7 +551,7 @@ impl Client {
|
|||||||
|
|
||||||
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
|
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
|
||||||
|
|
||||||
let mut batch = DBTransaction::new(&self.db.read());
|
let mut batch = DBTransaction::new();
|
||||||
// CHECK! I *think* this is fine, even if the state_root is equal to another
|
// CHECK! I *think* this is fine, even if the state_root is equal to another
|
||||||
// already-imported block of the same number.
|
// already-imported block of the same number.
|
||||||
// TODO: Prove it with a test.
|
// TODO: Prove it with a test.
|
||||||
@ -603,7 +600,7 @@ impl Client {
|
|||||||
trace!(target: "client", "Pruning state for ancient era {}", era);
|
trace!(target: "client", "Pruning state for ancient era {}", era);
|
||||||
match chain.block_hash(era) {
|
match chain.block_hash(era) {
|
||||||
Some(ancient_hash) => {
|
Some(ancient_hash) => {
|
||||||
let mut batch = DBTransaction::new(&self.db.read());
|
let mut batch = DBTransaction::new();
|
||||||
state_db.mark_canonical(&mut batch, era, &ancient_hash)?;
|
state_db.mark_canonical(&mut batch, era, &ancient_hash)?;
|
||||||
self.db.read().write_buffered(batch);
|
self.db.read().write_buffered(batch);
|
||||||
state_db.journal_db().flush();
|
state_db.journal_db().flush();
|
||||||
@ -1691,7 +1688,7 @@ mod tests {
|
|||||||
let go_thread = go.clone();
|
let go_thread = go.clone();
|
||||||
let another_client = client.reference().clone();
|
let another_client = client.reference().clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone());
|
let mut batch = DBTransaction::new();
|
||||||
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new());
|
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new());
|
||||||
go_thread.store(true, Ordering::SeqCst);
|
go_thread.store(true, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use util::{DBTransaction, Database, RwLock};
|
use util::{DBTransaction, KeyValueDB, RwLock};
|
||||||
|
|
||||||
use rlp;
|
use rlp;
|
||||||
|
|
||||||
@ -34,10 +34,12 @@ pub const COL_BODIES: Option<u32> = Some(2);
|
|||||||
pub const COL_EXTRA: Option<u32> = Some(3);
|
pub const COL_EXTRA: Option<u32> = Some(3);
|
||||||
/// Column for Traces
|
/// Column for Traces
|
||||||
pub const COL_TRACE: Option<u32> = Some(4);
|
pub const COL_TRACE: Option<u32> = Some(4);
|
||||||
/// Column for Traces
|
/// Column for the empty accounts bloom filter.
|
||||||
pub const COL_ACCOUNT_BLOOM: Option<u32> = Some(5);
|
pub const COL_ACCOUNT_BLOOM: Option<u32> = Some(5);
|
||||||
|
/// Column for general information from the local node which can persist.
|
||||||
|
pub const COL_NODE_INFO: Option<u32> = Some(6);
|
||||||
/// Number of columns in DB
|
/// Number of columns in DB
|
||||||
pub const NUM_COLUMNS: Option<u32> = Some(6);
|
pub const NUM_COLUMNS: Option<u32> = Some(7);
|
||||||
|
|
||||||
/// Modes for updating caches.
|
/// Modes for updating caches.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -212,7 +214,7 @@ impl Writable for DBTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Readable for Database {
|
impl<KVDB: KeyValueDB + ?Sized> Readable for KVDB {
|
||||||
fn read<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> Option<T> where T: rlp::Decodable, R: Deref<Target = [u8]> {
|
fn read<T, R>(&self, col: Option<u32>, key: &Key<T, Target = R>) -> Option<T> where T: rlp::Decodable, R: Deref<Target = [u8]> {
|
||||||
let result = self.get(col, &key.key());
|
let result = self.get(col, &key.key());
|
||||||
|
|
||||||
|
@ -220,8 +220,8 @@ impl Engine for AuthorityRound {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_sealer(&self, author: &Address) -> Option<bool> {
|
fn seals_internally(&self) -> Option<bool> {
|
||||||
Some(self.validators.contains(author))
|
Some(self.validators.contains(&self.signer.address()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to seal the block internally.
|
/// Attempt to seal the block internally.
|
||||||
|
@ -103,8 +103,8 @@ impl Engine for BasicAuthority {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_sealer(&self, author: &Address) -> Option<bool> {
|
fn seals_internally(&self) -> Option<bool> {
|
||||||
Some(self.validators.contains(author))
|
Some(self.validators.contains(&self.signer.address()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to seal the block internally.
|
/// Attempt to seal the block internally.
|
||||||
@ -268,7 +268,8 @@ mod tests {
|
|||||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||||
|
|
||||||
let engine = new_test_authority().engine;
|
let engine = new_test_authority().engine;
|
||||||
assert!(!engine.is_sealer(&Address::default()).unwrap());
|
assert!(!engine.seals_internally().unwrap());
|
||||||
assert!(engine.is_sealer(&authority).unwrap());
|
engine.set_signer(Arc::new(tap), authority, "".into());
|
||||||
|
assert!(engine.seals_internally().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ impl Engine for InstantSeal {
|
|||||||
Schedule::new_post_eip150(usize::max_value(), true, true, true)
|
Schedule::new_post_eip150(usize::max_value(), true, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) }
|
fn seals_internally(&self) -> Option<bool> { Some(true) }
|
||||||
|
|
||||||
fn generate_seal(&self, _block: &ExecutedBlock) -> Seal {
|
fn generate_seal(&self, _block: &ExecutedBlock) -> Seal {
|
||||||
Seal::Regular(Vec::new())
|
Seal::Regular(Vec::new())
|
||||||
|
@ -128,11 +128,10 @@ pub trait Engine : Sync + Send {
|
|||||||
/// Block transformation functions, after the transactions.
|
/// Block transformation functions, after the transactions.
|
||||||
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
|
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
|
||||||
|
|
||||||
/// If Some(true) this author is able to generate seals, generate_seal has to be implemented.
|
/// None means that it requires external input (e.g. PoW) to seal a block.
|
||||||
/// None indicates that this Engine never seals internally regardless of author (e.g. PoW).
|
/// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator).
|
||||||
fn is_sealer(&self, _author: &Address) -> Option<bool> { None }
|
/// Some(false) means that the node might seal internally but is not qualified now.
|
||||||
/// Checks if default address is able to seal.
|
fn seals_internally(&self) -> Option<bool> { None }
|
||||||
fn is_default_sealer(&self) -> Option<bool> { self.is_sealer(&Default::default()) }
|
|
||||||
/// Attempt to seal the block internally.
|
/// Attempt to seal the block internally.
|
||||||
///
|
///
|
||||||
/// If `Some` is returned, then you get a valid seal.
|
/// If `Some` is returned, then you get a valid seal.
|
||||||
|
@ -410,8 +410,8 @@ impl Engine for Tendermint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Should this node participate.
|
/// Should this node participate.
|
||||||
fn is_sealer(&self, address: &Address) -> Option<bool> {
|
fn seals_internally(&self) -> Option<bool> {
|
||||||
Some(self.is_authority(address))
|
Some(self.is_authority(&self.signer.address()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to seal generate a proposal seal.
|
/// Attempt to seal generate a proposal seal.
|
||||||
@ -649,8 +649,7 @@ mod tests {
|
|||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::{Engine, EngineError, Seal};
|
use engines::{Engine, EngineError, Seal};
|
||||||
use super::{Step, View, Height, message_info_rlp, message_full_rlp};
|
use super::*;
|
||||||
use super::message::VoteStep;
|
|
||||||
|
|
||||||
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
|
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
|
||||||
fn setup() -> (Spec, Arc<AccountProvider>) {
|
fn setup() -> (Spec, Arc<AccountProvider>) {
|
||||||
|
@ -19,7 +19,6 @@ use client::{BlockChainClient, Client, ClientConfig};
|
|||||||
use block::Block;
|
use block::Block;
|
||||||
use ethereum;
|
use ethereum;
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use devtools::*;
|
|
||||||
use spec::Genesis;
|
use spec::Genesis;
|
||||||
use ethjson;
|
use ethjson;
|
||||||
use miner::Miner;
|
use miner::Miner;
|
||||||
@ -58,16 +57,14 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
|
|||||||
spec
|
spec
|
||||||
};
|
};
|
||||||
|
|
||||||
let temp = RandomTempPath::new();
|
|
||||||
{
|
{
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db = Arc::new(::util::kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&spec,
|
&spec,
|
||||||
temp.as_path(),
|
db,
|
||||||
Arc::new(Miner::with_spec(&spec)),
|
Arc::new(Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
for b in &blockchain.blocks_rlp() {
|
for b in &blockchain.blocks_rlp() {
|
||||||
if Block::is_good(&b) {
|
if Block::is_good(&b) {
|
||||||
|
@ -26,3 +26,6 @@ pub use self::v9::Extract;
|
|||||||
|
|
||||||
mod v10;
|
mod v10;
|
||||||
pub use self::v10::ToV10;
|
pub use self::v10::ToV10;
|
||||||
|
|
||||||
|
mod v11;
|
||||||
|
pub use self::v11::ToV11;
|
||||||
|
@ -70,7 +70,7 @@ pub fn generate_bloom(source: Arc<Database>, dest: &mut Database) -> Result<(),
|
|||||||
|
|
||||||
trace!(target: "migration", "Generated {} bloom updates", bloom_journal.entries.len());
|
trace!(target: "migration", "Generated {} bloom updates", bloom_journal.entries.len());
|
||||||
|
|
||||||
let mut batch = DBTransaction::new(dest);
|
let mut batch = DBTransaction::new();
|
||||||
StateDB::commit_bloom(&mut batch, bloom_journal).map_err(|_| Error::Custom("Failed to commit bloom".to_owned()))?;
|
StateDB::commit_bloom(&mut batch, bloom_journal).map_err(|_| Error::Custom("Failed to commit bloom".to_owned()))?;
|
||||||
dest.write(batch)?;
|
dest.write(batch)?;
|
||||||
|
|
||||||
|
46
ethcore/src/migrations/v11.rs
Normal file
46
ethcore/src/migrations/v11.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
//! Adds a seventh column for node information.
|
||||||
|
|
||||||
|
use util::kvdb::Database;
|
||||||
|
use util::migration::{Batch, Config, Error, Migration, Progress};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Copies over data for all existing columns.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ToV11(Progress);
|
||||||
|
|
||||||
|
|
||||||
|
impl Migration for ToV11 {
|
||||||
|
fn pre_columns(&self) -> Option<u32> { Some(6) }
|
||||||
|
fn columns(&self) -> Option<u32> { Some(7) }
|
||||||
|
|
||||||
|
fn version(&self) -> u32 { 11 }
|
||||||
|
|
||||||
|
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
||||||
|
// just copy everything over.
|
||||||
|
let mut batch = Batch::new(config, col);
|
||||||
|
|
||||||
|
for (key, value) in source.iter(col) {
|
||||||
|
self.0.tick();
|
||||||
|
batch.insert(key.to_vec(), value.to_vec(), dest)?
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.commit(dest)
|
||||||
|
}
|
||||||
|
}
|
@ -215,8 +215,6 @@ pub struct Miner {
|
|||||||
sealing_block_last_request: Mutex<u64>,
|
sealing_block_last_request: Mutex<u64>,
|
||||||
// for sealing...
|
// for sealing...
|
||||||
options: MinerOptions,
|
options: MinerOptions,
|
||||||
/// Does the node perform internal (without work) sealing.
|
|
||||||
pub seals_internally: bool,
|
|
||||||
|
|
||||||
gas_range_target: RwLock<(U256, U256)>,
|
gas_range_target: RwLock<(U256, U256)>,
|
||||||
author: RwLock<Address>,
|
author: RwLock<Address>,
|
||||||
@ -275,9 +273,8 @@ impl Miner {
|
|||||||
queue: UsingQueue::new(options.work_queue_size),
|
queue: UsingQueue::new(options.work_queue_size),
|
||||||
enabled: options.force_sealing
|
enabled: options.force_sealing
|
||||||
|| !options.new_work_notify.is_empty()
|
|| !options.new_work_notify.is_empty()
|
||||||
|| spec.engine.is_default_sealer().unwrap_or(false)
|
|| spec.engine.seals_internally().is_some()
|
||||||
}),
|
}),
|
||||||
seals_internally: spec.engine.is_default_sealer().is_some(),
|
|
||||||
gas_range_target: RwLock::new((U256::zero(), U256::zero())),
|
gas_range_target: RwLock::new((U256::zero(), U256::zero())),
|
||||||
author: RwLock::new(Address::default()),
|
author: RwLock::new(Address::default()),
|
||||||
extra_data: RwLock::new(Vec::new()),
|
extra_data: RwLock::new(Vec::new()),
|
||||||
@ -455,7 +452,7 @@ impl Miner {
|
|||||||
let last_request = *self.sealing_block_last_request.lock();
|
let last_request = *self.sealing_block_last_request.lock();
|
||||||
let should_disable_sealing = !self.forced_sealing()
|
let should_disable_sealing = !self.forced_sealing()
|
||||||
&& !has_local_transactions
|
&& !has_local_transactions
|
||||||
&& !self.seals_internally
|
&& self.engine.seals_internally().is_none()
|
||||||
&& best_block > last_request
|
&& best_block > last_request
|
||||||
&& best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS;
|
&& best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS;
|
||||||
|
|
||||||
@ -765,21 +762,21 @@ impl MinerService for Miner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_author(&self, author: Address) {
|
fn set_author(&self, author: Address) {
|
||||||
if self.seals_internally {
|
if self.engine.seals_internally().is_some() {
|
||||||
let mut sealing_work = self.sealing_work.lock();
|
let mut sealing_work = self.sealing_work.lock();
|
||||||
sealing_work.enabled = self.engine.is_sealer(&author).unwrap_or(false);
|
sealing_work.enabled = true;
|
||||||
}
|
}
|
||||||
*self.author.write() = author;
|
*self.author.write() = author;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> {
|
fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> {
|
||||||
if self.seals_internally {
|
if self.engine.seals_internally().is_some() {
|
||||||
if let Some(ref ap) = self.accounts {
|
if let Some(ref ap) = self.accounts {
|
||||||
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
|
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
|
||||||
// Limit the scope of the locks.
|
// Limit the scope of the locks.
|
||||||
{
|
{
|
||||||
let mut sealing_work = self.sealing_work.lock();
|
let mut sealing_work = self.sealing_work.lock();
|
||||||
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
|
sealing_work.enabled = true;
|
||||||
*self.author.write() = address;
|
*self.author.write() = address;
|
||||||
}
|
}
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
@ -914,7 +911,7 @@ impl MinerService for Miner {
|
|||||||
if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() {
|
if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() {
|
||||||
// Make sure to do it after transaction is imported and lock is droped.
|
// Make sure to do it after transaction is imported and lock is droped.
|
||||||
// We need to create pending block and enable sealing.
|
// We need to create pending block and enable sealing.
|
||||||
if self.seals_internally || !self.prepare_work_sealing(chain) {
|
if self.engine.seals_internally().unwrap_or(false) || !self.prepare_work_sealing(chain) {
|
||||||
// If new block has not been prepared (means we already had one)
|
// If new block has not been prepared (means we already had one)
|
||||||
// or Engine might be able to seal internally,
|
// or Engine might be able to seal internally,
|
||||||
// we need to update sealing.
|
// we need to update sealing.
|
||||||
@ -984,7 +981,7 @@ impl MinerService for Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<SignedTransaction> {
|
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<PendingTransaction> {
|
||||||
let queue = self.transaction_queue.lock();
|
let queue = self.transaction_queue.lock();
|
||||||
match self.options.pending_set {
|
match self.options.pending_set {
|
||||||
PendingSet::AlwaysQueue => queue.find(hash),
|
PendingSet::AlwaysQueue => queue.find(hash),
|
||||||
@ -992,14 +989,14 @@ impl MinerService for Miner {
|
|||||||
self.from_pending_block(
|
self.from_pending_block(
|
||||||
best_block,
|
best_block,
|
||||||
|| queue.find(hash),
|
|| queue.find(hash),
|
||||||
|sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned()
|
|sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
PendingSet::AlwaysSealing => {
|
PendingSet::AlwaysSealing => {
|
||||||
self.from_pending_block(
|
self.from_pending_block(
|
||||||
best_block,
|
best_block,
|
||||||
|| None,
|
|| None,
|
||||||
|sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned()
|
|sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1071,14 +1068,18 @@ impl MinerService for Miner {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
trace!(target: "miner", "update_sealing: preparing a block");
|
trace!(target: "miner", "update_sealing: preparing a block");
|
||||||
let (block, original_work_hash) = self.prepare_block(chain);
|
let (block, original_work_hash) = self.prepare_block(chain);
|
||||||
if self.seals_internally {
|
match self.engine.seals_internally() {
|
||||||
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
Some(true) => {
|
||||||
if self.seal_and_import_block_internally(chain, block) {
|
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
||||||
trace!(target: "miner", "update_sealing: imported internally sealed block");
|
if self.seal_and_import_block_internally(chain, block) {
|
||||||
}
|
trace!(target: "miner", "update_sealing: imported internally sealed block");
|
||||||
} else {
|
}
|
||||||
trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work");
|
},
|
||||||
self.prepare_work(block, original_work_hash);
|
None => {
|
||||||
|
trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work");
|
||||||
|
self.prepare_work(block, original_work_hash)
|
||||||
|
},
|
||||||
|
_ => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync {
|
|||||||
where F: FnOnce(&ClosedBlock) -> T, Self: Sized;
|
where F: FnOnce(&ClosedBlock) -> T, Self: Sized;
|
||||||
|
|
||||||
/// Query pending transactions for hash.
|
/// Query pending transactions for hash.
|
||||||
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<SignedTransaction>;
|
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<PendingTransaction>;
|
||||||
|
|
||||||
/// Get a list of all pending transactions in the queue.
|
/// Get a list of all pending transactions in the queue.
|
||||||
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
||||||
|
@ -1109,7 +1109,7 @@ impl TransactionQueue {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all ready transactions.
|
/// Return all future transactions.
|
||||||
pub fn future_transactions(&self) -> Vec<PendingTransaction> {
|
pub fn future_transactions(&self) -> Vec<PendingTransaction> {
|
||||||
self.future.by_priority
|
self.future.by_priority
|
||||||
.iter()
|
.iter()
|
||||||
@ -1137,8 +1137,8 @@ impl TransactionQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finds transaction in the queue by hash (if any)
|
/// Finds transaction in the queue by hash (if any)
|
||||||
pub fn find(&self, hash: &H256) -> Option<SignedTransaction> {
|
pub fn find(&self, hash: &H256) -> Option<PendingTransaction> {
|
||||||
self.by_hash.get(hash).map(|tx| tx.transaction.clone())
|
self.by_hash.get(hash).map(|tx| PendingTransaction { transaction: tx.transaction.clone(), condition: tx.condition.clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all elements (in any state) from the queue
|
/// Removes all elements (in any state) from the queue
|
||||||
|
@ -57,6 +57,7 @@ pub struct ClientService {
|
|||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
snapshot: Arc<SnapshotService>,
|
snapshot: Arc<SnapshotService>,
|
||||||
panic_handler: Arc<PanicHandler>,
|
panic_handler: Arc<PanicHandler>,
|
||||||
|
database: Arc<Database>,
|
||||||
_stop_guard: ::devtools::StopGuard,
|
_stop_guard: ::devtools::StopGuard,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +89,14 @@ impl ClientService {
|
|||||||
db_config.compaction = config.db_compaction.compaction_profile(client_path);
|
db_config.compaction = config.db_compaction.compaction_profile(client_path);
|
||||||
db_config.wal = config.db_wal;
|
db_config.wal = config.db_wal;
|
||||||
|
|
||||||
|
let db = Arc::new(Database::open(
|
||||||
|
&db_config,
|
||||||
|
&client_path.to_str().expect("DB path could not be converted to string.")
|
||||||
|
).map_err(::client::Error::Database)?);
|
||||||
|
|
||||||
|
|
||||||
let pruning = config.pruning;
|
let pruning = config.pruning;
|
||||||
let client = Client::new(config, &spec, client_path, miner, io_service.channel(), &db_config)?;
|
let client = Client::new(config, &spec, db.clone(), miner, io_service.channel())?;
|
||||||
|
|
||||||
let snapshot_params = SnapServiceParams {
|
let snapshot_params = SnapServiceParams {
|
||||||
engine: spec.engine.clone(),
|
engine: spec.engine.clone(),
|
||||||
@ -119,15 +126,11 @@ impl ClientService {
|
|||||||
client: client,
|
client: client,
|
||||||
snapshot: snapshot,
|
snapshot: snapshot,
|
||||||
panic_handler: panic_handler,
|
panic_handler: panic_handler,
|
||||||
|
database: db,
|
||||||
_stop_guard: stop_guard,
|
_stop_guard: stop_guard,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a node to network
|
|
||||||
pub fn add_node(&mut self, _enode: &str) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get general IO interface
|
/// Get general IO interface
|
||||||
pub fn register_io_handler(&self, handler: Arc<IoHandler<ClientIoMessage> + Send>) -> Result<(), IoError> {
|
pub fn register_io_handler(&self, handler: Arc<IoHandler<ClientIoMessage> + Send>) -> Result<(), IoError> {
|
||||||
self.io_service.register_handler(handler)
|
self.io_service.register_handler(handler)
|
||||||
@ -152,6 +155,9 @@ impl ClientService {
|
|||||||
pub fn add_notify(&self, notify: Arc<ChainNotify>) {
|
pub fn add_notify(&self, notify: Arc<ChainNotify>) {
|
||||||
self.client.add_notify(notify);
|
self.client.add_notify(notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the database.
|
||||||
|
pub fn db(&self) -> Arc<KeyValueDB> { self.database.clone() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MayPanic for ClientService {
|
impl MayPanic for ClientService {
|
||||||
|
@ -27,7 +27,7 @@ use tests::helpers::generate_dummy_client_with_spec_and_data;
|
|||||||
|
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
use io::IoChannel;
|
use io::IoChannel;
|
||||||
use util::kvdb::DatabaseConfig;
|
use util::kvdb::{Database, DatabaseConfig};
|
||||||
|
|
||||||
struct NoopDBRestore;
|
struct NoopDBRestore;
|
||||||
|
|
||||||
@ -54,15 +54,15 @@ fn restored_is_equivalent() {
|
|||||||
path.push("snapshot");
|
path.push("snapshot");
|
||||||
|
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Database::open(&db_config, client_db.to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
let spec = Spec::new_null();
|
let spec = Spec::new_null();
|
||||||
let client2 = Client::new(
|
let client2 = Client::new(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
&spec,
|
&spec,
|
||||||
&client_db,
|
Arc::new(client_db),
|
||||||
Arc::new(::miner::Miner::with_spec(&spec)),
|
Arc::new(::miner::Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let service_params = ServiceParams {
|
let service_params = ServiceParams {
|
||||||
@ -140,4 +140,4 @@ fn guards_delete_folders() {
|
|||||||
|
|
||||||
drop(service);
|
drop(service);
|
||||||
assert!(!path.exists());
|
assert!(!path.exists());
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@ use std::collections::{VecDeque, HashSet};
|
|||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use util::cache::MemoryLruCache;
|
use util::cache::MemoryLruCache;
|
||||||
use util::journaldb::JournalDB;
|
use util::journaldb::JournalDB;
|
||||||
|
use util::kvdb::KeyValueDB;
|
||||||
use util::hash::{H256};
|
use util::hash::{H256};
|
||||||
use util::hashdb::HashDB;
|
use util::hashdb::HashDB;
|
||||||
use state::Account;
|
use state::Account;
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
use util::{Arc, Address, Database, DBTransaction, UtilError, Mutex, Hashable};
|
use util::{Arc, Address, DBTransaction, UtilError, Mutex, Hashable};
|
||||||
use bloom_journal::{Bloom, BloomJournal};
|
use bloom_journal::{Bloom, BloomJournal};
|
||||||
use db::COL_ACCOUNT_BLOOM;
|
use db::COL_ACCOUNT_BLOOM;
|
||||||
use byteorder::{LittleEndian, ByteOrder};
|
use byteorder::{LittleEndian, ByteOrder};
|
||||||
@ -116,7 +117,7 @@ impl StateDB {
|
|||||||
// TODO: make the cache size actually accurate by moving the account storage cache
|
// TODO: make the cache size actually accurate by moving the account storage cache
|
||||||
// into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`.
|
// into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`.
|
||||||
pub fn new(db: Box<JournalDB>, cache_size: usize) -> StateDB {
|
pub fn new(db: Box<JournalDB>, cache_size: usize) -> StateDB {
|
||||||
let bloom = Self::load_bloom(db.backing());
|
let bloom = Self::load_bloom(&**db.backing());
|
||||||
let acc_cache_size = cache_size * ACCOUNT_CACHE_RATIO / 100;
|
let acc_cache_size = cache_size * ACCOUNT_CACHE_RATIO / 100;
|
||||||
let code_cache_size = cache_size - acc_cache_size;
|
let code_cache_size = cache_size - acc_cache_size;
|
||||||
let cache_items = acc_cache_size / ::std::mem::size_of::<Option<Account>>();
|
let cache_items = acc_cache_size / ::std::mem::size_of::<Option<Account>>();
|
||||||
@ -139,7 +140,7 @@ impl StateDB {
|
|||||||
|
|
||||||
/// Loads accounts bloom from the database
|
/// Loads accounts bloom from the database
|
||||||
/// This bloom is used to handle request for the non-existant account fast
|
/// This bloom is used to handle request for the non-existant account fast
|
||||||
pub fn load_bloom(db: &Database) -> Bloom {
|
pub fn load_bloom(db: &KeyValueDB) -> Bloom {
|
||||||
let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY)
|
let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY)
|
||||||
.expect("Low-level database error");
|
.expect("Low-level database error");
|
||||||
|
|
||||||
@ -477,7 +478,7 @@ mod tests {
|
|||||||
let h2b = H256::random();
|
let h2b = H256::random();
|
||||||
let h3a = H256::random();
|
let h3a = H256::random();
|
||||||
let h3b = H256::random();
|
let h3b = H256::random();
|
||||||
let mut batch = DBTransaction::new(state_db.journal_db().backing());
|
let mut batch = DBTransaction::new();
|
||||||
|
|
||||||
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
||||||
// balance [ 5 5 4 3 2 2 ]
|
// balance [ 5 5 4 3 2 2 ]
|
||||||
|
@ -36,14 +36,14 @@ fn imports_from_empty() {
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let spec = get_test_spec();
|
let spec = get_test_spec();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&spec,
|
&spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec(&spec)),
|
Arc::new(Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
client.import_verified_blocks();
|
client.import_verified_blocks();
|
||||||
client.flush_queue();
|
client.flush_queue();
|
||||||
@ -54,14 +54,14 @@ fn should_return_registrar() {
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let spec = ethereum::new_morden();
|
let spec = ethereum::new_morden();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&spec,
|
&spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec(&spec)),
|
Arc::new(Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let params = client.additional_params();
|
let params = client.additional_params();
|
||||||
let address = ¶ms["registrar"];
|
let address = ¶ms["registrar"];
|
||||||
@ -85,14 +85,14 @@ fn imports_good_block() {
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let spec = get_test_spec();
|
let spec = get_test_spec();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&spec,
|
&spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec(&spec)),
|
Arc::new(Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let good_block = get_good_dummy_block();
|
let good_block = get_good_dummy_block();
|
||||||
if client.import_block(good_block).is_err() {
|
if client.import_block(good_block).is_err() {
|
||||||
@ -110,14 +110,14 @@ fn query_none_block() {
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let spec = get_test_spec();
|
let spec = get_test_spec();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&spec,
|
&spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec(&spec)),
|
Arc::new(Miner::with_spec(&spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let non_existant = client.block_header(BlockId::Number(188));
|
let non_existant = client.block_header(BlockId::Number(188));
|
||||||
assert!(non_existant.is_none());
|
assert!(non_existant.is_none());
|
||||||
@ -276,10 +276,19 @@ fn change_history_size() {
|
|||||||
let test_spec = Spec::new_null();
|
let test_spec = Spec::new_null();
|
||||||
let mut config = ClientConfig::default();
|
let mut config = ClientConfig::default();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
config.history = 2;
|
config.history = 2;
|
||||||
let address = Address::random();
|
let address = Address::random();
|
||||||
{
|
{
|
||||||
let client = Client::new(ClientConfig::default(), &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
|
let client = Client::new(
|
||||||
|
ClientConfig::default(),
|
||||||
|
&test_spec,
|
||||||
|
client_db.clone(),
|
||||||
|
Arc::new(Miner::with_spec(&test_spec)),
|
||||||
|
IoChannel::disconnected()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
for _ in 0..20 {
|
for _ in 0..20 {
|
||||||
let mut b = client.prepare_open_block(Address::default(), (3141562.into(), 31415620.into()), vec![]);
|
let mut b = client.prepare_open_block(Address::default(), (3141562.into(), 31415620.into()), vec![]);
|
||||||
b.block_mut().fields_mut().state.add_balance(&address, &5.into(), CleanupMode::NoEmpty);
|
b.block_mut().fields_mut().state.add_balance(&address, &5.into(), CleanupMode::NoEmpty);
|
||||||
@ -290,7 +299,13 @@ fn change_history_size() {
|
|||||||
}
|
}
|
||||||
let mut config = ClientConfig::default();
|
let mut config = ClientConfig::default();
|
||||||
config.history = 10;
|
config.history = 10;
|
||||||
let client = Client::new(config, &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
|
let client = Client::new(
|
||||||
|
config,
|
||||||
|
&test_spec,
|
||||||
|
client_db,
|
||||||
|
Arc::new(Miner::with_spec(&test_spec)),
|
||||||
|
IoChannel::disconnected(),
|
||||||
|
).unwrap();
|
||||||
assert_eq!(client.state().balance(&address), 100.into());
|
assert_eq!(client.state().balance(&address), 100.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,14 +154,14 @@ pub fn generate_dummy_client_with_spec_accounts_and_data<F>(get_test_spec: F, ac
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let test_spec = get_test_spec();
|
let test_spec = get_test_spec();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&test_spec,
|
&test_spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)),
|
Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let test_engine = &*test_spec.engine;
|
let test_engine = &*test_spec.engine;
|
||||||
|
|
||||||
@ -260,14 +260,14 @@ pub fn get_test_client_with_blocks(blocks: Vec<Bytes>) -> GuardedTempResult<Arc<
|
|||||||
let dir = RandomTempPath::new();
|
let dir = RandomTempPath::new();
|
||||||
let test_spec = get_test_spec();
|
let test_spec = get_test_spec();
|
||||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||||
|
let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap());
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&test_spec,
|
&test_spec,
|
||||||
dir.as_path(),
|
client_db,
|
||||||
Arc::new(Miner::with_spec(&test_spec)),
|
Arc::new(Miner::with_spec(&test_spec)),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
&db_config
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
for block in &blocks {
|
for block in &blocks {
|
||||||
|
@ -20,7 +20,7 @@ use std::collections::{HashMap, VecDeque};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use bloomchain::{Number, Config as BloomConfig};
|
use bloomchain::{Number, Config as BloomConfig};
|
||||||
use bloomchain::group::{BloomGroupDatabase, BloomGroupChain, GroupPosition, BloomGroup};
|
use bloomchain::group::{BloomGroupDatabase, BloomGroupChain, GroupPosition, BloomGroup};
|
||||||
use util::{H256, H264, Database, DBTransaction, RwLock, HeapSizeOf};
|
use util::{H256, H264, KeyValueDB, DBTransaction, RwLock, HeapSizeOf};
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
use trace::{LocalizedTrace, Config, Filter, Database as TraceDatabase, ImportRequest, DatabaseExtras};
|
use trace::{LocalizedTrace, Config, Filter, Database as TraceDatabase, ImportRequest, DatabaseExtras};
|
||||||
use db::{self, Key, Writable, Readable, CacheUpdatePolicy};
|
use db::{self, Key, Writable, Readable, CacheUpdatePolicy};
|
||||||
@ -106,7 +106,7 @@ pub struct TraceDB<T> where T: DatabaseExtras {
|
|||||||
blooms: RwLock<HashMap<TraceGroupPosition, blooms::BloomGroup>>,
|
blooms: RwLock<HashMap<TraceGroupPosition, blooms::BloomGroup>>,
|
||||||
cache_manager: RwLock<CacheManager<CacheId>>,
|
cache_manager: RwLock<CacheManager<CacheId>>,
|
||||||
// db
|
// db
|
||||||
tracesdb: Arc<Database>,
|
tracesdb: Arc<KeyValueDB>,
|
||||||
// config,
|
// config,
|
||||||
bloom_config: BloomConfig,
|
bloom_config: BloomConfig,
|
||||||
// tracing enabled
|
// tracing enabled
|
||||||
@ -126,8 +126,8 @@ impl<T> BloomGroupDatabase for TraceDB<T> where T: DatabaseExtras {
|
|||||||
|
|
||||||
impl<T> TraceDB<T> where T: DatabaseExtras {
|
impl<T> TraceDB<T> where T: DatabaseExtras {
|
||||||
/// Creates new instance of `TraceDB`.
|
/// Creates new instance of `TraceDB`.
|
||||||
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
|
pub fn new(config: Config, tracesdb: Arc<KeyValueDB>, extras: Arc<T>) -> Self {
|
||||||
let mut batch = DBTransaction::new(&tracesdb);
|
let mut batch = DBTransaction::new();
|
||||||
let genesis = extras.block_hash(0)
|
let genesis = extras.block_hash(0)
|
||||||
.expect("Genesis block is always inserted upon extras db creation qed");
|
.expect("Genesis block is always inserted upon extras db creation qed");
|
||||||
batch.write(db::COL_TRACE, &genesis, &FlatBlockTraces::default());
|
batch.write(db::COL_TRACE, &genesis, &FlatBlockTraces::default());
|
||||||
@ -404,8 +404,7 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::{Address, U256, H256, Database, DatabaseConfig, DBTransaction};
|
use util::{Address, U256, H256, DBTransaction};
|
||||||
use devtools::RandomTempPath;
|
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
use trace::{Config, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest};
|
use trace::{Config, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest};
|
||||||
use trace::{Filter, LocalizedTrace, AddressesFilter, TraceError};
|
use trace::{Filter, LocalizedTrace, AddressesFilter, TraceError};
|
||||||
@ -455,14 +454,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_db(path: &str) -> Arc<Database> {
|
fn new_db() -> Arc<::util::kvdb::KeyValueDB> {
|
||||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
Arc::new(::util::kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reopening_db_with_tracing_off() {
|
fn test_reopening_db_with_tracing_off() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
|
|
||||||
// set autotracing
|
// set autotracing
|
||||||
@ -476,8 +474,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reopening_db_with_tracing_on() {
|
fn test_reopening_db_with_tracing_on() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
|
|
||||||
// set tracing on
|
// set tracing on
|
||||||
@ -555,8 +552,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_import_non_canon_traces() {
|
fn test_import_non_canon_traces() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.enabled = true;
|
config.enabled = true;
|
||||||
let block_0 = H256::from(0xa1);
|
let block_0 = H256::from(0xa1);
|
||||||
@ -574,7 +570,7 @@ mod tests {
|
|||||||
|
|
||||||
// import block 0
|
// import block 0
|
||||||
let request = create_noncanon_import_request(0, block_0.clone());
|
let request = create_noncanon_import_request(0, block_0.clone());
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
tracedb.import(&mut batch, request);
|
tracedb.import(&mut batch, request);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
|
|
||||||
@ -584,8 +580,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_import() {
|
fn test_import() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.enabled = true;
|
config.enabled = true;
|
||||||
let block_1 = H256::from(0xa1);
|
let block_1 = H256::from(0xa1);
|
||||||
@ -605,7 +600,7 @@ mod tests {
|
|||||||
|
|
||||||
// import block 1
|
// import block 1
|
||||||
let request = create_simple_import_request(1, block_1.clone());
|
let request = create_simple_import_request(1, block_1.clone());
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
tracedb.import(&mut batch, request);
|
tracedb.import(&mut batch, request);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
|
|
||||||
@ -621,7 +616,7 @@ mod tests {
|
|||||||
|
|
||||||
// import block 2
|
// import block 2
|
||||||
let request = create_simple_import_request(2, block_2.clone());
|
let request = create_simple_import_request(2, block_2.clone());
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
tracedb.import(&mut batch, request);
|
tracedb.import(&mut batch, request);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
|
|
||||||
@ -664,8 +659,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn query_trace_after_reopen() {
|
fn query_trace_after_reopen() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
let mut extras = Extras::default();
|
let mut extras = Extras::default();
|
||||||
let block_0 = H256::from(0xa1);
|
let block_0 = H256::from(0xa1);
|
||||||
@ -684,7 +678,7 @@ mod tests {
|
|||||||
|
|
||||||
// import block 1
|
// import block 1
|
||||||
let request = create_simple_import_request(1, block_0.clone());
|
let request = create_simple_import_request(1, block_0.clone());
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new();
|
||||||
tracedb.import(&mut batch, request);
|
tracedb.import(&mut batch, request);
|
||||||
db.write(batch).unwrap();
|
db.write(batch).unwrap();
|
||||||
}
|
}
|
||||||
@ -698,8 +692,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn query_genesis() {
|
fn query_genesis() {
|
||||||
let temp = RandomTempPath::new();
|
let db = new_db();
|
||||||
let db = new_db(temp.as_str());
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
let mut extras = Extras::default();
|
let mut extras = Extras::default();
|
||||||
let block_0 = H256::from(0xa1);
|
let block_0 = H256::from(0xa1);
|
||||||
|
@ -16,23 +16,33 @@ Ethereum key management.
|
|||||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ethstore insert <secret> <password> [--dir DIR]
|
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore list [--dir DIR]
|
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore import [--src DIR] [--dir DIR]
|
ethstore import [--src DIR] [--dir DIR]
|
||||||
ethstore import-wallet <path> <password> [--dir DIR]
|
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore remove <address> <password> [--dir DIR]
|
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore sign <address> <password> <message> [--dir DIR]
|
ethstore sign <address> <password> <message> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore public <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore list-vaults [--dir DIR]
|
||||||
|
ethstore create-vault <vault> <password> [--dir DIR]
|
||||||
|
ethstore change-vault-pwd <vault> <old-pwd> <new-pwd> [--dir DIR]
|
||||||
|
ethstore move-to-vault <address> <vault> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore move-from-vault <address> <vault> <password> [--dir DIR]
|
||||||
ethstore [-h | --help]
|
ethstore [-h | --help]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display this message and exit.
|
-h, --help Display this message and exit.
|
||||||
--dir DIR Specify the secret store directory. It may be either
|
--dir DIR Specify the secret store directory. It may be either
|
||||||
parity, parity-test, geth, geth-test
|
parity, parity-test, geth, geth-test
|
||||||
or a path [default: parity].
|
or a path [default: parity].
|
||||||
--src DIR Specify import source. It may be either
|
--vault VAULT Specify vault to use in this operation.
|
||||||
parity, parity-test, get, geth-test
|
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||||
or a path [default: geth].
|
that this option is required when vault option is set.
|
||||||
|
Otherwise it is ignored.
|
||||||
|
--src DIR Specify import source. It may be either
|
||||||
|
parity, parity-test, get, geth-test
|
||||||
|
or a path [default: geth].
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
insert Save account with password.
|
insert Save account with password.
|
||||||
@ -42,16 +52,24 @@ Commands:
|
|||||||
import-wallet Import presale wallet.
|
import-wallet Import presale wallet.
|
||||||
remove Remove account.
|
remove Remove account.
|
||||||
sign Sign message.
|
sign Sign message.
|
||||||
|
public Displays public key for an address.
|
||||||
|
list-vaults List vaults.
|
||||||
|
create-vault Create new vault.
|
||||||
|
change-vault-pwd Change vault password.
|
||||||
|
move-to-vault Move account to vault from another vault/root directory.
|
||||||
|
move-from-vault Move account to root directory from given vault or root.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
#### `insert <secret> <password> [--dir DIR]`
|
#### `insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*Encrypt secret with a password and save it in secret store.*
|
*Encrypt secret with a password and save it in secret store.*
|
||||||
|
|
||||||
- `<secret>` - ethereum secret, 32 bytes long
|
- `<secret>` - ethereum secret, 32 bytes long
|
||||||
- `<password>` - account password, file path
|
- `<password>` - account password, file path
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 password.txt
|
ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 password.txt
|
||||||
@ -73,13 +91,15 @@ ethstore insert `ethkey generate random -s` "this is sparta"
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
#### `change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]`
|
#### `change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*Change account password.*
|
*Change account password.*
|
||||||
|
|
||||||
- `<address>` - ethereum address, 20 bytes long
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
- `<old-pwd>` - old account password, file path
|
- `<old-pwd>` - old account password, file path
|
||||||
- `<new-pwd>` - new account password, file path
|
- `<new-pwd>` - new account password, file path
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e old_pwd.txt new_pwd.txt
|
ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e old_pwd.txt new_pwd.txt
|
||||||
@ -91,10 +111,12 @@ true
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
#### `list [--dir DIR]`
|
#### `list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*List secret store accounts.*
|
*List secret store accounts.*
|
||||||
|
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore list
|
ethstore list
|
||||||
@ -125,12 +147,14 @@ ethstore import
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
#### `import-wallet <path> <password> [--dir DIR]`
|
#### `import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*Import account from presale wallet.*
|
*Import account from presale wallet.*
|
||||||
|
|
||||||
- `<path>` - presale wallet path
|
- `<path>` - presale wallet path
|
||||||
- `<password>` - account password, file path
|
- `<password>` - account password, file path
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore import-wallet ethwallet.json password.txt
|
ethstore import-wallet ethwallet.json password.txt
|
||||||
@ -142,12 +166,14 @@ e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
#### `remove <address> <password> [--dir DIR]`
|
#### `remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*Remove account from secret store.*
|
*Remove account from secret store.*
|
||||||
|
|
||||||
- `<address>` - ethereum address, 20 bytes long
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
- `<password>` - account password, file path
|
- `<password>` - account password, file path
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e password.txt
|
ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e password.txt
|
||||||
@ -159,13 +185,15 @@ true
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
#### `sign <address> <password> <message> [--dir DIR]`
|
#### `sign <address> <password> <message> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
*Sign message with account's secret.*
|
*Sign message with account's secret.*
|
||||||
|
|
||||||
- `<address>` - ethereum address, 20 bytes long
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
- `<password>` - account password, file path
|
- `<password>` - account password, file path
|
||||||
- `<message>` - message to sign, 32 bytes long
|
- `<message>` - message to sign, 32 bytes long
|
||||||
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
```
|
```
|
||||||
ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 password.txt 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
|
ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 password.txt 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
|
||||||
@ -177,6 +205,119 @@ c6649f9555232d90ff716d7e552a744c5af771574425a74860e12f763479eb1b708c1f3a7dc0a0a7
|
|||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
|
#### `public <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
|
*Displays public key for an address.*
|
||||||
|
|
||||||
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
|
- `<password>` - account password, file path
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - vault to use in this operation
|
||||||
|
- `[--vault-pwd VAULTPWD]` - vault password to use in this operation, file path
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore public 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea account_password.txt --vault vault_name --vault-pwd vault_password.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
0x84161d8c05a996a534efbec50f24485cfcc07458efaef749a1b22156d7836c903eeb39bf2df74676e702eacc4cfdde069e5fd86692b5ef6ef81ba906e9e77d82
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
#### `list-vaults [--dir DIR]`
|
||||||
|
*List vaults.*
|
||||||
|
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore list-vaults
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
vault1
|
||||||
|
vault2
|
||||||
|
vault3
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
#### `create-vault <vault> <password> [--dir DIR]`
|
||||||
|
*Create new vault.*
|
||||||
|
|
||||||
|
- `<vault>` - name of new vault. This can only contain letters, digits, whitespaces, dashes and underscores
|
||||||
|
- `<password>` - vault password, file path
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore create-vault vault3 vault3_password.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
#### `change-vault-pwd <vault> <old-pwd> <new-pwd> [--dir DIR]`
|
||||||
|
*Change vault password.*
|
||||||
|
|
||||||
|
- `<vault>` - name of existing vault
|
||||||
|
- `<old-pwd>` - old vault password, file path
|
||||||
|
- `<new-pwd>` - new vault password, file path
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore change-vault-pwd vault3 vault3_password.txt new_vault3_password.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
#### `move-to-vault <address> <vault> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||||
|
*Move account to vault from another vault/root directory.*
|
||||||
|
|
||||||
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
|
- `<vault>` - name of existing vault to move account to
|
||||||
|
- `<password>` - password of existing `<vault>` to move account to, file path
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
- `[--vault VAULT]` - current vault of the `<address>` argument, if set
|
||||||
|
- `[--vault-pwd VAULTPWD]` - password for the current vault of the `<address>` argument, if any. file path
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault3 vault3_password.txt
|
||||||
|
ethstore move-to-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt --vault vault3 --vault-pwd vault3_password.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
OK
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
#### `move-from-vault <address> <vault> <password> [--dir DIR]`
|
||||||
|
*Move account to root directory from given vault.*
|
||||||
|
|
||||||
|
- `<address>` - ethereum address, 20 bytes long
|
||||||
|
- `<vault>` - name of existing vault to move account to
|
||||||
|
- `<password>` - password of existing `<vault>` to move account to, file path
|
||||||
|
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
ethstore move-from-vault 00e63fdb87ceb815ec96ae185b8f7381a0b4a5ea vault1 vault1_password.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
# Ethcore toolchain
|
# Ethcore toolchain
|
||||||
*this project is a part of the ethcore toolchain*
|
*this project is a part of the ethcore toolchain*
|
||||||
|
|
||||||
|
@ -31,24 +31,33 @@ Ethereum key management.
|
|||||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ethstore insert <secret> <password> [--dir DIR]
|
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore list [--dir DIR]
|
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore import [--src DIR] [--dir DIR]
|
ethstore import [--src DIR] [--dir DIR]
|
||||||
ethstore import-wallet <path> <password> [--dir DIR]
|
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore remove <address> <password> [--dir DIR]
|
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore sign <address> <password> <message> [--dir DIR]
|
ethstore sign <address> <password> <message> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
ethstore public <address> <password>
|
ethstore public <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore list-vaults [--dir DIR]
|
||||||
|
ethstore create-vault <vault> <password> [--dir DIR]
|
||||||
|
ethstore change-vault-pwd <vault> <old-pwd> <new-pwd> [--dir DIR]
|
||||||
|
ethstore move-to-vault <address> <vault> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||||
|
ethstore move-from-vault <address> <vault> <password> [--dir DIR]
|
||||||
ethstore [-h | --help]
|
ethstore [-h | --help]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display this message and exit.
|
-h, --help Display this message and exit.
|
||||||
--dir DIR Specify the secret store directory. It may be either
|
--dir DIR Specify the secret store directory. It may be either
|
||||||
parity, parity-test, geth, geth-test
|
parity, parity-test, geth, geth-test
|
||||||
or a path [default: parity].
|
or a path [default: parity].
|
||||||
--src DIR Specify import source. It may be either
|
--vault VAULT Specify vault to use in this operation.
|
||||||
parity, parity-test, get, geth-test
|
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||||
or a path [default: geth].
|
that this option is required when vault option is set.
|
||||||
|
Otherwise it is ignored.
|
||||||
|
--src DIR Specify import source. It may be either
|
||||||
|
parity, parity-test, get, geth-test
|
||||||
|
or a path [default: geth].
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
insert Save account with password.
|
insert Save account with password.
|
||||||
@ -59,6 +68,11 @@ Commands:
|
|||||||
remove Remove account.
|
remove Remove account.
|
||||||
sign Sign message.
|
sign Sign message.
|
||||||
public Displays public key for an address.
|
public Displays public key for an address.
|
||||||
|
list-vaults List vaults.
|
||||||
|
create-vault Create new vault.
|
||||||
|
change-vault-pwd Change vault password.
|
||||||
|
move-to-vault Move account to vault from another vault/root directory.
|
||||||
|
move-from-vault Move account to root directory from given vault.
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[derive(Debug, RustcDecodable)]
|
#[derive(Debug, RustcDecodable)]
|
||||||
@ -71,6 +85,11 @@ struct Args {
|
|||||||
cmd_remove: bool,
|
cmd_remove: bool,
|
||||||
cmd_sign: bool,
|
cmd_sign: bool,
|
||||||
cmd_public: bool,
|
cmd_public: bool,
|
||||||
|
cmd_list_vaults: bool,
|
||||||
|
cmd_create_vault: bool,
|
||||||
|
cmd_change_vault_pwd: bool,
|
||||||
|
cmd_move_to_vault: bool,
|
||||||
|
cmd_move_from_vault: bool,
|
||||||
arg_secret: String,
|
arg_secret: String,
|
||||||
arg_password: String,
|
arg_password: String,
|
||||||
arg_old_pwd: String,
|
arg_old_pwd: String,
|
||||||
@ -78,8 +97,11 @@ struct Args {
|
|||||||
arg_address: String,
|
arg_address: String,
|
||||||
arg_message: String,
|
arg_message: String,
|
||||||
arg_path: String,
|
arg_path: String,
|
||||||
|
arg_vault: String,
|
||||||
flag_src: String,
|
flag_src: String,
|
||||||
flag_dir: String,
|
flag_dir: String,
|
||||||
|
flag_vault: String,
|
||||||
|
flag_vault_pwd: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -104,6 +126,23 @@ fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
|
|||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Error> {
|
||||||
|
if args.flag_vault.is_empty() {
|
||||||
|
return Ok(SecretVaultRef::Root);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vault_pwd = load_password(&args.flag_vault_pwd)?;
|
||||||
|
store.open_vault(&args.flag_vault, &vault_pwd)?;
|
||||||
|
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_args_vault_account(store: &EthStore, address: Address, args: &Args) -> Result<StoreAccountRef, Error> {
|
||||||
|
match open_args_vault(store, args)? {
|
||||||
|
SecretVaultRef::Root => Ok(StoreAccountRef::root(address)),
|
||||||
|
SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn format_accounts(accounts: &[Address]) -> String {
|
fn format_accounts(accounts: &[Address]) -> String {
|
||||||
accounts.iter()
|
accounts.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -112,10 +151,14 @@ fn format_accounts(accounts: &[Address]) -> String {
|
|||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_vaults(vaults: &[String]) -> String {
|
||||||
|
vaults.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
fn load_password(path: &str) -> Result<String, Error> {
|
fn load_password(path: &str) -> Result<String, Error> {
|
||||||
let mut file = fs::File::open(path)?;
|
let mut file = fs::File::open(path).map_err(|e| Error::Custom(format!("Error opening password file {}: {}", path, e)))?;
|
||||||
let mut password = String::new();
|
let mut password = String::new();
|
||||||
file.read_to_string(&mut password)?;
|
file.read_to_string(&mut password).map_err(|e| Error::Custom(format!("Error reading password file {}: {}", path, e)))?;
|
||||||
// drop EOF
|
// drop EOF
|
||||||
let _ = password.pop();
|
let _ = password.pop();
|
||||||
Ok(password)
|
Ok(password)
|
||||||
@ -131,17 +174,24 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
|||||||
return if args.cmd_insert {
|
return if args.cmd_insert {
|
||||||
let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?;
|
let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let address = store.insert_account(SecretVaultRef::Root, secret, &password)?;
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
|
let address = store.insert_account(vault_ref, secret, &password)?;
|
||||||
Ok(format!("0x{:?}", address))
|
Ok(format!("0x{:?}", address))
|
||||||
} else if args.cmd_change_pwd {
|
} else if args.cmd_change_pwd {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
let ok = store.change_password(&StoreAccountRef::root(address), &old_pwd, &new_pwd).is_ok();
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let ok = store.change_password(&account_ref, &old_pwd, &new_pwd).is_ok();
|
||||||
Ok(format!("{}", ok))
|
Ok(format!("{}", ok))
|
||||||
} else if args.cmd_list {
|
} else if args.cmd_list {
|
||||||
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
let accounts = store.accounts()?;
|
let accounts = store.accounts()?;
|
||||||
let accounts: Vec<_> = accounts.into_iter().map(|a| a.address).collect();
|
let accounts: Vec<_> = accounts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|a| &a.vault == &vault_ref)
|
||||||
|
.map(|a| a.address)
|
||||||
|
.collect();
|
||||||
Ok(format_accounts(&accounts))
|
Ok(format_accounts(&accounts))
|
||||||
} else if args.cmd_import {
|
} else if args.cmd_import {
|
||||||
let src = key_dir(&args.flag_src)?;
|
let src = key_dir(&args.flag_src)?;
|
||||||
@ -152,24 +202,54 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
|||||||
let wallet = PresaleWallet::open(&args.arg_path)?;
|
let wallet = PresaleWallet::open(&args.arg_path)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let kp = wallet.decrypt(&password)?;
|
let kp = wallet.decrypt(&password)?;
|
||||||
let address = store.insert_account(SecretVaultRef::Root, kp.secret().clone(), &password)?;
|
let vault_ref = open_args_vault(&store, &args)?;
|
||||||
|
let address = store.insert_account(vault_ref, kp.secret().clone(), &password)?;
|
||||||
Ok(format!("0x{:?}", address))
|
Ok(format!("0x{:?}", address))
|
||||||
} else if args.cmd_remove {
|
} else if args.cmd_remove {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let ok = store.remove_account(&StoreAccountRef::root(address), &password).is_ok();
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let ok = store.remove_account(&account_ref, &password).is_ok();
|
||||||
Ok(format!("{}", ok))
|
Ok(format!("{}", ok))
|
||||||
} else if args.cmd_sign {
|
} else if args.cmd_sign {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?;
|
let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let signature = store.sign(&StoreAccountRef::root(address), &password, &message)?;
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let signature = store.sign(&account_ref, &password, &message)?;
|
||||||
Ok(format!("0x{:?}", signature))
|
Ok(format!("0x{:?}", signature))
|
||||||
} else if args.cmd_public {
|
} else if args.cmd_public {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let public = store.public(&StoreAccountRef::root(address), &password)?;
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
let public = store.public(&account_ref, &password)?;
|
||||||
Ok(format!("0x{:?}", public))
|
Ok(format!("0x{:?}", public))
|
||||||
|
} else if args.cmd_list_vaults {
|
||||||
|
let vaults = store.list_vaults()?;
|
||||||
|
Ok(format_vaults(&vaults))
|
||||||
|
} else if args.cmd_create_vault {
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
store.create_vault(&args.arg_vault, &password)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_change_vault_pwd {
|
||||||
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
|
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||||
|
store.open_vault(&args.arg_vault, &old_pwd)?;
|
||||||
|
store.change_vault_password(&args.arg_vault, &new_pwd)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_move_to_vault {
|
||||||
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||||
|
store.open_vault(&args.arg_vault, &password)?;
|
||||||
|
store.change_account_vault(SecretVaultRef::Vault(args.arg_vault), account_ref)?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
|
} else if args.cmd_move_from_vault {
|
||||||
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
|
let password = load_password(&args.arg_password)?;
|
||||||
|
store.open_vault(&args.arg_vault, &password)?;
|
||||||
|
store.change_account_vault(SecretVaultRef::Root, StoreAccountRef::vault(&args.arg_vault, address))?;
|
||||||
|
Ok("OK".to_owned())
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("{}", USAGE))
|
Ok(format!("{}", USAGE))
|
||||||
}
|
}
|
||||||
|
@ -90,11 +90,8 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// all accounts found in keys directory
|
fn files(&self) -> Result<Vec<PathBuf>, Error> {
|
||||||
fn files(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
Ok(fs::read_dir(&self.path)?
|
||||||
// it's not done using one iterator cause
|
|
||||||
// there is an issue with rustc and it takes tooo much time to compile
|
|
||||||
let paths = fs::read_dir(&self.path)?
|
|
||||||
.flat_map(Result::ok)
|
.flat_map(Result::ok)
|
||||||
.filter(|entry| {
|
.filter(|entry| {
|
||||||
let metadata = entry.metadata().ok();
|
let metadata = entry.metadata().ok();
|
||||||
@ -102,14 +99,34 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
|||||||
let name = file_name.to_string_lossy();
|
let name = file_name.to_string_lossy();
|
||||||
// filter directories
|
// filter directories
|
||||||
metadata.map_or(false, |m| !m.is_dir()) &&
|
metadata.map_or(false, |m| !m.is_dir()) &&
|
||||||
// hidden files
|
// hidden files
|
||||||
!name.starts_with(".") &&
|
!name.starts_with(".") &&
|
||||||
// other ignored files
|
// other ignored files
|
||||||
!IGNORED_FILES.contains(&&*name)
|
!IGNORED_FILES.contains(&&*name)
|
||||||
})
|
})
|
||||||
.map(|entry| entry.path())
|
.map(|entry| entry.path())
|
||||||
.collect::<Vec<PathBuf>>();
|
.collect::<Vec<PathBuf>>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn files_hash(&self) -> Result<u64, Error> {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
let files = self.files()?;
|
||||||
|
for file in files {
|
||||||
|
hasher.write(file.to_str().unwrap_or("").as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// all accounts found in keys directory
|
||||||
|
fn files_content(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
||||||
|
// it's not done using one iterator cause
|
||||||
|
// there is an issue with rustc and it takes tooo much time to compile
|
||||||
|
let paths = self.files()?;
|
||||||
Ok(paths
|
Ok(paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
@ -166,7 +183,7 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
|||||||
|
|
||||||
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||||
let accounts = self.files()?
|
let accounts = self.files_content()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, account)| account)
|
.map(|(_, account)| account)
|
||||||
.collect();
|
.collect();
|
||||||
@ -191,7 +208,7 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
|||||||
fn remove(&self, account: &SafeAccount) -> 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 = self.files()?
|
let to_remove = self.files_content()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
|
||||||
|
|
||||||
@ -207,6 +224,10 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
|||||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
|
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.files_hash()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
||||||
@ -279,7 +300,6 @@ mod test {
|
|||||||
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
||||||
let res = directory.insert(account);
|
let res = directory.insert(account);
|
||||||
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(res.is_ok(), "Should save account succesfuly.");
|
assert!(res.is_ok(), "Should save account succesfuly.");
|
||||||
assert!(res.unwrap().filename.is_some(), "Filename has been assigned.");
|
assert!(res.unwrap().filename.is_some(), "Filename has been assigned.");
|
||||||
@ -336,4 +356,25 @@ mod test {
|
|||||||
assert!(vaults.iter().any(|v| &*v == "vault1"));
|
assert!(vaults.iter().any(|v| &*v == "vault1"));
|
||||||
assert!(vaults.iter().any(|v| &*v == "vault2"));
|
assert!(vaults.iter().any(|v| &*v == "vault2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_of_files() {
|
||||||
|
let temp_path = RandomTempPath::new();
|
||||||
|
let directory = RootDiskDirectory::create(&temp_path).unwrap();
|
||||||
|
|
||||||
|
let hash = directory.files_hash().expect("Files hash should be calculated ok");
|
||||||
|
assert_eq!(
|
||||||
|
hash,
|
||||||
|
15130871412783076140
|
||||||
|
);
|
||||||
|
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
let password = "test pass";
|
||||||
|
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
||||||
|
directory.insert(account).expect("Account should be inserted ok");
|
||||||
|
|
||||||
|
let new_hash = directory.files_hash().expect("New files hash should be calculated ok");
|
||||||
|
|
||||||
|
assert!(new_hash != hash, "hash of the file list should change once directory content changed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,4 +95,8 @@ impl KeyDirectory for GethDirectory {
|
|||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(account)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.dir.unique_repr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,5 +63,12 @@ impl KeyDirectory for MemoryDirectory {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
let mut val = 0u64;
|
||||||
|
let accounts = self.accounts.read();
|
||||||
|
for acc in accounts.keys() { val = val ^ ::util::FixedHash::low_u64(acc) }
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,8 @@ pub trait KeyDirectory: Send + Sync {
|
|||||||
fn path(&self) -> Option<&PathBuf> { None }
|
fn path(&self) -> Option<&PathBuf> { None }
|
||||||
/// Return vault provider, if available
|
/// Return vault provider, if available
|
||||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None }
|
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None }
|
||||||
|
/// Unique representation of directory account collection
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vaults provider
|
/// Vaults provider
|
||||||
|
@ -74,4 +74,8 @@ impl KeyDirectory for ParityDirectory {
|
|||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(account)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.dir.unique_repr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,7 @@ pub struct EthMultiStore {
|
|||||||
// order lock: cache, then vaults
|
// order lock: cache, then vaults
|
||||||
cache: RwLock<BTreeMap<StoreAccountRef, Vec<SafeAccount>>>,
|
cache: RwLock<BTreeMap<StoreAccountRef, Vec<SafeAccount>>>,
|
||||||
vaults: Mutex<HashMap<String, Box<VaultKeyDirectory>>>,
|
vaults: Mutex<HashMap<String, Box<VaultKeyDirectory>>>,
|
||||||
|
dir_hash: Mutex<Option<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthMultiStore {
|
impl EthMultiStore {
|
||||||
@ -244,11 +245,23 @@ impl EthMultiStore {
|
|||||||
vaults: Mutex::new(HashMap::new()),
|
vaults: Mutex::new(HashMap::new()),
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
cache: Default::default(),
|
cache: Default::default(),
|
||||||
|
dir_hash: Default::default(),
|
||||||
};
|
};
|
||||||
store.reload_accounts()?;
|
store.reload_accounts()?;
|
||||||
Ok(store)
|
Ok(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reload_if_changed(&self) -> Result<(), Error> {
|
||||||
|
let mut last_dir_hash = self.dir_hash.lock();
|
||||||
|
let dir_hash = Some(self.dir.unique_repr()?);
|
||||||
|
if *last_dir_hash == dir_hash {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
self.reload_accounts()?;
|
||||||
|
*last_dir_hash = dir_hash;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn reload_accounts(&self) -> Result<(), Error> {
|
fn reload_accounts(&self) -> Result<(), Error> {
|
||||||
let mut cache = self.cache.write();
|
let mut cache = self.cache.write();
|
||||||
|
|
||||||
@ -284,7 +297,7 @@ impl EthMultiStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.reload_accounts()?;
|
self.reload_if_changed()?;
|
||||||
let cache = self.cache.read();
|
let cache = self.cache.read();
|
||||||
let accounts = cache.get(account).ok_or(Error::InvalidAccount)?;
|
let accounts = cache.get(account).ok_or(Error::InvalidAccount)?;
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
@ -431,7 +444,7 @@ impl SimpleSecretStore for EthMultiStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
||||||
self.reload_accounts()?;
|
self.reload_if_changed()?;
|
||||||
self.cache.read().keys()
|
self.cache.read().keys()
|
||||||
.find(|r| &r.address == address)
|
.find(|r| &r.address == address)
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -439,7 +452,7 @@ impl SimpleSecretStore for EthMultiStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
|
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
|
||||||
self.reload_accounts()?;
|
self.reload_if_changed()?;
|
||||||
Ok(self.cache.read().keys().cloned().collect())
|
Ok(self.cache.read().keys().cloned().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,4 +74,8 @@ impl KeyDirectory for TransientDir {
|
|||||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(account)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unique_repr(&self) -> Result<u64, Error> {
|
||||||
|
self.dir.unique_repr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,12 +50,31 @@ pub enum Error {
|
|||||||
/// Computed hash
|
/// Computed hash
|
||||||
got: H256,
|
got: H256,
|
||||||
},
|
},
|
||||||
|
/// Server didn't respond with OK status.
|
||||||
|
InvalidStatus,
|
||||||
/// IO Error while validating hash.
|
/// IO Error while validating hash.
|
||||||
IO(io::Error),
|
IO(io::Error),
|
||||||
/// Error during fetch.
|
/// Error during fetch.
|
||||||
Fetch(FetchError),
|
Fetch(FetchError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl PartialEq for Error {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
use Error::*;
|
||||||
|
match (self, other) {
|
||||||
|
(&HashMismatch { expected, got }, &HashMismatch { expected: e, got: g }) => {
|
||||||
|
expected == e && got == g
|
||||||
|
},
|
||||||
|
(&NoResolution, &NoResolution) => true,
|
||||||
|
(&InvalidStatus, &InvalidStatus) => true,
|
||||||
|
(&IO(_), &IO(_)) => true,
|
||||||
|
(&Fetch(_), &Fetch(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<FetchError> for Error {
|
impl From<FetchError> for Error {
|
||||||
fn from(error: FetchError) -> Self {
|
fn from(error: FetchError) -> Self {
|
||||||
Error::Fetch(error)
|
Error::Fetch(error)
|
||||||
@ -115,6 +134,10 @@ impl<F: Fetch + 'static> HashFetch for Client<F> {
|
|||||||
let future = self.fetch.fetch(&url).then(move |result| {
|
let future = self.fetch.fetch(&url).then(move |result| {
|
||||||
fn validate_hash(path: PathBuf, hash: H256, result: Result<Response, FetchError>) -> Result<PathBuf, Error> {
|
fn validate_hash(path: PathBuf, hash: H256, result: Result<Response, FetchError>) -> Result<PathBuf, Error> {
|
||||||
let response = result?;
|
let response = result?;
|
||||||
|
if !response.is_success() {
|
||||||
|
return Err(Error::InvalidStatus);
|
||||||
|
}
|
||||||
|
|
||||||
// Read the response
|
// Read the response
|
||||||
let mut reader = io::BufReader::new(response);
|
let mut reader = io::BufReader::new(response);
|
||||||
let mut writer = io::BufWriter::new(fs::File::create(&path)?);
|
let mut writer = io::BufWriter::new(fs::File::create(&path)?);
|
||||||
@ -160,3 +183,119 @@ fn random_temp_path() -> PathBuf {
|
|||||||
path.push(file);
|
path.push(file);
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{Arc, mpsc};
|
||||||
|
use util::{Mutex, FromHex};
|
||||||
|
use futures::future;
|
||||||
|
use fetch::{self, Fetch};
|
||||||
|
use parity_reactor::Remote;
|
||||||
|
use urlhint::tests::{FakeRegistrar, URLHINT};
|
||||||
|
use super::{Error, Client, HashFetch};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct FakeFetch {
|
||||||
|
return_success: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fetch for FakeFetch {
|
||||||
|
type Result = future::Ok<fetch::Response, fetch::Error>;
|
||||||
|
|
||||||
|
fn new() -> Result<Self, fetch::Error> where Self: Sized {
|
||||||
|
Ok(FakeFetch { return_success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result {
|
||||||
|
assert_eq!(url, "https://ethcore.io/assets/images/ethcore-black-horizontal.png");
|
||||||
|
future::ok(if self.return_success {
|
||||||
|
let cursor = ::std::io::Cursor::new(b"result");
|
||||||
|
fetch::Response::from_reader(cursor)
|
||||||
|
} else {
|
||||||
|
fetch::Response::not_found()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn registrar() -> FakeRegistrar {
|
||||||
|
let mut registrar = FakeRegistrar::new();
|
||||||
|
registrar.responses = Mutex::new(vec![
|
||||||
|
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
|
||||||
|
Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()),
|
||||||
|
]);
|
||||||
|
registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_error_if_hash_not_found() {
|
||||||
|
// given
|
||||||
|
let contract = Arc::new(FakeRegistrar::new());
|
||||||
|
let fetch = FakeFetch { return_success: false };
|
||||||
|
let client = Client::with_fetch(contract.clone(), fetch, Remote::new_sync());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
client.fetch(2.into(), Box::new(move |result| {
|
||||||
|
tx.send(result).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let result = rx.recv().unwrap();
|
||||||
|
assert_eq!(result.unwrap_err(), Error::NoResolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_error_if_response_is_not_successful() {
|
||||||
|
// given
|
||||||
|
let registrar = Arc::new(registrar());
|
||||||
|
let fetch = FakeFetch { return_success: false };
|
||||||
|
let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
client.fetch(2.into(), Box::new(move |result| {
|
||||||
|
tx.send(result).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let result = rx.recv().unwrap();
|
||||||
|
assert_eq!(result.unwrap_err(), Error::InvalidStatus);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn should_return_hash_mismatch() {
|
||||||
|
// given
|
||||||
|
let registrar = Arc::new(registrar());
|
||||||
|
let fetch = FakeFetch { return_success: true };
|
||||||
|
let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
client.fetch(2.into(), Box::new(move |result| {
|
||||||
|
tx.send(result).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let result = rx.recv().unwrap();
|
||||||
|
let hash = "0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into();
|
||||||
|
assert_eq!(result.unwrap_err(), Error::HashMismatch { expected: 2.into(), got: hash });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_path_if_hash_matches() {
|
||||||
|
// given
|
||||||
|
let registrar = Arc::new(registrar());
|
||||||
|
let fetch = FakeFetch { return_success: true };
|
||||||
|
let client = Client::with_fetch(registrar.clone(), fetch, Remote::new_sync());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
client.fetch("0x06b0a4f426f6713234b2d4b2468640bc4e0bb72657a920ad24c5087153c593c8".into(), Box::new(move |result| {
|
||||||
|
tx.send(result).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// then
|
||||||
|
let result = rx.recv().unwrap();
|
||||||
|
assert!(result.is_ok(), "Should return path, got: {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -264,7 +264,7 @@ fn as_string<T: fmt::Debug>(e: T) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use rustc_serialize::hex::FromHex;
|
use rustc_serialize::hex::FromHex;
|
||||||
@ -273,16 +273,16 @@ mod tests {
|
|||||||
use super::guess_mime_type;
|
use super::guess_mime_type;
|
||||||
use util::{Bytes, Address, Mutex, ToPretty};
|
use util::{Bytes, Address, Mutex, ToPretty};
|
||||||
|
|
||||||
struct FakeRegistrar {
|
pub struct FakeRegistrar {
|
||||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
||||||
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
pub const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
pub const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||||
|
|
||||||
impl FakeRegistrar {
|
impl FakeRegistrar {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
FakeRegistrar {
|
FakeRegistrar {
|
||||||
calls: Arc::new(Mutex::new(Vec::new())),
|
calls: Arc::new(Mutex::new(Vec::new())),
|
||||||
responses: Mutex::new(
|
responses: Mutex::new(
|
||||||
|
@ -230,7 +230,7 @@ impl Manager {
|
|||||||
if result.len() != 65 {
|
if result.len() != 65 {
|
||||||
return Err(Error::Protocol("Signature packet size mismatch"));
|
return Err(Error::Protocol("Signature packet size mismatch"));
|
||||||
}
|
}
|
||||||
let v = result[0];
|
let v = (result[0] + 1) % 2;
|
||||||
let r = H256::from_slice(&result[1..33]);
|
let r = H256::from_slice(&result[1..33]);
|
||||||
let s = H256::from_slice(&result[33..65]);
|
let s = H256::from_slice(&result[33..65]);
|
||||||
Ok(Signature::from_rsv(&r, &s, v))
|
Ok(Signature::from_rsv(&r, &s, v))
|
||||||
@ -289,7 +289,7 @@ impl Manager {
|
|||||||
let mut chunk: [u8; HID_PACKET_SIZE] = [0; HID_PACKET_SIZE];
|
let mut chunk: [u8; HID_PACKET_SIZE] = [0; HID_PACKET_SIZE];
|
||||||
let chunk_size = handle.read(&mut chunk)?;
|
let chunk_size = handle.read(&mut chunk)?;
|
||||||
trace!("read {:?}", &chunk[..]);
|
trace!("read {:?}", &chunk[..]);
|
||||||
if chunk_size < 5 || chunk[1] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG {
|
if chunk_size < 5 || chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != APDU_TAG {
|
||||||
return Err(Error::Protocol("Unexpected chunk header"));
|
return Err(Error::Protocol("Unexpected chunk header"));
|
||||||
}
|
}
|
||||||
let seq = (chunk[3] as usize) << 8 | (chunk[4] as usize);
|
let seq = (chunk[3] as usize) << 8 | (chunk[4] as usize);
|
||||||
|
15
ipfs/Cargo.toml
Normal file
15
ipfs/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
description = "Parity IPFS-compatible API"
|
||||||
|
name = "parity-ipfs-api"
|
||||||
|
version = "1.6.0"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ethcore = { path = "../ethcore" }
|
||||||
|
ethcore-util = { path = "../util" }
|
||||||
|
rlp = { path = "../util/rlp" }
|
||||||
|
mime = "0.2"
|
||||||
|
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||||
|
cid = "0.2.1"
|
||||||
|
multihash = "0.5"
|
94
ipfs/src/error.rs
Normal file
94
ipfs/src/error.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use {multihash, cid, hyper};
|
||||||
|
use handler::Out;
|
||||||
|
|
||||||
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// IPFS server error
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ServerError {
|
||||||
|
/// Wrapped `std::io::Error`
|
||||||
|
IoError(::std::io::Error),
|
||||||
|
/// Other `hyper` error
|
||||||
|
Other(hyper::error::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
CidParsingFailed,
|
||||||
|
UnsupportedHash,
|
||||||
|
UnsupportedCid,
|
||||||
|
BlockNotFound,
|
||||||
|
TransactionNotFound,
|
||||||
|
StateRootNotFound,
|
||||||
|
ContractNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert Error into Out, handy when switching from Rust's Result-based
|
||||||
|
/// error handling to Hyper's request handling.
|
||||||
|
impl From<Error> for Out {
|
||||||
|
fn from(err: Error) -> Out {
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
match err {
|
||||||
|
UnsupportedHash => Out::Bad("Hash must be Keccak-256"),
|
||||||
|
UnsupportedCid => Out::Bad("CID codec not supported"),
|
||||||
|
CidParsingFailed => Out::Bad("CID parsing failed"),
|
||||||
|
BlockNotFound => Out::NotFound("Block not found"),
|
||||||
|
TransactionNotFound => Out::NotFound("Transaction not found"),
|
||||||
|
StateRootNotFound => Out::NotFound("State root not found"),
|
||||||
|
ContractNotFound => Out::NotFound("Contract not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert Content ID errors.
|
||||||
|
impl From<cid::Error> for Error {
|
||||||
|
fn from(_: cid::Error) -> Error {
|
||||||
|
Error::CidParsingFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert multihash errors (multihash being part of CID).
|
||||||
|
impl From<multihash::Error> for Error {
|
||||||
|
fn from(_: multihash::Error) -> Error {
|
||||||
|
Error::CidParsingFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle IO errors (ports taken when starting the server).
|
||||||
|
impl From<::std::io::Error> for ServerError {
|
||||||
|
fn from(err: ::std::io::Error) -> ServerError {
|
||||||
|
ServerError::IoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::error::Error> for ServerError {
|
||||||
|
fn from(err: hyper::error::Error) -> ServerError {
|
||||||
|
ServerError::Other(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ServerError> for String {
|
||||||
|
fn from(err: ServerError) -> String {
|
||||||
|
match err {
|
||||||
|
ServerError::IoError(err) => err.to_string(),
|
||||||
|
ServerError::Other(err) => err.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
268
ipfs/src/handler.rs
Normal file
268
ipfs/src/handler.rs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use {rlp, multihash};
|
||||||
|
use error::{Error, Result};
|
||||||
|
use cid::{ToCid, Codec};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use multihash::Hash;
|
||||||
|
use hyper::Next;
|
||||||
|
use util::{Bytes, H256};
|
||||||
|
use ethcore::client::{BlockId, TransactionId, BlockChainClient};
|
||||||
|
|
||||||
|
type Reason = &'static str;
|
||||||
|
|
||||||
|
/// Keeps the state of the response to send out
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Out {
|
||||||
|
OctetStream(Bytes),
|
||||||
|
NotFound(Reason),
|
||||||
|
Bad(Reason),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request/response handler
|
||||||
|
pub struct IpfsHandler {
|
||||||
|
/// Reference to the Blockchain Client
|
||||||
|
client: Arc<BlockChainClient>,
|
||||||
|
|
||||||
|
/// Response to send out
|
||||||
|
pub out: Out,
|
||||||
|
|
||||||
|
/// How many bytes from the response have been written
|
||||||
|
pub out_progress: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpfsHandler {
|
||||||
|
pub fn new(client: Arc<BlockChainClient>) -> Self {
|
||||||
|
IpfsHandler {
|
||||||
|
client: client,
|
||||||
|
out: Out::Bad("Invalid Request"),
|
||||||
|
out_progress: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route path + query string to a specialized method
|
||||||
|
pub fn route(&mut self, path: &str, query: Option<&str>) -> Next {
|
||||||
|
self.out = match path {
|
||||||
|
"/api/v0/block/get" => {
|
||||||
|
let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or("");
|
||||||
|
|
||||||
|
self.route_cid(arg).unwrap_or_else(Into::into)
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => Out::NotFound("Route not found")
|
||||||
|
};
|
||||||
|
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to read Content ID from `arg` query parameter, get a hash and
|
||||||
|
/// route further by the CID's codec.
|
||||||
|
fn route_cid(&self, cid: &str) -> Result<Out> {
|
||||||
|
let cid = cid.to_cid()?;
|
||||||
|
|
||||||
|
let mh = multihash::decode(&cid.hash)?;
|
||||||
|
|
||||||
|
if mh.alg != Hash::Keccak256 { return Err(Error::UnsupportedHash); }
|
||||||
|
|
||||||
|
let hash: H256 = mh.digest.into();
|
||||||
|
|
||||||
|
match cid.codec {
|
||||||
|
Codec::EthereumBlock => self.block(hash),
|
||||||
|
Codec::EthereumBlockList => self.block_list(hash),
|
||||||
|
Codec::EthereumTx => self.transaction(hash),
|
||||||
|
Codec::EthereumStateTrie => self.state_trie(hash),
|
||||||
|
Codec::Raw => self.contract_code(hash),
|
||||||
|
_ => return Err(Error::UnsupportedCid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block header by hash as raw binary.
|
||||||
|
fn block(&self, hash: H256) -> Result<Out> {
|
||||||
|
let block_id = BlockId::Hash(hash);
|
||||||
|
let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?;
|
||||||
|
|
||||||
|
Ok(Out::OctetStream(block.into_inner()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get list of block ommers by hash as raw binary.
|
||||||
|
fn block_list(&self, hash: H256) -> Result<Out> {
|
||||||
|
let uncles = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?;
|
||||||
|
|
||||||
|
Ok(Out::OctetStream(rlp::encode(&uncles).to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get transaction by hash and return as raw binary.
|
||||||
|
fn transaction(&self, hash: H256) -> Result<Out> {
|
||||||
|
let tx_id = TransactionId::Hash(hash);
|
||||||
|
let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?;
|
||||||
|
|
||||||
|
Ok(Out::OctetStream(rlp::encode(&*tx).to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get state trie node by hash and return as raw binary.
|
||||||
|
fn state_trie(&self, hash: H256) -> Result<Out> {
|
||||||
|
let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?;
|
||||||
|
|
||||||
|
Ok(Out::OctetStream(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get state trie node by hash and return as raw binary.
|
||||||
|
fn contract_code(&self, hash: H256) -> Result<Out> {
|
||||||
|
let data = self.client.state_data(&hash).ok_or(Error::ContractNotFound)?;
|
||||||
|
|
||||||
|
Ok(Out::OctetStream(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a query parameter's value by name.
|
||||||
|
fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> {
|
||||||
|
query.split('&')
|
||||||
|
.find(|part| part.starts_with(name) && part[name.len()..].starts_with("="))
|
||||||
|
.map(|part| &part[name.len() + 1..])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ethcore::client::TestBlockChainClient;
|
||||||
|
|
||||||
|
fn get_mocked_handler() -> IpfsHandler {
|
||||||
|
IpfsHandler::new(Arc::new(TestBlockChainClient::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_param() {
|
||||||
|
let query = "foo=100&bar=200&qux=300";
|
||||||
|
|
||||||
|
assert_eq!(get_param(query, "foo"), Some("100"));
|
||||||
|
assert_eq!(get_param(query, "bar"), Some("200"));
|
||||||
|
assert_eq!(get_param(query, "qux"), Some("300"));
|
||||||
|
assert_eq!(get_param(query, "bar="), None);
|
||||||
|
assert_eq!(get_param(query, "200"), None);
|
||||||
|
assert_eq!(get_param("", "foo"), None);
|
||||||
|
assert_eq!(get_param("foo", "foo"), None);
|
||||||
|
assert_eq!(get_param("foo&bar", "foo"), None);
|
||||||
|
assert_eq!(get_param("bar&foo", "foo"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_block() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `eth-block` with Keccak-256
|
||||||
|
let cid = "z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_block_list() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `eth-block-list` with Keccak-256
|
||||||
|
let cid = "z43c7o7FsNxqdLJW8Ucj19tuCALtnmUb2EkDptj4W6xSkFVTqWs";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_tx() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `eth-tx` with Keccak-256
|
||||||
|
let cid = "z44VCrqbpbPcb8SUBc8Tba4EaKuoDz2grdEoQXx4TP7WYh9ZGBu";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::TransactionNotFound), handler.route_cid(cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_state_trie() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `eth-state-trie` with Keccak-256
|
||||||
|
let cid = "z45oqTS7kR2n2peRGJQ4VCJEeaG9sorqcCyfmznZPJM7FMdhQCT";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_contract_code() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `raw` with Keccak-256
|
||||||
|
let cid = "zb34WAp1Q5fhtLGZ3w3jhnTWaNbVV5ZZvGq4vuJQzERj6Pu3H";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::ContractNotFound), handler.route_cid(&cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_invalid_hash() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `eth-block` with SHA3-256 hash
|
||||||
|
let cid = "z43Aa9gr1MM7TENJh4Em9d9Ttr7p3UcfyMpNei6WLVeCmSEPu8F";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::UnsupportedHash), handler.route_cid(cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_route_invalid_codec() {
|
||||||
|
let handler = get_mocked_handler();
|
||||||
|
|
||||||
|
// `bitcoin-block` with Keccak-256
|
||||||
|
let cid = "z4HFyHvb8CarYARyxz4cCcPaciduXd49TFPCKLhYmvNxf7Auvwu";
|
||||||
|
|
||||||
|
assert_eq!(Err(Error::UnsupportedCid), handler.route_cid(&cid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn route_block() {
|
||||||
|
let mut handler = get_mocked_handler();
|
||||||
|
|
||||||
|
let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"));
|
||||||
|
|
||||||
|
assert_eq!(handler.out, Out::NotFound("Block not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn route_block_missing_query() {
|
||||||
|
let mut handler = get_mocked_handler();
|
||||||
|
|
||||||
|
let _ = handler.route("/api/v0/block/get", None);
|
||||||
|
|
||||||
|
assert_eq!(handler.out, Out::Bad("CID parsing failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn route_block_invalid_query() {
|
||||||
|
let mut handler = get_mocked_handler();
|
||||||
|
|
||||||
|
let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"));
|
||||||
|
|
||||||
|
assert_eq!(handler.out, Out::Bad("CID parsing failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn route_invalid_route() {
|
||||||
|
let mut handler = get_mocked_handler();
|
||||||
|
|
||||||
|
let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"));
|
||||||
|
|
||||||
|
assert_eq!(handler.out, Out::NotFound("Route not found"));
|
||||||
|
}
|
||||||
|
}
|
205
ipfs/src/lib.rs
Normal file
205
ipfs/src/lib.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate mime;
|
||||||
|
extern crate hyper;
|
||||||
|
extern crate multihash;
|
||||||
|
extern crate cid;
|
||||||
|
|
||||||
|
extern crate rlp;
|
||||||
|
extern crate ethcore;
|
||||||
|
extern crate ethcore_util as util;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod handler;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::net::{SocketAddr, IpAddr, Ipv4Addr};
|
||||||
|
use error::ServerError;
|
||||||
|
use handler::{IpfsHandler, Out};
|
||||||
|
use hyper::server::{Listening, Handler, Request, Response};
|
||||||
|
use hyper::net::HttpStream;
|
||||||
|
use hyper::header::{ContentLength, ContentType, Origin};
|
||||||
|
use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode};
|
||||||
|
use ethcore::client::BlockChainClient;
|
||||||
|
|
||||||
|
/// Implement Hyper's HTTP handler
|
||||||
|
impl Handler<HttpStream> for IpfsHandler {
|
||||||
|
fn on_request(&mut self, req: Request<HttpStream>) -> Next {
|
||||||
|
if *req.method() != Method::Get {
|
||||||
|
return Next::write();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject requests if the Origin header isn't valid
|
||||||
|
if req.headers().get::<Origin>().map(|o| "127.0.0.1" != &o.host.hostname).unwrap_or(false) {
|
||||||
|
self.out = Out::Bad("Illegal Origin");
|
||||||
|
|
||||||
|
return Next::write();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (path, query) = match *req.uri() {
|
||||||
|
RequestUri::AbsolutePath { ref path, ref query } => (path, query.as_ref().map(AsRef::as_ref)),
|
||||||
|
_ => return Next::write(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.route(path, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||||
|
use Out::*;
|
||||||
|
|
||||||
|
match self.out {
|
||||||
|
OctetStream(ref bytes) => {
|
||||||
|
use mime::{Mime, TopLevel, SubLevel};
|
||||||
|
|
||||||
|
// `OctetStream` is not a valid variant, so need to construct
|
||||||
|
// the type manually.
|
||||||
|
let content_type = Mime(
|
||||||
|
TopLevel::Application,
|
||||||
|
SubLevel::Ext("octet-stream".into()),
|
||||||
|
vec![]
|
||||||
|
);
|
||||||
|
|
||||||
|
res.headers_mut().set(ContentLength(bytes.len() as u64));
|
||||||
|
res.headers_mut().set(ContentType(content_type));
|
||||||
|
|
||||||
|
Next::write()
|
||||||
|
},
|
||||||
|
NotFound(reason) => {
|
||||||
|
res.set_status(StatusCode::NotFound);
|
||||||
|
|
||||||
|
res.headers_mut().set(ContentLength(reason.len() as u64));
|
||||||
|
res.headers_mut().set(ContentType(mime!(Text/Plain)));
|
||||||
|
|
||||||
|
Next::write()
|
||||||
|
},
|
||||||
|
Bad(reason) => {
|
||||||
|
res.set_status(StatusCode::BadRequest);
|
||||||
|
|
||||||
|
res.headers_mut().set(ContentLength(reason.len() as u64));
|
||||||
|
res.headers_mut().set(ContentType(mime!(Text/Plain)));
|
||||||
|
|
||||||
|
Next::write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_response_writable(&mut self, transport: &mut Encoder<HttpStream>) -> Next {
|
||||||
|
use Out::*;
|
||||||
|
|
||||||
|
// Get the data to write as a byte slice
|
||||||
|
let data = match self.out {
|
||||||
|
OctetStream(ref bytes) => &bytes,
|
||||||
|
NotFound(reason) | Bad(reason) => reason.as_bytes(),
|
||||||
|
};
|
||||||
|
|
||||||
|
write_chunk(transport, &mut self.out_progress, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_chunk<W: Write>(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next {
|
||||||
|
// Skip any bytes that have already been written
|
||||||
|
let chunk = &data[*progress..];
|
||||||
|
|
||||||
|
// Write an get written count
|
||||||
|
let written = match transport.write(chunk) {
|
||||||
|
Ok(written) => written,
|
||||||
|
Err(_) => return Next::end(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*progress += written;
|
||||||
|
|
||||||
|
// Close the connection if the entire chunk has been written, otherwise increment progress
|
||||||
|
if written < chunk.len() {
|
||||||
|
Next::write()
|
||||||
|
} else {
|
||||||
|
Next::end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_server(port: u16, client: Arc<BlockChainClient>) -> Result<Listening, ServerError> {
|
||||||
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
hyper::Server::http(&addr)?
|
||||||
|
.handle(move |_| IpfsHandler::new(client.clone()))
|
||||||
|
.map(|(listening, srv)| {
|
||||||
|
|
||||||
|
::std::thread::spawn(move || {
|
||||||
|
srv.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
listening
|
||||||
|
})?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_chunk_to_vec() {
|
||||||
|
let mut transport = Vec::new();
|
||||||
|
let mut progress = 0;
|
||||||
|
|
||||||
|
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
|
||||||
|
|
||||||
|
assert_eq!(b"foobar".to_vec(), transport);
|
||||||
|
assert_eq!(6, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_chunk_to_vec_part() {
|
||||||
|
let mut transport = Vec::new();
|
||||||
|
let mut progress = 3;
|
||||||
|
|
||||||
|
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
|
||||||
|
|
||||||
|
assert_eq!(b"bar".to_vec(), transport);
|
||||||
|
assert_eq!(6, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_chunk_to_array() {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 3];
|
||||||
|
let mut progress = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut transport: Cursor<&mut [u8]> = Cursor::new(&mut buf);
|
||||||
|
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(*b"foo", buf);
|
||||||
|
assert_eq!(3, progress);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut transport: Cursor<&mut [u8]> = Cursor::new(&mut buf);
|
||||||
|
let _ = write_chunk(&mut transport, &mut progress, b"foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(*b"bar", buf);
|
||||||
|
assert_eq!(6, progress);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
}
|
}
|
||||||
/* cyrillic */
|
/* cyrillic */
|
||||||
@ -11,7 +11,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
/* greek-ext */
|
/* greek-ext */
|
||||||
@ -19,7 +19,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
/* greek */
|
/* greek */
|
||||||
@ -27,7 +27,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
/* vietnamese */
|
/* vietnamese */
|
||||||
@ -35,7 +35,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@ -43,7 +43,7 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@ -51,6 +51,6 @@
|
|||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Light'), local('Roboto-Light'), url(v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2');
|
src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
}
|
}
|
||||||
/* cyrillic */
|
/* cyrillic */
|
||||||
@ -11,7 +11,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
/* greek-ext */
|
/* greek-ext */
|
||||||
@ -19,7 +19,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
/* greek */
|
/* greek */
|
||||||
@ -27,7 +27,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
/* vietnamese */
|
/* vietnamese */
|
||||||
@ -35,7 +35,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2');
|
||||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@ -43,7 +43,7 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@ -51,6 +51,6 @@
|
|||||||
font-family: 'Roboto Mono';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2');
|
src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.3.89",
|
"version": "0.3.94",
|
||||||
"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>",
|
||||||
|
@ -170,18 +170,30 @@ export default class Parity {
|
|||||||
.execute('parity_generateSecretPhrase');
|
.execute('parity_generateSecretPhrase');
|
||||||
}
|
}
|
||||||
|
|
||||||
getDappsAddresses (dappId) {
|
getDappAddresses (dappId) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_getDappsAddresses', dappId)
|
.execute('parity_getDappAddresses', dappId)
|
||||||
.then(outAddresses);
|
.then(outAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewDappsWhitelist () {
|
getDappDefaultAddress (dappId) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_getNewDappsWhitelist')
|
.execute('parity_getDappDefaultAddress', dappId)
|
||||||
|
.then(outAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewDappsAddresses () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_getNewDappsAddresses')
|
||||||
.then((addresses) => addresses ? addresses.map(outAddress) : null);
|
.then((addresses) => addresses ? addresses.map(outAddress) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNewDappsDefaultAddress () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_getNewDappsDefaultAddress')
|
||||||
|
.then(outAddress);
|
||||||
|
}
|
||||||
|
|
||||||
getVaultMeta (vaultName) {
|
getVaultMeta (vaultName) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_getVaultMeta', vaultName)
|
.execute('parity_getVaultMeta', vaultName)
|
||||||
@ -391,9 +403,14 @@ export default class Parity {
|
|||||||
.execute('parity_setAuthor', inAddress(address));
|
.execute('parity_setAuthor', inAddress(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
setDappsAddresses (dappId, addresses) {
|
setDappAddresses (dappId, addresses) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_setDappsAddresses', dappId, inAddresses(addresses));
|
.execute('parity_setDappAddresses', dappId, inAddresses(addresses));
|
||||||
|
}
|
||||||
|
|
||||||
|
setDappDefaultAddress (dappId, address) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEngineSigner (address, password) {
|
setEngineSigner (address, password) {
|
||||||
@ -431,9 +448,14 @@ export default class Parity {
|
|||||||
.execute('parity_setMode', mode);
|
.execute('parity_setMode', mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNewDappsWhitelist (addresses) {
|
setNewDappsAddresses (addresses) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null);
|
.execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewDappsDefaultAddress (address) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setNewDappsDefaultAddress', inAddress(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
setTransactionsLimit (quantity) {
|
setTransactionsLimit (quantity) {
|
||||||
|
@ -119,13 +119,15 @@ export default class Personal {
|
|||||||
case 'parity_removeAddress':
|
case 'parity_removeAddress':
|
||||||
case 'parity_setAccountName':
|
case 'parity_setAccountName':
|
||||||
case 'parity_setAccountMeta':
|
case 'parity_setAccountMeta':
|
||||||
case 'parity_changeVault':
|
|
||||||
this._accountsInfo();
|
this._accountsInfo();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'parity_setDappsAddresses':
|
case 'parity_setDappAddresses':
|
||||||
case 'parity_setNewDappsWhitelist':
|
case 'parity_setDappDefaultAddress':
|
||||||
|
case 'parity_setNewDappsAddresses':
|
||||||
|
case 'parity_setNewDappsDefaultAddress':
|
||||||
this._defaultAccount(true);
|
this._defaultAccount(true);
|
||||||
|
this._listAccounts();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1186,9 +1186,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setDappsAddresses: {
|
setDappAddresses: {
|
||||||
subdoc: SUBDOC_ACCOUNTS,
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
desc: 'Sets the available addresses for a dapp.',
|
desc: 'Sets the available addresses for a dapp. When provided with non-empty list changes the default account as well.',
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
type: String,
|
type: String,
|
||||||
@ -1197,7 +1197,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: Array,
|
type: Array,
|
||||||
desc: 'Array of available accounts available to the dapp.',
|
desc: 'Array of available accounts available to the dapp or `null` for default list.',
|
||||||
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -1208,7 +1208,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getDappsAddresses: {
|
getDappAddresses: {
|
||||||
subdoc: SUBDOC_ACCOUNTS,
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
desc: 'Returns the list of accounts available to a specific dapp.',
|
desc: 'Returns the list of accounts available to a specific dapp.',
|
||||||
params: [
|
params: [
|
||||||
@ -1225,13 +1225,52 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setNewDappsWhitelist: {
|
setDappDefaultAddress: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Changes dapp default address. Does not affect other accounts exposed for this dapp, but default account will always be retured as the first one.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Dapp Id.',
|
||||||
|
example: 'web'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Default Address.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Boolean,
|
||||||
|
desc: '`true` if the call was successful',
|
||||||
|
example: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getDappDefaultAddress: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Returns a default account available to a specific dapp.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
desc: 'Dapp Id.',
|
||||||
|
example: 'web'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Address,
|
||||||
|
desc: 'Default Address',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setNewDappsAddresses: {
|
||||||
subdoc: SUBDOC_ACCOUNTS,
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
desc: 'Sets the list of accounts available to new dapps.',
|
desc: 'Sets the list of accounts available to new dapps.',
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
type: Array,
|
type: Array,
|
||||||
desc: 'List of accounts available by default.',
|
desc: 'List of accounts available by default or `null` for all accounts.',
|
||||||
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -1242,7 +1281,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getNewDappsWhitelist: {
|
getNewDappsAddresses: {
|
||||||
subdoc: SUBDOC_ACCOUNTS,
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
desc: 'Returns the list of accounts available to a new dapps.',
|
desc: 'Returns the list of accounts available to a new dapps.',
|
||||||
params: [],
|
params: [],
|
||||||
@ -1253,6 +1292,34 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setNewDappsDefaultAddress: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Changes global default address. This setting may be overriden for a specific dapp.',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: Address,
|
||||||
|
desc: 'Default Address.',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: Boolean,
|
||||||
|
desc: '`true` if the call was successful',
|
||||||
|
example: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNewDappsDefaultAddress: {
|
||||||
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
|
desc: 'Returns a default account available to dapps.',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Address,
|
||||||
|
desc: 'Default Address',
|
||||||
|
example: '0x407d73d8a49eeb85d32cf465507dd71d507100c1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
listRecentDapps: {
|
listRecentDapps: {
|
||||||
subdoc: SUBDOC_ACCOUNTS,
|
subdoc: SUBDOC_ACCOUNTS,
|
||||||
desc: 'Returns a list of the most recent active dapps.',
|
desc: 'Returns a list of the most recent active dapps.',
|
||||||
|
@ -19,23 +19,24 @@
|
|||||||
line-height: 1.618em;
|
line-height: 1.618em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password {
|
/* TODO: 2 column layout can be made generic, now duplicated in Vaults */
|
||||||
flex: 0 1 50%;
|
|
||||||
width: 50%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:nth-child(odd) {
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
padding-left: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwords {
|
.passwords {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.password {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 1 50%;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.identities, .selector {
|
.identities, .selector {
|
||||||
|
@ -18,37 +18,44 @@ import React from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
duplicateName: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='errors.duplicateName'
|
||||||
|
defaultMessage='the name already exists'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
noFile: (
|
noFile: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noFile'
|
id='errors.noFile'
|
||||||
defaultMessage='select a valid wallet file to import'
|
defaultMessage='select a valid wallet file to import'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
||||||
noKey: (
|
noKey: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noKey'
|
id='errors.noKey'
|
||||||
defaultMessage='you need to provide the raw private key'
|
defaultMessage='you need to provide the raw private key'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
||||||
noMatchPassword: (
|
noMatchPassword: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noMatchPassword'
|
id='errors.noMatchPassword'
|
||||||
defaultMessage='the supplied passwords does not match'
|
defaultMessage='the supplied passwords does not match'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
||||||
noName: (
|
noName: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noName'
|
id='errors.noName'
|
||||||
defaultMessage='you need to specify a valid name for the account'
|
defaultMessage='you need to specify a valid name'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
||||||
invalidKey: (
|
invalidKey: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.invalidKey'
|
id='errors.invalidKey'
|
||||||
defaultMessage='the raw key needs to be hex, 64 characters in length and contain the prefix "0x"'
|
defaultMessage='the raw key needs to be hex, 64 characters in length and contain the prefix "0x"'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -102,7 +102,7 @@ export default class Store {
|
|||||||
|
|
||||||
loadWhitelist () {
|
loadWhitelist () {
|
||||||
return this._api.parity
|
return this._api.parity
|
||||||
.getNewDappsWhitelist()
|
.getNewDappsAddresses()
|
||||||
.then((whitelist) => {
|
.then((whitelist) => {
|
||||||
this.setWhitelist(whitelist);
|
this.setWhitelist(whitelist);
|
||||||
})
|
})
|
||||||
@ -113,7 +113,7 @@ export default class Store {
|
|||||||
|
|
||||||
updateWhitelist (whitelist) {
|
updateWhitelist (whitelist) {
|
||||||
return this._api.parity
|
return this._api.parity
|
||||||
.setNewDappsWhitelist(whitelist)
|
.setNewDappsAddresses(whitelist)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setWhitelist(whitelist);
|
this.setWhitelist(whitelist);
|
||||||
})
|
})
|
||||||
|
@ -31,8 +31,8 @@ let store;
|
|||||||
function create () {
|
function create () {
|
||||||
api = {
|
api = {
|
||||||
parity: {
|
parity: {
|
||||||
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
getNewDappsAddresses: sinon.stub().resolves(WHITELIST),
|
||||||
setNewDappsWhitelist: sinon.stub().resolves(true)
|
setNewDappsAddresses: sinon.stub().resolves(true)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ describe('modals/DappPermissions/store', () => {
|
|||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('retrieves the whitelist via api', () => {
|
it('retrieves the whitelist via api', () => {
|
||||||
expect(api.parity.getNewDappsWhitelist).to.be.calledOnce;
|
expect(api.parity.getNewDappsAddresses).to.be.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the retrieved whitelist', () => {
|
it('sets the retrieved whitelist', () => {
|
||||||
@ -79,12 +79,12 @@ describe('modals/DappPermissions/store', () => {
|
|||||||
store.closeModal();
|
store.closeModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls setNewDappsWhitelist', () => {
|
it('calls setNewDappsAddresses', () => {
|
||||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
|
expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has the default account in first position', () => {
|
it('has the default account in first position', () => {
|
||||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']);
|
expect(api.parity.setNewDappsAddresses).to.have.been.calledWith(['789', '456']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
17
js/src/modals/VaultAccounts/index.js
Normal file
17
js/src/modals/VaultAccounts/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultAccounts';
|
48
js/src/modals/VaultAccounts/vaultAccounts.css
Normal file
48
js/src/modals/VaultAccounts/vaultAccounts.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity is free software: you can redistribute it and/or modify
|
||||||
|
/* it under the terms of the GNU General Public License as published by
|
||||||
|
/* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity is distributed in the hope that it will be useful,
|
||||||
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/* GNU General Public License for more details.
|
||||||
|
/*
|
||||||
|
/* You should have received a copy of the GNU General Public License
|
||||||
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* TODO: These overlap with DappPermissions now, make DRY */
|
||||||
|
/* (selection component or just styles?) */
|
||||||
|
.iconDisabled {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5em;
|
||||||
|
top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected,
|
||||||
|
.unselected {
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
195
js/src/modals/VaultAccounts/vaultAccounts.js
Normal file
195
js/src/modals/VaultAccounts/vaultAccounts.js
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
|
import { personalAccountsInfo } from '~/redux/providers/personalActions';
|
||||||
|
import { AccountCard, Button, Portal, SectionList } from '~/ui';
|
||||||
|
import { CancelIcon, CheckIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import styles from './vaultAccounts.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class VaultAccounts extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
api: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
accounts: PropTypes.object.isRequired,
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
|
personalAccountsInfo: PropTypes.func.isRequired,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { accounts } = this.props;
|
||||||
|
const { isBusyAccounts, isModalAccountsOpen, selectedAccounts } = this.props.vaultStore;
|
||||||
|
|
||||||
|
if (!isModalAccountsOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaultAccounts = Object
|
||||||
|
.keys(accounts)
|
||||||
|
.filter((address) => accounts[address].uuid)
|
||||||
|
.map((address) => accounts[address]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
buttons={ [
|
||||||
|
<Button
|
||||||
|
disabled={ isBusyAccounts }
|
||||||
|
icon={ <CancelIcon /> }
|
||||||
|
key='cancel'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.accounts.button.cancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ isBusyAccounts }
|
||||||
|
icon={ <CheckIcon /> }
|
||||||
|
key='execute'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.accounts.button.execute'
|
||||||
|
defaultMessage='Set'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onExecute }
|
||||||
|
/>
|
||||||
|
] }
|
||||||
|
busy={ isBusyAccounts }
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.accounts.title'
|
||||||
|
defaultMessage='Manage Vault Accounts'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SectionList
|
||||||
|
items={ vaultAccounts }
|
||||||
|
noStretch
|
||||||
|
renderItem={ this.renderAccount }
|
||||||
|
selectedAccounts={ selectedAccounts }
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: There are a lot of similarities between the dapp permissions selector
|
||||||
|
// (although that has defaults) and this one. A genrerix multi-select component
|
||||||
|
// would be applicable going forward. (Originals passed in, new selections back)
|
||||||
|
renderAccount = (account) => {
|
||||||
|
const { vaultName, selectedAccounts } = this.props.vaultStore;
|
||||||
|
const isInVault = account.meta.vault === vaultName;
|
||||||
|
const isSelected = isInVault
|
||||||
|
? !selectedAccounts[account.address]
|
||||||
|
: selectedAccounts[account.address];
|
||||||
|
|
||||||
|
const onSelect = () => {
|
||||||
|
this.props.vaultStore.toggleSelectedAccount(account.address);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.item }>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
className={
|
||||||
|
isSelected
|
||||||
|
? styles.selected
|
||||||
|
: styles.unselected
|
||||||
|
}
|
||||||
|
onClick={ onSelect }
|
||||||
|
/>
|
||||||
|
<div className={ styles.overlay }>
|
||||||
|
{
|
||||||
|
isSelected
|
||||||
|
? <CheckIcon onClick={ onSelect } />
|
||||||
|
: <CheckIcon className={ styles.iconDisabled } onClick={ onSelect } />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.vaultStore.closeAccountsModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
const { api } = this.context;
|
||||||
|
const { accounts, personalAccountsInfo, vaultStore } = this.props;
|
||||||
|
const { vaultName, selectedAccounts } = this.props.vaultStore;
|
||||||
|
|
||||||
|
const vaultAccounts = Object
|
||||||
|
.keys(accounts)
|
||||||
|
.filter((address) => accounts[address].uuid && selectedAccounts[address])
|
||||||
|
.map((address) => accounts[address]);
|
||||||
|
|
||||||
|
return vaultStore
|
||||||
|
.moveAccounts(
|
||||||
|
vaultName,
|
||||||
|
vaultAccounts
|
||||||
|
.filter((account) => account.meta.vault !== vaultName)
|
||||||
|
.map((account) => account.address),
|
||||||
|
vaultAccounts
|
||||||
|
.filter((account) => account.meta.vault === vaultName)
|
||||||
|
.map((account) => account.address)
|
||||||
|
)
|
||||||
|
.catch(this.props.newError)
|
||||||
|
.then(() => {
|
||||||
|
// TODO: We manually call parity_allAccountsInfo after all the promises
|
||||||
|
// have been resolved. If bulk moves do become available in the future,
|
||||||
|
// subscriptions can transparently take care of this instead of calling
|
||||||
|
// and manually dispatching an update. (Using subscriptions currently
|
||||||
|
// means allAccountsInfo is called after each and every move call)
|
||||||
|
return api.parity
|
||||||
|
.allAccountsInfo()
|
||||||
|
.then(personalAccountsInfo);
|
||||||
|
})
|
||||||
|
.then(this.onClose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
const { accounts } = state.personal;
|
||||||
|
|
||||||
|
return { accounts };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError,
|
||||||
|
personalAccountsInfo
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(VaultAccounts);
|
179
js/src/modals/VaultAccounts/vaultAccounts.spec.js
Normal file
179
js/src/modals/VaultAccounts/vaultAccounts.spec.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultAccounts from './';
|
||||||
|
|
||||||
|
const ACCOUNT_A = '0x1234567890123456789012345678901234567890';
|
||||||
|
const ACCOUNT_B = '0x0123456789012345678901234567890123456789';
|
||||||
|
const ACCOUNT_C = '0x9012345678901234567890123456789012345678';
|
||||||
|
const ACCOUNT_D = '0x8901234567890123456789012345678901234567';
|
||||||
|
const VAULTNAME = 'testVault';
|
||||||
|
const ACCOUNTS = {
|
||||||
|
[ACCOUNT_A]: {
|
||||||
|
address: ACCOUNT_A,
|
||||||
|
uuid: null
|
||||||
|
},
|
||||||
|
[ACCOUNT_B]: {
|
||||||
|
address: ACCOUNT_B,
|
||||||
|
uuid: ACCOUNT_B,
|
||||||
|
meta: {
|
||||||
|
vault: 'somethingElse'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ACCOUNT_C]: {
|
||||||
|
address: ACCOUNT_C,
|
||||||
|
uuid: ACCOUNT_C,
|
||||||
|
meta: {
|
||||||
|
vault: VAULTNAME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ACCOUNT_D]: {
|
||||||
|
address: ACCOUNT_D,
|
||||||
|
uuid: ACCOUNT_D,
|
||||||
|
meta: {
|
||||||
|
vault: VAULTNAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let reduxStore;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
api = {
|
||||||
|
parity: {
|
||||||
|
allAccountsInfo: sinon.stub().resolves({})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createReduxStore () {
|
||||||
|
reduxStore = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
personal: {
|
||||||
|
accounts: ACCOUNTS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return reduxStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
isBusyAccounts: false,
|
||||||
|
isModalAccountsOpen: true,
|
||||||
|
selectedAccounts: { [ACCOUNT_B]: true, [ACCOUNT_C]: true },
|
||||||
|
vaultName: VAULTNAME,
|
||||||
|
closeAccountsModal: sinon.stub(),
|
||||||
|
moveAccounts: sinon.stub().resolves(true),
|
||||||
|
toggleSelectedAccount: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<VaultAccounts vaultStore={ createVaultStore() } />,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
store: createReduxStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).find('VaultAccounts').shallow({
|
||||||
|
context: {
|
||||||
|
api: createApi()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/VaultAccounts', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('SectionList', () => {
|
||||||
|
let sectionList;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sectionList = component.find('SectionList');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has the filtered accounts', () => {
|
||||||
|
expect(sectionList.props().items).to.deep.equal([
|
||||||
|
ACCOUNTS[ACCOUNT_B], ACCOUNTS[ACCOUNT_C], ACCOUNTS[ACCOUNT_D]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders via renderAccount', () => {
|
||||||
|
expect(sectionList.props().renderItem).to.equal(instance.renderAccount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event handlers', () => {
|
||||||
|
describe('onClose', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into closeAccountsModal', () => {
|
||||||
|
expect(vaultStore.closeAccountsModal).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onExecute', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(instance, 'onClose');
|
||||||
|
return instance.onExecute();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.onClose.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into moveAccounts', () => {
|
||||||
|
expect(vaultStore.moveAccounts).to.have.been.calledWith(VAULTNAME, [ACCOUNT_B], [ACCOUNT_C]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes modal', () => {
|
||||||
|
expect(instance.onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/modals/VaultCreate/index.js
Normal file
17
js/src/modals/VaultCreate/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultCreate';
|
38
js/src/modals/VaultCreate/vaultCreate.css
Normal file
38
js/src/modals/VaultCreate/vaultCreate.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity is free software: you can redistribute it and/or modify
|
||||||
|
/* it under the terms of the GNU General Public License as published by
|
||||||
|
/* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity is distributed in the hope that it will be useful,
|
||||||
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/* GNU General Public License for more details.
|
||||||
|
/*
|
||||||
|
/* You should have received a copy of the GNU General Public License
|
||||||
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.body {
|
||||||
|
/* TODO: These styles are shared with CreateAccount - DRY up */
|
||||||
|
.passwords {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.password {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 1 50%;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
js/src/modals/VaultCreate/vaultCreate.js
Normal file
227
js/src/modals/VaultCreate/vaultCreate.js
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
|
import { Button, Input, Portal } from '~/ui';
|
||||||
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
|
import { CheckIcon, CloseIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import styles from './vaultCreate.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class VaultCreate extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isBusyCreate, isModalCreateOpen, vaultDescription, vaultName, vaultNameError, vaultPassword, vaultPasswordHint, vaultPasswordRepeat, vaultPasswordRepeatError } = this.props.vaultStore;
|
||||||
|
const hasError = !!vaultNameError || !!vaultPasswordRepeatError;
|
||||||
|
|
||||||
|
if (!isModalCreateOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
busy={ isBusyCreate }
|
||||||
|
buttons={ [
|
||||||
|
<Button
|
||||||
|
disabled={ isBusyCreate }
|
||||||
|
icon={ <CloseIcon /> }
|
||||||
|
key='close'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.button.close'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ hasError || isBusyCreate }
|
||||||
|
icon={ <CheckIcon /> }
|
||||||
|
key='vault'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.button.vault'
|
||||||
|
defaultMessage='create vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onCreate }
|
||||||
|
/>
|
||||||
|
] }
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.title'
|
||||||
|
defaultMessage='Create a new vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={ styles.body }>
|
||||||
|
<Input
|
||||||
|
error={ vaultNameError }
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.name.hint'
|
||||||
|
defaultMessage='a name for the vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.name.label'
|
||||||
|
defaultMessage='vault name'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditName }
|
||||||
|
value={ vaultName }
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.description.hint'
|
||||||
|
defaultMessage='an extended description for the vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.descriptions.label'
|
||||||
|
defaultMessage='(optional) description'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditDescription }
|
||||||
|
value={ vaultDescription }
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.hint.hint'
|
||||||
|
defaultMessage='(optional) a hint to help with remembering the password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.hint.label'
|
||||||
|
defaultMessage='password hint'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPasswordHint }
|
||||||
|
value={ vaultPasswordHint }
|
||||||
|
/>
|
||||||
|
<div className={ styles.passwords }>
|
||||||
|
<div className={ styles.password }>
|
||||||
|
<Input
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.password.hint'
|
||||||
|
defaultMessage='a strong, unique password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.password.label'
|
||||||
|
defaultMessage='password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPassword }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPassword }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.password }>
|
||||||
|
<Input
|
||||||
|
error={ vaultPasswordRepeatError }
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.password2.hint'
|
||||||
|
defaultMessage='verify your password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.create.password2.label'
|
||||||
|
defaultMessage='password (repeat)'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPasswordRepeat }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPasswordRepeat }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PasswordStrength input={ vaultPassword } />
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditDescription = (event, description) => {
|
||||||
|
this.props.vaultStore.setVaultDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditName = (event, name) => {
|
||||||
|
this.props.vaultStore.setVaultName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPassword = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPasswordHint = (event, hint) => {
|
||||||
|
this.props.vaultStore.setVaultPasswordHint(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPasswordRepeat = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPasswordRepeat(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreate = () => {
|
||||||
|
const { vaultNameError, vaultPasswordRepeatError } = this.props.vaultStore;
|
||||||
|
|
||||||
|
if (vaultNameError || vaultPasswordRepeatError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.vaultStore
|
||||||
|
.createVault()
|
||||||
|
.catch(this.props.newError)
|
||||||
|
.then(this.onClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.vaultStore.closeCreateModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(VaultCreate);
|
162
js/src/modals/VaultCreate/vaultCreate.spec.js
Normal file
162
js/src/modals/VaultCreate/vaultCreate.spec.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultCreate from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let reduxStore;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function vaultReduxStore () {
|
||||||
|
reduxStore = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return reduxStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vaultVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
isBusyCreate: false,
|
||||||
|
isModalCreateOpen: true,
|
||||||
|
vaultDescription: 'initialDesc',
|
||||||
|
vaultName: 'initialName',
|
||||||
|
vaultPassword: 'initialPassword',
|
||||||
|
vaultPasswordRepeat: 'initialPassword',
|
||||||
|
vaultPasswordHint: 'initialHint',
|
||||||
|
closeCreateModal: sinon.stub(),
|
||||||
|
createVault: sinon.stub().resolves(true),
|
||||||
|
setVaultDescription: sinon.stub(),
|
||||||
|
setVaultName: sinon.stub(),
|
||||||
|
setVaultPassword: sinon.stub(),
|
||||||
|
setVaultPasswordHint: sinon.stub(),
|
||||||
|
setVaultPasswordRepeat: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<VaultCreate vaultStore={ vaultVaultStore() } />,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
store: vaultReduxStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).find('VaultCreate').shallow();
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/VaultCreate', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event handlers', () => {
|
||||||
|
describe('onClose', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into closeCreateModal', () => {
|
||||||
|
expect(vaultStore.closeCreateModal).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onCreate', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(instance, 'onClose');
|
||||||
|
return instance.onCreate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.onClose.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into createVault', () => {
|
||||||
|
expect(vaultStore.createVault).to.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes modal', () => {
|
||||||
|
expect(instance.onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditDescription', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditDescription(null, 'testDescription');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultDescription', () => {
|
||||||
|
expect(vaultStore.setVaultDescription).to.have.been.calledWith('testDescription');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditName', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditName(null, 'testName');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultName', () => {
|
||||||
|
expect(vaultStore.setVaultName).to.have.been.calledWith('testName');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPassword(null, 'testPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPassword', () => {
|
||||||
|
expect(vaultStore.setVaultPassword).to.have.been.calledWith('testPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPasswordHint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPasswordHint(null, 'testPasswordHint');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPasswordHint', () => {
|
||||||
|
expect(vaultStore.setVaultPasswordHint).to.have.been.calledWith('testPasswordHint');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPasswordRepeat', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPasswordRepeat(null, 'testPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPasswordRepeat', () => {
|
||||||
|
expect(vaultStore.setVaultPasswordRepeat).to.have.been.calledWith('testPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/modals/VaultLock/index.js
Normal file
17
js/src/modals/VaultLock/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultLock';
|
92
js/src/modals/VaultLock/vaultLock.js
Normal file
92
js/src/modals/VaultLock/vaultLock.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
|
import { ConfirmDialog, VaultCard } from '~/ui';
|
||||||
|
|
||||||
|
import styles from '../VaultUnlock/vaultUnlock.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class VaultLock extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isBusyLock, isModalLockOpen, vault } = this.props.vaultStore;
|
||||||
|
|
||||||
|
if (!isModalLockOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDialog
|
||||||
|
busy={ isBusyLock }
|
||||||
|
disabledConfirm={ isBusyLock }
|
||||||
|
disabledDeny={ isBusyLock }
|
||||||
|
onConfirm={ this.onExecute }
|
||||||
|
onDeny={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmClose.title'
|
||||||
|
defaultMessage='Close Vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={ styles.textbox }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmClose.info'
|
||||||
|
defaultMessage="You are about to close a vault. Any accounts associated with the vault won't be visible after this operation concludes. To view the associated accounts, open the vault again."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VaultCard.Layout
|
||||||
|
withBorder
|
||||||
|
vault={ vault }
|
||||||
|
/>
|
||||||
|
</ConfirmDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
return this.props.vaultStore
|
||||||
|
.closeVault()
|
||||||
|
.catch(this.props.newError)
|
||||||
|
.then(this.onClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.vaultStore.closeLockModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(VaultLock);
|
131
js/src/modals/VaultLock/vaultLock.spec.js
Normal file
131
js/src/modals/VaultLock/vaultLock.spec.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultLock from './';
|
||||||
|
|
||||||
|
const VAULT = {
|
||||||
|
name: 'testVault'
|
||||||
|
};
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let reduxStore;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createReduxStore () {
|
||||||
|
reduxStore = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return reduxStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
isBusyLock: false,
|
||||||
|
isModalLockOpen: true,
|
||||||
|
vault: VAULT,
|
||||||
|
vaultName: VAULT.name,
|
||||||
|
vaults: [VAULT],
|
||||||
|
closeLockModal: sinon.stub(),
|
||||||
|
closeVault: sinon.stub().resolves(true)
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<VaultLock vaultStore={ createVaultStore() } />,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
store: createReduxStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).find('VaultLock').shallow();
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/VaultLock', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ConfirmDialog', () => {
|
||||||
|
let dialog;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dialog = component.find('ConfirmDialog');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the dialog', () => {
|
||||||
|
expect(dialog.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes onConfirm as onExecute', () => {
|
||||||
|
expect(dialog.props().onConfirm).to.equal(instance.onExecute);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes onDeny as onClose', () => {
|
||||||
|
expect(dialog.props().onDeny).to.equal(instance.onClose);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event methods', () => {
|
||||||
|
describe('onExecute', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(instance, 'onClose');
|
||||||
|
return instance.onExecute();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.onClose.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the modal', () => {
|
||||||
|
expect(instance.onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into vaultStore.closeVault', () => {
|
||||||
|
expect(vaultStore.closeVault).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClose', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into closeLockModal', () => {
|
||||||
|
expect(vaultStore.closeLockModal).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/modals/VaultUnlock/index.js
Normal file
17
js/src/modals/VaultUnlock/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultUnlock';
|
27
js/src/modals/VaultUnlock/vaultUnlock.css
Normal file
27
js/src/modals/VaultUnlock/vaultUnlock.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity is free software: you can redistribute it and/or modify
|
||||||
|
/* it under the terms of the GNU General Public License as published by
|
||||||
|
/* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity is distributed in the hope that it will be useful,
|
||||||
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/* GNU General Public License for more details.
|
||||||
|
/*
|
||||||
|
/* You should have received a copy of the GNU General Public License
|
||||||
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.passwordHint {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-size: 0.75em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textbox {
|
||||||
|
line-height: 1.5em;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
118
js/src/modals/VaultUnlock/vaultUnlock.js
Normal file
118
js/src/modals/VaultUnlock/vaultUnlock.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
|
import { ConfirmDialog, Input, VaultCard } from '~/ui';
|
||||||
|
|
||||||
|
import styles from './vaultUnlock.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class VaultUnlock extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isBusyUnlock, isModalUnlockOpen, vault, vaultPassword } = this.props.vaultStore;
|
||||||
|
|
||||||
|
if (!isModalUnlockOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDialog
|
||||||
|
busy={ isBusyUnlock }
|
||||||
|
disabledConfirm={ isBusyUnlock }
|
||||||
|
disabledDeny={ isBusyUnlock }
|
||||||
|
onConfirm={ this.onExecute }
|
||||||
|
onDeny={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmOpen.title'
|
||||||
|
defaultMessage='Open Vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={ styles.textbox }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmOpen.info'
|
||||||
|
defaultMessage='You are about to open a vault. After confirming your password, all accounts associated with this vault will be visible. Closing the vault will remove the accounts from view until the vault is opened again.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VaultCard.Layout
|
||||||
|
withBorder
|
||||||
|
vault={ vault }
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmOpen.password.hint'
|
||||||
|
defaultMessage='the password specified when creating the vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.confirmOpen.password.label'
|
||||||
|
defaultMessage='vault password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPassword }
|
||||||
|
onSubmit={ this.onExecute }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPassword }
|
||||||
|
/>
|
||||||
|
<div className={ styles.passwordHint }>
|
||||||
|
{ vault.meta.passwordHint }
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
</ConfirmDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPassword = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.vaultStore.closeUnlockModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
return this.props.vaultStore
|
||||||
|
.openVault()
|
||||||
|
.catch(this.props.newError)
|
||||||
|
.then(this.onClose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(VaultUnlock);
|
146
js/src/modals/VaultUnlock/vaultUnlock.spec.js
Normal file
146
js/src/modals/VaultUnlock/vaultUnlock.spec.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultUnlock from './';
|
||||||
|
|
||||||
|
const VAULT = {
|
||||||
|
name: 'testVault',
|
||||||
|
meta: {
|
||||||
|
passwordHint: 'some hint'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let reduxStore;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createReduxStore () {
|
||||||
|
reduxStore = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return reduxStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
isBusyUnlock: false,
|
||||||
|
isModalUnlockOpen: true,
|
||||||
|
vault: VAULT,
|
||||||
|
vaultName: VAULT.name,
|
||||||
|
vaultPassword: 'testPassword',
|
||||||
|
vaults: [VAULT],
|
||||||
|
closeUnlockModal: sinon.stub(),
|
||||||
|
openVault: sinon.stub().resolves(true),
|
||||||
|
setVaultPassword: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<VaultUnlock vaultStore={ createVaultStore() } />,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
store: createReduxStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).find('VaultUnlock').shallow();
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/VaultUnlock', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ConfirmDialog', () => {
|
||||||
|
let dialog;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dialog = component.find('ConfirmDialog');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the dialog', () => {
|
||||||
|
expect(dialog.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes onConfirm as onExecute', () => {
|
||||||
|
expect(dialog.props().onConfirm).to.equal(instance.onExecute);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes onDeny as onClose', () => {
|
||||||
|
expect(dialog.props().onDeny).to.equal(instance.onClose);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event methods', () => {
|
||||||
|
describe('onExecute', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(instance, 'onClose');
|
||||||
|
return instance.onExecute();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.onClose.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the modal', () => {
|
||||||
|
expect(instance.onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into vaultStore.openVault', () => {
|
||||||
|
expect(vaultStore.openVault).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClose', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into closeUnlockModal', () => {
|
||||||
|
expect(vaultStore.closeUnlockModal).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPassword(null, 'someVaultPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into vaultStore.setVaultPassword', () => {
|
||||||
|
expect(vaultStore.setVaultPassword).to.have.been.calledWith('someVaultPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -14,44 +14,26 @@
|
|||||||
// 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 AddAddress from './AddAddress';
|
export AddAddress from './AddAddress';
|
||||||
import AddContract from './AddContract';
|
export AddContract from './AddContract';
|
||||||
import CreateAccount from './CreateAccount';
|
export CreateAccount from './CreateAccount';
|
||||||
import CreateWallet from './CreateWallet';
|
export CreateWallet from './CreateWallet';
|
||||||
import DappPermissions from './DappPermissions';
|
export DappPermissions from './DappPermissions';
|
||||||
import DappsVisible from './AddDapps';
|
export DappsVisible from './AddDapps';
|
||||||
import DeleteAccount from './DeleteAccount';
|
export DeleteAccount from './DeleteAccount';
|
||||||
import DeployContract from './DeployContract';
|
export DeployContract from './DeployContract';
|
||||||
import EditMeta from './EditMeta';
|
export EditMeta from './EditMeta';
|
||||||
import ExecuteContract from './ExecuteContract';
|
export ExecuteContract from './ExecuteContract';
|
||||||
import FirstRun from './FirstRun';
|
export FirstRun from './FirstRun';
|
||||||
import LoadContract from './LoadContract';
|
export LoadContract from './LoadContract';
|
||||||
import SaveContract from './SaveContract';
|
export PasswordManager from './PasswordManager';
|
||||||
import Shapeshift from './Shapeshift';
|
export SaveContract from './SaveContract';
|
||||||
import Verification from './Verification';
|
export Shapeshift from './Shapeshift';
|
||||||
import Transfer from './Transfer';
|
export Transfer from './Transfer';
|
||||||
import PasswordManager from './PasswordManager';
|
export UpgradeParity from './UpgradeParity';
|
||||||
import UpgradeParity from './UpgradeParity';
|
export VaultAccounts from './VaultAccounts';
|
||||||
import WalletSettings from './WalletSettings';
|
export VaultCreate from './VaultCreate';
|
||||||
|
export VaultLock from './VaultLock';
|
||||||
export {
|
export VaultUnlock from './VaultUnlock';
|
||||||
AddAddress,
|
export Verification from './Verification';
|
||||||
AddContract,
|
export WalletSettings from './WalletSettings';
|
||||||
CreateAccount,
|
|
||||||
CreateWallet,
|
|
||||||
DappPermissions,
|
|
||||||
DappsVisible,
|
|
||||||
DeleteAccount,
|
|
||||||
DeployContract,
|
|
||||||
EditMeta,
|
|
||||||
ExecuteContract,
|
|
||||||
FirstRun,
|
|
||||||
LoadContract,
|
|
||||||
SaveContract,
|
|
||||||
Shapeshift,
|
|
||||||
Verification,
|
|
||||||
Transfer,
|
|
||||||
PasswordManager,
|
|
||||||
UpgradeParity,
|
|
||||||
WalletSettings
|
|
||||||
};
|
|
||||||
|
@ -74,7 +74,7 @@ $codeColor: #93a1a1;
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background-color: #$codeBackground;
|
background-color: $codeBackground;
|
||||||
color: $codeColor;
|
color: $codeColor;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
|
|
||||||
@ -86,5 +86,6 @@ $codeColor: #93a1a1;
|
|||||||
.component {
|
.component {
|
||||||
flex: 3;
|
flex: 3;
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import AccountCard from '~/ui/AccountCard/accountCard.example';
|
||||||
import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example';
|
import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example';
|
||||||
import QrCode from '~/ui/QrCode/qrCode.example';
|
import QrCode from '~/ui/QrCode/qrCode.example';
|
||||||
import SectionList from '~/ui/SectionList/sectionList.example';
|
import SectionList from '~/ui/SectionList/sectionList.example';
|
||||||
@ -25,6 +26,7 @@ import Portal from '~/ui/Portal/portal.example';
|
|||||||
import PlaygroundStore from './store';
|
import PlaygroundStore from './store';
|
||||||
import styles from './playground.css';
|
import styles from './playground.css';
|
||||||
|
|
||||||
|
PlaygroundStore.register(<AccountCard />);
|
||||||
PlaygroundStore.register(<CurrencySymbol />);
|
PlaygroundStore.register(<CurrencySymbol />);
|
||||||
PlaygroundStore.register(<QrCode />);
|
PlaygroundStore.register(<QrCode />);
|
||||||
PlaygroundStore.register(<SectionList />);
|
PlaygroundStore.register(<SectionList />);
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
Contract, Contracts, Dapp, Dapps, HistoryStore, Home,
|
Contract, Contracts, Dapp, Dapps, HistoryStore, Home,
|
||||||
Settings, SettingsBackground, SettingsParity, SettingsProxy,
|
Settings, SettingsBackground, SettingsParity, SettingsProxy,
|
||||||
SettingsViews, Signer, Status,
|
SettingsViews, Signer, Status,
|
||||||
Wallet, Web, WriteContract
|
Vaults, Wallet, Web, WriteContract
|
||||||
} from '~/views';
|
} from '~/views';
|
||||||
import builtinDapps from '~/views/Dapps/builtin.json';
|
import builtinDapps from '~/views/Dapps/builtin.json';
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ const accountsRoutes = [
|
|||||||
accountsHistory.add(params.address, 'account');
|
accountsHistory.add(params.address, 'account');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ path: '/vaults', component: Vaults },
|
||||||
{
|
{
|
||||||
path: '/wallet/:address',
|
path: '/wallet/:address',
|
||||||
component: Wallet,
|
component: Wallet,
|
||||||
|
@ -16,18 +16,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.account {
|
.account {
|
||||||
padding: 1em;
|
align-items: stretch;
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
overflow: hidden;
|
||||||
transition: transform ease-out 0.1s;
|
transition: transform ease-out 0.1s;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.copied {
|
&.copied {
|
||||||
animation-duration: 0.25s;
|
animation-duration: 0.25s;
|
||||||
@ -53,6 +49,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainContainer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagsContainer {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: calc(100% - 0.5em);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 0.25em;
|
||||||
|
padding-right: 2em;
|
||||||
|
position: absolute;
|
||||||
|
right: -2.5em;
|
||||||
|
transition: background-color 0.2s ease-out;
|
||||||
|
width: calc(100% + 0.25em);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
padding-left: 0.5em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.infoContainer {
|
.infoContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
110
js/src/ui/AccountCard/accountCard.example.js
Normal file
110
js/src/ui/AccountCard/accountCard.example.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import PlaygroundExample from '~/playground/playgroundExample';
|
||||||
|
|
||||||
|
import AccountCard from './accountCard';
|
||||||
|
|
||||||
|
export default class AccountCardExample extends Component {
|
||||||
|
render () {
|
||||||
|
const account = {
|
||||||
|
address: '0x639ba260535db072a41115c472830846e4e9ad0f',
|
||||||
|
description: 'This is a description for the main account',
|
||||||
|
meta: {
|
||||||
|
tags: [ 'important', 'zargo' ]
|
||||||
|
},
|
||||||
|
name: 'Main Account'
|
||||||
|
};
|
||||||
|
|
||||||
|
const balance = {
|
||||||
|
tokens: [
|
||||||
|
{
|
||||||
|
value: 100000000000000000000,
|
||||||
|
token: {
|
||||||
|
tag: 'ETH'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const accountManyTags = {
|
||||||
|
...account,
|
||||||
|
meta: { tags: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => `tag #${n}`) }
|
||||||
|
};
|
||||||
|
|
||||||
|
const accountNoTags = {
|
||||||
|
...account,
|
||||||
|
meta: { tags: [] }
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PlaygroundExample name='Standard Account Card'>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</PlaygroundExample>
|
||||||
|
|
||||||
|
<PlaygroundExample name='Small Account Card'>
|
||||||
|
<div style={ { width: 300 } }>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PlaygroundExample>
|
||||||
|
|
||||||
|
<PlaygroundExample name='Many Tags Account Card'>
|
||||||
|
<div style={ { width: 300 } }>
|
||||||
|
<AccountCard
|
||||||
|
account={ accountManyTags }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PlaygroundExample>
|
||||||
|
|
||||||
|
<PlaygroundExample name='No Tags Account Card'>
|
||||||
|
<div style={ { width: 300 } }>
|
||||||
|
<AccountCard
|
||||||
|
account={ accountNoTags }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PlaygroundExample>
|
||||||
|
|
||||||
|
<PlaygroundExample name='Two Account Card'>
|
||||||
|
<div style={ { display: 'flex' } }>
|
||||||
|
<div style={ { margin: '0 0.5em' } }>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={ { margin: '0 0.5em' } }>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
balance={ balance }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PlaygroundExample>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -58,28 +58,45 @@ export default class AccountCard extends Component {
|
|||||||
onFocus={ this.onFocus }
|
onFocus={ this.onFocus }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
>
|
>
|
||||||
<div className={ styles.infoContainer }>
|
<div className={ styles.mainContainer }>
|
||||||
<IdentityIcon address={ address } />
|
<div className={ styles.infoContainer }>
|
||||||
<div className={ styles.accountInfo }>
|
<IdentityIcon address={ address } />
|
||||||
<div className={ styles.accountName }>
|
<div className={ styles.accountInfo }>
|
||||||
<IdentityName
|
<div className={ styles.accountName }>
|
||||||
address={ address }
|
<IdentityName
|
||||||
name={ name }
|
address={ address }
|
||||||
unknown
|
name={ name }
|
||||||
/>
|
unknown
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ this.renderDescription(description) }
|
||||||
|
{ this.renderAddress(address) }
|
||||||
</div>
|
</div>
|
||||||
{ this.renderDescription(description) }
|
|
||||||
{ this.renderAddress(address) }
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Balance
|
||||||
|
balance={ balance }
|
||||||
|
className={ styles.balance }
|
||||||
|
showOnlyEth
|
||||||
|
showZeroValues
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tags tags={ tags } />
|
{
|
||||||
<Balance
|
tags && tags.length > 0
|
||||||
balance={ balance }
|
? (
|
||||||
className={ styles.balance }
|
<div className={ styles.tagsContainer }>
|
||||||
showOnlyEth
|
<div className={ styles.tags }>
|
||||||
showZeroValues
|
<Tags
|
||||||
/>
|
floating={ false }
|
||||||
|
horizontal
|
||||||
|
tags={ tags }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,9 @@ function render (props = {}) {
|
|||||||
address: TEST_ADDRESS,
|
address: TEST_ADDRESS,
|
||||||
description: 'testDescription',
|
description: 'testDescription',
|
||||||
name: TEST_NAME,
|
name: TEST_NAME,
|
||||||
meta: {}
|
meta: {
|
||||||
|
tags: [ 'tag 1', 'tag 2' ]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import Modal from '../Modal';
|
import Portal from '../Portal';
|
||||||
import { CancelIcon, CheckIcon } from '../Icons';
|
import { CancelIcon, CheckIcon } from '../Icons';
|
||||||
|
|
||||||
import styles from './confirmDialog.css';
|
import styles from './confirmDialog.css';
|
||||||
@ -42,47 +42,58 @@ export default class ConfirmDialog extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
disabledConfirm: PropTypes.bool,
|
||||||
|
disabledDeny: PropTypes.bool,
|
||||||
|
busy: PropTypes.bool,
|
||||||
iconConfirm: PropTypes.node,
|
iconConfirm: PropTypes.node,
|
||||||
iconDeny: PropTypes.node,
|
iconDeny: PropTypes.node,
|
||||||
labelConfirm: PropTypes.string,
|
labelConfirm: PropTypes.string,
|
||||||
labelDeny: PropTypes.string,
|
labelDeny: PropTypes.string,
|
||||||
onConfirm: PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
onDeny: PropTypes.func.isRequired,
|
onDeny: PropTypes.func.isRequired,
|
||||||
|
open: PropTypes.bool,
|
||||||
title: nodeOrStringProptype().isRequired,
|
title: nodeOrStringProptype().isRequired,
|
||||||
visible: PropTypes.bool.isRequired
|
visible: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, className, title, visible } = this.props;
|
const { busy, children, className, disabledConfirm, disabledDeny, iconConfirm, iconDeny, labelConfirm, labelDeny, onConfirm, onDeny, open, title, visible } = this.props;
|
||||||
|
|
||||||
|
// TODO: visible is for compatibility with existing, open aligns with Portal.
|
||||||
|
// (Cleanup once all uses of ConfirmDialog has been migrated)
|
||||||
|
if (!visible && !open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Portal
|
||||||
|
buttons={ [
|
||||||
|
<Button
|
||||||
|
disabled={ disabledDeny }
|
||||||
|
icon={ iconDeny || <CancelIcon /> }
|
||||||
|
key='deny'
|
||||||
|
label={ labelDeny || DEFAULT_NO }
|
||||||
|
onClick={ onDeny }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ disabledConfirm }
|
||||||
|
icon={ iconConfirm || <CheckIcon /> }
|
||||||
|
key='confirm'
|
||||||
|
label={ labelConfirm || DEFAULT_YES }
|
||||||
|
onClick={ onConfirm }
|
||||||
|
/>
|
||||||
|
] }
|
||||||
|
busy={ busy }
|
||||||
className={ className }
|
className={ className }
|
||||||
actions={ this.renderActions() }
|
isSmallModal
|
||||||
|
onClose={ onDeny }
|
||||||
title={ title }
|
title={ title }
|
||||||
visible={ visible }
|
open
|
||||||
>
|
>
|
||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActions () {
|
|
||||||
const { iconConfirm, iconDeny, labelConfirm, labelDeny, onConfirm, onDeny } = this.props;
|
|
||||||
|
|
||||||
return [
|
|
||||||
<Button
|
|
||||||
icon={ iconDeny || <CancelIcon /> }
|
|
||||||
label={ labelDeny || DEFAULT_NO }
|
|
||||||
onClick={ onDeny }
|
|
||||||
/>,
|
|
||||||
<Button
|
|
||||||
icon={ iconConfirm || <CheckIcon /> }
|
|
||||||
label={ labelConfirm || DEFAULT_YES }
|
|
||||||
onClick={ onConfirm }
|
|
||||||
/>
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,41 +15,24 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React, { PropTypes } from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import muiTheme from '../Theme';
|
|
||||||
|
|
||||||
import ConfirmDialog from './';
|
import ConfirmDialog from './';
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let instance;
|
|
||||||
let onConfirm;
|
let onConfirm;
|
||||||
let onDeny;
|
let onDeny;
|
||||||
|
|
||||||
function createRedux () {
|
|
||||||
return {
|
|
||||||
dispatch: sinon.stub(),
|
|
||||||
subscribe: sinon.stub(),
|
|
||||||
getState: () => {
|
|
||||||
return {
|
|
||||||
settings: {
|
|
||||||
backgroundSeed: 'xyz'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function render (props = {}) {
|
function render (props = {}) {
|
||||||
onConfirm = sinon.stub();
|
onConfirm = sinon.stub();
|
||||||
onDeny = sinon.stub();
|
onDeny = sinon.stub();
|
||||||
|
|
||||||
if (props.visible === undefined) {
|
if (props.open === undefined) {
|
||||||
props.visible = true;
|
props.open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseComponent = shallow(
|
component = shallow(
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
{ ...props }
|
{ ...props }
|
||||||
title='test title'
|
title='test title'
|
||||||
@ -62,57 +45,54 @@ function render (props = {}) {
|
|||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
instance = baseComponent.instance();
|
|
||||||
component = baseComponent.find('Connect(Modal)').shallow({
|
|
||||||
childContextTypes: {
|
|
||||||
muiTheme: PropTypes.object,
|
|
||||||
store: PropTypes.object
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
muiTheme,
|
|
||||||
store: createRedux()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ui/ConfirmDialog', () => {
|
describe('ui/ConfirmDialog', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders defaults', () => {
|
it('renders defaults', () => {
|
||||||
expect(render()).to.be.ok;
|
expect(component).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the body as provided', () => {
|
it('renders the body as provided', () => {
|
||||||
expect(render().find('div[id="testContent"]').text()).to.equal('some test content');
|
expect(component.find('div[id="testContent"]').text()).to.equal('some test content');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('properties', () => {
|
describe('Portal properties', () => {
|
||||||
let props;
|
let props;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
props = render().props();
|
props = component.find('Portal').props();
|
||||||
});
|
|
||||||
|
|
||||||
it('passes the actions', () => {
|
|
||||||
expect(props.actions).to.deep.equal(instance.renderActions());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes title', () => {
|
it('passes title', () => {
|
||||||
expect(props.title).to.equal('test title');
|
expect(props.title).to.equal('test title');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes visiblity flag', () => {
|
it('passes open flag', () => {
|
||||||
expect(props.visible).to.be.true;
|
expect(props.open).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('renderActions', () => {
|
it('passes the small flag', () => {
|
||||||
describe('defaults', () => {
|
expect(props.isSmallModal).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps onClose to onDeny', () => {
|
||||||
|
expect(props.onClose).to.equal(onDeny);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buttons', () => {
|
||||||
let buttons;
|
let buttons;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render();
|
buttons = component.props().buttons;
|
||||||
buttons = instance.renderActions();
|
});
|
||||||
|
|
||||||
|
it('passes the buttons', () => {
|
||||||
|
expect(buttons.length).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with supplied onConfim/onDeny callbacks', () => {
|
it('renders with supplied onConfim/onDeny callbacks', () => {
|
||||||
@ -129,29 +109,27 @@ describe('ui/ConfirmDialog', () => {
|
|||||||
expect(buttons[0].props.icon.type.displayName).to.equal('ContentClear');
|
expect(buttons[0].props.icon.type.displayName).to.equal('ContentClear');
|
||||||
expect(buttons[1].props.icon.type.displayName).to.equal('NavigationCheck');
|
expect(buttons[1].props.icon.type.displayName).to.equal('NavigationCheck');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('overrides', () => {
|
describe('overrides', () => {
|
||||||
let buttons;
|
beforeEach(() => {
|
||||||
|
render({
|
||||||
beforeEach(() => {
|
labelConfirm: 'labelConfirm',
|
||||||
render({
|
labelDeny: 'labelDeny',
|
||||||
labelConfirm: 'labelConfirm',
|
iconConfirm: 'iconConfirm',
|
||||||
labelDeny: 'labelDeny',
|
iconDeny: 'iconDeny'
|
||||||
iconConfirm: 'iconConfirm',
|
});
|
||||||
iconDeny: 'iconDeny'
|
buttons = component.props().buttons;
|
||||||
});
|
});
|
||||||
buttons = instance.renderActions();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders supplied labels', () => {
|
it('renders supplied labels', () => {
|
||||||
expect(buttons[0].props.label).to.equal('labelDeny');
|
expect(buttons[0].props.label).to.equal('labelDeny');
|
||||||
expect(buttons[1].props.label).to.equal('labelConfirm');
|
expect(buttons[1].props.label).to.equal('labelConfirm');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders supplied icons', () => {
|
it('renders supplied icons', () => {
|
||||||
expect(buttons[0].props.icon).to.equal('iconDeny');
|
expect(buttons[0].props.icon).to.equal('iconDeny');
|
||||||
expect(buttons[1].props.icon).to.equal('iconConfirm');
|
expect(buttons[1].props.icon).to.equal('iconConfirm');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ $smallFontSize: 0.75rem;
|
|||||||
color: $bylineColor;
|
color: $bylineColor;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
line-height: $bylineLineHeight;
|
line-height: $bylineLineHeight;
|
||||||
|
min-height: $bylineMaxHeight;
|
||||||
max-height: $bylineMaxHeight;
|
max-height: $bylineMaxHeight;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -45,5 +46,8 @@ $smallFontSize: 0.75rem;
|
|||||||
.title {
|
.title {
|
||||||
line-height: $titleLineHeight;
|
line-height: $titleLineHeight;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -16,33 +16,39 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$background: rgba(18, 18, 18, 0.85);
|
$background: rgba(18, 18, 18, 0.85);
|
||||||
$backgroundOverlay: rgba(18, 18, 18, 1);
|
$backgroundHover: rgba(18, 18, 18, 1);
|
||||||
|
$transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background: $background;
|
background: $background;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0em;
|
padding: 0em;
|
||||||
transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
position: relative;
|
||||||
|
transition: $transitionAll;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.hoverOverlay {
|
.hoverOverlay {
|
||||||
background: $backgroundOverlay;
|
background: $background;
|
||||||
display: none;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-top: -1.5em;
|
margin-top: -1.5em;
|
||||||
|
opacity: inherit;
|
||||||
padding: 0 1.5em 1.5em 1.5em;
|
padding: 0 1.5em 1.5em 1.5em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
transition: $transitionAll;
|
||||||
|
transform: scale(0.5, 0);
|
||||||
|
transform-origin: top center;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $backgroundOverlay;
|
background: $backgroundHover;
|
||||||
|
|
||||||
.hoverOverlay {
|
.hoverOverlay {
|
||||||
display: block;
|
background: $backgroundHover;
|
||||||
|
transform: scale(1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export AccountsIcon from 'material-ui/svg-icons/action/account-balance-wallet';
|
||||||
export AddIcon from 'material-ui/svg-icons/content/add';
|
export AddIcon from 'material-ui/svg-icons/content/add';
|
||||||
export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
|
export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
|
||||||
export CancelIcon from 'material-ui/svg-icons/content/clear';
|
export CancelIcon from 'material-ui/svg-icons/content/clear';
|
||||||
@ -28,6 +29,7 @@ export DeleteIcon from 'material-ui/svg-icons/action/delete';
|
|||||||
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
export EditIcon from 'material-ui/svg-icons/content/create';
|
export EditIcon from 'material-ui/svg-icons/content/create';
|
||||||
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
|
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
|
||||||
|
export KeyIcon from 'material-ui/svg-icons/communication/vpn-key';
|
||||||
export LinkIcon from 'material-ui/svg-icons/content/link';
|
export LinkIcon from 'material-ui/svg-icons/content/link';
|
||||||
export LockedIcon from 'material-ui/svg-icons/action/lock';
|
export LockedIcon from 'material-ui/svg-icons/action/lock';
|
||||||
export MoveIcon from 'material-ui/svg-icons/action/open-with';
|
export MoveIcon from 'material-ui/svg-icons/action/open-with';
|
||||||
@ -41,6 +43,7 @@ export SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
|||||||
export StarCircleIcon from 'material-ui/svg-icons/action/stars';
|
export StarCircleIcon from 'material-ui/svg-icons/action/stars';
|
||||||
export StarIcon from 'material-ui/svg-icons/toggle/star';
|
export StarIcon from 'material-ui/svg-icons/toggle/star';
|
||||||
export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
|
export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
|
||||||
|
export UnlockedIcon from 'material-ui/svg-icons/action/lock-open';
|
||||||
export VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
export VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
||||||
export VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
export VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||||
export VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
export VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
||||||
|
28
js/src/ui/Icons/index.spec.js
Normal file
28
js/src/ui/Icons/index.spec.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import * as Icons from './';
|
||||||
|
|
||||||
|
describe('ui/Icons', () => {
|
||||||
|
Object.keys(Icons).forEach((icon) => {
|
||||||
|
it(`contains & renders ${icon}`, () => {
|
||||||
|
expect(shallow(createElement(Icons[icon]))).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -54,9 +54,19 @@ export default class MethodDecodingStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadFromAbi (_abi, contractAddress) {
|
loadFromAbi (_abi, contractAddress) {
|
||||||
const abi = new Abi(_abi);
|
let abi;
|
||||||
|
|
||||||
if (contractAddress && abi) {
|
try {
|
||||||
|
abi = new Abi(_abi);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('loadFromAbi', error, _abi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!abi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contractAddress) {
|
||||||
this._contractsAbi[contractAddress] = abi;
|
this._contractsAbi[contractAddress] = abi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,22 +31,20 @@ export default class Page extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { buttons, className, children, title } = this.props;
|
const { buttons, className, children, title } = this.props;
|
||||||
const classes = `${styles.layout} ${className}`;
|
|
||||||
let actionbar = null;
|
|
||||||
|
|
||||||
if (title || buttons) {
|
|
||||||
actionbar = (
|
|
||||||
<Actionbar
|
|
||||||
buttons={ buttons }
|
|
||||||
title={ title }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ actionbar }
|
{
|
||||||
<div className={ classes }>
|
title || buttons
|
||||||
|
? (
|
||||||
|
<Actionbar
|
||||||
|
buttons={ buttons }
|
||||||
|
title={ title }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className={ [styles.layout, className].join(' ') }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
79
js/src/ui/Page/page.spec.js
Normal file
79
js/src/ui/Page/page.spec.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Page from './';
|
||||||
|
|
||||||
|
const BUTTONS = ['buttonA', 'buttonB'];
|
||||||
|
const CLASSNAME = 'testClass';
|
||||||
|
const TESTTEXT = 'testing children';
|
||||||
|
const TITLE = 'test title';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<Page
|
||||||
|
buttons={ BUTTONS }
|
||||||
|
className={ CLASSNAME }
|
||||||
|
title={ TITLE }
|
||||||
|
>
|
||||||
|
<div id='testContent'>
|
||||||
|
{ TESTTEXT }
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/Page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the children', () => {
|
||||||
|
expect(component.find('div[id="testContent"]').text()).to.equal(TESTTEXT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('ActionBar', () => {
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
actions = component.find('Actionbar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the actionbar', () => {
|
||||||
|
expect(actions.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the provided title', () => {
|
||||||
|
expect(actions.props().title).to.equal(TITLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passed the provided buttons', () => {
|
||||||
|
expect(actions.props().buttons).to.equal(BUTTONS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
$modalMargin: 1.5em;
|
$modalMargin: 1.5em;
|
||||||
$modalPadding: 1.5em;
|
$modalPadding: 1.5em;
|
||||||
|
$modalPaddingChild: 3em;
|
||||||
$modalBackZ: 2500;
|
$modalBackZ: 2500;
|
||||||
|
|
||||||
/* This should be the default case, the Portal used as a stand-alone modal */
|
/* This should be the default case, the Portal used as a stand-alone modal */
|
||||||
@ -50,7 +51,7 @@ $popoverZ: 3600;
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
z-index: -1;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@ -67,11 +68,24 @@ $popoverZ: 3600;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.modal {
|
&.modal {
|
||||||
bottom: $modalBottom;
|
&:not(.small) {
|
||||||
left: $modalLeft;
|
bottom: $modalBottom;
|
||||||
right: $modalRight;
|
left: $modalLeft;
|
||||||
top: $modalTop;
|
right: $modalRight;
|
||||||
z-index: $modalZ;
|
top: $modalTop;
|
||||||
|
z-index: $modalZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Small Portals don't adjust their overall height like we have with the
|
||||||
|
/* rest, so really tiny screens and large small Portals (it shouldn't be be done,
|
||||||
|
/* but may well be) will scretch to non-visible areas.
|
||||||
|
*/
|
||||||
|
&.small {
|
||||||
|
margin: 1.5em auto;
|
||||||
|
max-width: 768px;
|
||||||
|
position: relative;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.popover {
|
&.popover {
|
||||||
@ -100,8 +114,11 @@ $popoverZ: 3600;
|
|||||||
|
|
||||||
.childContainer {
|
.childContainer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin: 0 -$modalPadding;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding: 0 $modalPaddingChild;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeIcon {
|
.closeIcon {
|
||||||
|
@ -43,6 +43,7 @@ export default class Portal extends Component {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
hideClose: PropTypes.bool,
|
hideClose: PropTypes.bool,
|
||||||
isChildModal: PropTypes.bool,
|
isChildModal: PropTypes.bool,
|
||||||
|
isSmallModal: PropTypes.bool,
|
||||||
onKeyDown: PropTypes.func,
|
onKeyDown: PropTypes.func,
|
||||||
steps: PropTypes.array,
|
steps: PropTypes.array,
|
||||||
title: nodeOrStringProptype()
|
title: nodeOrStringProptype()
|
||||||
@ -63,7 +64,7 @@ export default class Portal extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { activeStep, busy, busySteps, children, className, isChildModal, open, steps, title } = this.props;
|
const { activeStep, busy, busySteps, children, className, isChildModal, isSmallModal, open, steps, title } = this.props;
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
return null;
|
return null;
|
||||||
@ -85,6 +86,9 @@ export default class Portal extends Component {
|
|||||||
isChildModal
|
isChildModal
|
||||||
? styles.popover
|
? styles.popover
|
||||||
: styles.modal,
|
: styles.modal,
|
||||||
|
isSmallModal
|
||||||
|
? styles.small
|
||||||
|
: null,
|
||||||
className
|
className
|
||||||
].join(' ')
|
].join(' ')
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,18 @@ export default class SectionList extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rendered = items
|
||||||
|
.map(this.renderItem)
|
||||||
|
.filter((item) => item);
|
||||||
|
|
||||||
|
if (!rendered.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={ [styles.section, className].join(' ') }>
|
<section className={ [styles.section, className].join(' ') }>
|
||||||
{ this.renderOverlay() }
|
{ this.renderOverlay() }
|
||||||
{ chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) }
|
{ chunkArray(rendered, ITEMS_PER_ROW).map(this.renderRow) }
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -74,11 +82,7 @@ export default class SectionList extends Component {
|
|||||||
className={ styles.row }
|
className={ styles.row }
|
||||||
key={ `row_${index}` }
|
key={ `row_${index}` }
|
||||||
>
|
>
|
||||||
{
|
{ row }
|
||||||
row
|
|
||||||
.map(this.renderItem)
|
|
||||||
.filter((item) => item)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -74,10 +74,6 @@ describe('SectionList', () => {
|
|||||||
it('adds a key for the row', () => {
|
it('adds a key for the row', () => {
|
||||||
expect(row.key).to.be.ok;
|
expect(row.key).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls renderItem for the items', () => {
|
|
||||||
expect(instance.renderItem).to.have.been.calledTwice;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderItem', () => {
|
describe('renderItem', () => {
|
||||||
|
@ -18,16 +18,39 @@
|
|||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: absolute;
|
|
||||||
right: 0.25rem;
|
&.floating {
|
||||||
top: 0;
|
position: absolute;
|
||||||
|
right: 0.25rem;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating .tag {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal .tag {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 150%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
background: rgba(255, 255, 255, 0.07);
|
background: rgba(255, 255, 255, 0.07);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
margin: 0.75em 0.5em 0 0;
|
margin: 0 0.5em;
|
||||||
padding: 0.25em 1em;
|
padding: 0.25em 1em;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.2s ease-out;
|
transition: opacity 0.2s ease-out;
|
||||||
|
@ -22,20 +22,37 @@ import styles from './tags.css';
|
|||||||
|
|
||||||
export default class Tags extends Component {
|
export default class Tags extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
floating: PropTypes.bool,
|
||||||
|
horizontal: PropTypes.bool,
|
||||||
handleAddSearchToken: PropTypes.func,
|
handleAddSearchToken: PropTypes.func,
|
||||||
setRefs: PropTypes.func,
|
setRefs: PropTypes.func,
|
||||||
tags: arrayOrObjectProptype()
|
tags: arrayOrObjectProptype()
|
||||||
}
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
horizontal: false,
|
||||||
|
floating: true
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { tags } = this.props;
|
const { floating, horizontal, tags } = this.props;
|
||||||
|
|
||||||
if (!tags || tags.length === 0) {
|
if (!tags || tags.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const classes = [ styles.tags ];
|
||||||
|
|
||||||
|
if (floating) {
|
||||||
|
classes.push(styles.floating);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizontal) {
|
||||||
|
classes.push(styles.horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.tags }>
|
<div className={ classes.join(' ') }>
|
||||||
{ this.renderTags() }
|
{ this.renderTags() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -66,7 +83,7 @@ export default class Tags extends Component {
|
|||||||
onClick={ onClick }
|
onClick={ onClick }
|
||||||
ref={ setRef }
|
ref={ setRef }
|
||||||
>
|
>
|
||||||
{ tag }
|
<span className={ styles.text }>{ tag }</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -15,12 +15,17 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.title {
|
.title,
|
||||||
|
.subtitle {
|
||||||
.steps {
|
.steps {
|
||||||
margin: -0.5em 0 -1em 0;
|
margin: -0.5em 0 -1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.waiting {
|
.waiting {
|
||||||
margin: 1em -1em -1em -1em;
|
margin: 1em -1.5em 0 -1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
@ -30,15 +30,18 @@ import styles from './title.css';
|
|||||||
export default class Title extends Component {
|
export default class Title extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
activeStep: PropTypes.number,
|
activeStep: PropTypes.number,
|
||||||
|
description: nodeOrStringProptype(),
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
busySteps: PropTypes.array,
|
busySteps: PropTypes.array,
|
||||||
|
byline: nodeOrStringProptype(),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
isSubTitle: PropTypes.bool,
|
||||||
steps: PropTypes.array,
|
steps: PropTypes.array,
|
||||||
title: nodeOrStringProptype()
|
title: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { activeStep, className, steps, title } = this.props;
|
const { activeStep, byline, className, description, isSubTitle, steps, title } = this.props;
|
||||||
|
|
||||||
if (!title && !steps) {
|
if (!title && !steps) {
|
||||||
return null;
|
return null;
|
||||||
@ -47,10 +50,17 @@ export default class Title extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
[styles.title, className].join(' ')
|
[
|
||||||
|
isSubTitle
|
||||||
|
? styles.subtitle
|
||||||
|
: styles.title,
|
||||||
|
className
|
||||||
|
].join(' ')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ContainerTitle
|
<ContainerTitle
|
||||||
|
byline={ byline }
|
||||||
|
description={ description }
|
||||||
title={
|
title={
|
||||||
steps
|
steps
|
||||||
? steps[activeStep || 0]
|
? steps[activeStep || 0]
|
||||||
|
90
js/src/ui/Title/title.spec.js
Normal file
90
js/src/ui/Title/title.spec.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Title from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
component = shallow(
|
||||||
|
<Title
|
||||||
|
activeStep={ 0 }
|
||||||
|
byline='testByline'
|
||||||
|
className='testClass'
|
||||||
|
description='testDescription'
|
||||||
|
title='testTitle'
|
||||||
|
{ ...props }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/Title', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('renderSteps', () => {
|
||||||
|
let stepper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
render({ steps: ['stepA', 'stepB'] });
|
||||||
|
stepper = shallow(instance.renderSteps());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Stepper', () => {
|
||||||
|
expect(stepper.find('Stepper').get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderTimeline', () => {
|
||||||
|
let steps;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
render({ steps: ['stepA', 'StepB'] });
|
||||||
|
steps = instance.renderTimeline();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Step', () => {
|
||||||
|
expect(steps.length).to.equal(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderWaiting', () => {
|
||||||
|
let waiting;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
render({ busy: true });
|
||||||
|
waiting = shallow(instance.renderWaiting());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the LinearProgress', () => {
|
||||||
|
expect(waiting.find('LinearProgress').get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/ui/VaultCard/Layout/index.js
Normal file
17
js/src/ui/VaultCard/Layout/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './layout';
|
45
js/src/ui/VaultCard/Layout/layout.css
Normal file
45
js/src/ui/VaultCard/Layout/layout.css
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity is free software: you can redistribute it and/or modify
|
||||||
|
/* it under the terms of the GNU General Public License as published by
|
||||||
|
/* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity is distributed in the hope that it will be useful,
|
||||||
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/* GNU General Public License for more details.
|
||||||
|
/*
|
||||||
|
/* You should have received a copy of the GNU General Public License
|
||||||
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$imageHeight: 56px;
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
min-height: $imageHeight;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.border {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
padding: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.identityIcon {
|
||||||
|
margin-right: 1em;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
&.locked {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
66
js/src/ui/VaultCard/Layout/layout.js
Normal file
66
js/src/ui/VaultCard/Layout/layout.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import Title from '~/ui/Title';
|
||||||
|
import IdentityIcon from '~/ui/IdentityIcon';
|
||||||
|
|
||||||
|
import styles from './layout.css';
|
||||||
|
|
||||||
|
export default class Layout extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
vault: PropTypes.object.isRequired,
|
||||||
|
withBorder: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { vault, withBorder } = this.props;
|
||||||
|
const { isOpen, meta, name } = vault;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
[
|
||||||
|
styles.layout,
|
||||||
|
withBorder
|
||||||
|
? styles.border
|
||||||
|
: null
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IdentityIcon
|
||||||
|
address={ name }
|
||||||
|
center
|
||||||
|
className={
|
||||||
|
[
|
||||||
|
styles.identityIcon,
|
||||||
|
isOpen || withBorder
|
||||||
|
? styles.unlocked
|
||||||
|
: styles.locked
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className={ styles.info }>
|
||||||
|
<Title
|
||||||
|
byline={ meta.description }
|
||||||
|
title={ name }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
js/src/ui/VaultCard/Layout/layout.spec.js
Normal file
90
js/src/ui/VaultCard/Layout/layout.spec.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Layout from './';
|
||||||
|
|
||||||
|
const DESCRIPTION = 'some description';
|
||||||
|
const NAME = 'testName';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<Layout
|
||||||
|
vault={ {
|
||||||
|
isOpen: true,
|
||||||
|
meta: {
|
||||||
|
description: DESCRIPTION,
|
||||||
|
passwordHint: 'some hint'
|
||||||
|
},
|
||||||
|
name: NAME
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/VaultCard/Layout', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('IdentityIcon', () => {
|
||||||
|
let icon;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
icon = component.find('Connect(IdentityIcon)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(icon.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the name as address key', () => {
|
||||||
|
expect(icon.props().address).to.equal(NAME);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Title', () => {
|
||||||
|
let title;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
title = component.find('Title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(title.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the name as title', () => {
|
||||||
|
expect(title.props().title).to.equal(NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the description as byline', () => {
|
||||||
|
expect(title.props().byline).to.equal(DESCRIPTION);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/ui/VaultCard/index.js
Normal file
17
js/src/ui/VaultCard/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultCard';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user