Merge branch 'master' into lightrpc
This commit is contained in:
commit
d8b1cfe082
@ -22,18 +22,20 @@ linux-stable:
|
||||
- triggers
|
||||
script:
|
||||
- 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/evmbin
|
||||
- strip target/release/evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/evmbin deb/usr/bin/evmbin
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- cp target/release/parity/evm deb/usr/bin/evm
|
||||
- cp target/release/parity/ethstore deb/usr/bin/ethstore
|
||||
- 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")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
@ -53,6 +55,9 @@ linux-stable:
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
- target/release/parity/evmbin
|
||||
- target/release/parity/ethstore
|
||||
- target/release/parity/ethkey
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
@ -485,9 +490,10 @@ docker-build:
|
||||
- docker info
|
||||
script:
|
||||
- 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 build --tag ethcore/parity:$CI_BUILD_REF_NAME .
|
||||
- docker push ethcore/parity:$CI_BUILD_REF_NAME
|
||||
- docker build --no-cache=true --tag ethcore/parity:$DOCKER_TAG .
|
||||
- docker push ethcore/parity:$DOCKER_TAG
|
||||
tags:
|
||||
- docker
|
||||
test-darwin:
|
||||
|
108
Cargo.lock
generated
108
Cargo.lock
generated
@ -20,6 +20,7 @@ dependencies = [
|
||||
"ethcore-light 1.6.0",
|
||||
"ethcore-logger 1.6.0",
|
||||
"ethcore-rpc 1.6.0",
|
||||
"ethcore-secretstore 1.0.0",
|
||||
"ethcore-signer 1.6.0",
|
||||
"ethcore-stratum 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)",
|
||||
"number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-hash-fetch 1.6.0",
|
||||
"parity-ipfs-api 1.6.0",
|
||||
"parity-local-store 0.1.0",
|
||||
"parity-reactor 0.1.0",
|
||||
"parity-rpc-client 1.4.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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "base32"
|
||||
version = "0.3.1"
|
||||
@ -189,6 +197,16 @@ name = "cfg-if"
|
||||
version = "0.1.0"
|
||||
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]]
|
||||
name = "clippy"
|
||||
version = "0.0.103"
|
||||
@ -622,6 +640,23 @@ dependencies = [
|
||||
"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]]
|
||||
name = "ethcore-signer"
|
||||
version = "1.6.0"
|
||||
@ -1008,6 +1043,11 @@ dependencies = [
|
||||
"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]]
|
||||
name = "ipc-common-types"
|
||||
version = "1.6.0"
|
||||
@ -1319,6 +1359,23 @@ dependencies = [
|
||||
"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]]
|
||||
name = "nanomsg"
|
||||
version = "0.5.1"
|
||||
@ -1575,6 +1632,34 @@ dependencies = [
|
||||
"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]]
|
||||
name = "parity-reactor"
|
||||
version = "0.1.0"
|
||||
@ -1620,7 +1705,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
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 = [
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "rlp"
|
||||
version = "0.1.0"
|
||||
@ -2346,6 +2440,11 @@ name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
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]]
|
||||
name = "url"
|
||||
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 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 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 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"
|
||||
@ -2484,6 +2584,7 @@ dependencies = [
|
||||
"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 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_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"
|
||||
@ -2520,6 +2621,7 @@ dependencies = [
|
||||
"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 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 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"
|
||||
@ -2551,6 +2653,8 @@ dependencies = [
|
||||
"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 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-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"
|
||||
@ -2601,6 +2705,7 @@ dependencies = [
|
||||
"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 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-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<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-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 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 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"
|
||||
|
@ -44,10 +44,13 @@ rlp = { path = "util/rlp" }
|
||||
rpc-cli = { path = "rpc_cli" }
|
||||
parity-rpc-client = { path = "rpc_client" }
|
||||
parity-hash-fetch = { path = "hash-fetch" }
|
||||
parity-ipfs-api = { path = "ipfs" }
|
||||
parity-updater = { path = "updater" }
|
||||
parity-reactor = { path = "util/reactor" }
|
||||
parity-local-store = { path = "local-store" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
ethcore-secretstore = { path = "secret_store", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||
@ -82,6 +85,7 @@ evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
final = ["ethcore-util/final"]
|
||||
secretstore = ["ethcore-secretstore"]
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
|
@ -1,4 +1,5 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Parity Technologies <devops@parity.io>
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
@ -25,46 +26,54 @@ RUN apt-get update && \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
libudev-dev
|
||||
|
||||
# cmake and llvm ppas. then update ppas
|
||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
libudev-dev &&\
|
||||
# cmake and llvm ppa's. then update ppa's
|
||||
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" && \
|
||||
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
|
||||
RUN git clone https://github.com/debris/evmjit && \
|
||||
git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
cmake .. && make && make install && cd && \
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
PATH=/root/.cargo/bin:$PATH && \
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
RUST_BACKTRACE=1 && \
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd /build&&git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git pull && \
|
||||
cargo build --release --features final && \
|
||||
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
|
||||
git pull&& \
|
||||
git checkout $CI_BUILD_REF_NAME && \
|
||||
cargo build --verbose --release --features final && \
|
||||
#ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity && \
|
||||
file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\
|
||||
#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
|
||||
ENTRYPOINT ["/parity"]
|
||||
ENTRYPOINT ["/parity/parity"]
|
||||
|
@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
|
||||
use std::fmt;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::{Instant, Duration};
|
||||
use util::RwLock;
|
||||
use util::{FixedHash, RwLock};
|
||||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
||||
random_string, SecretVaultRef, StoreAccountRef};
|
||||
use ethstore::dir::MemoryDirectory;
|
||||
@ -241,25 +241,88 @@ impl AccountProvider {
|
||||
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.
|
||||
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 {
|
||||
None => NewDappsPolicy::AllAccounts,
|
||||
None => NewDappsPolicy::AllAccounts {
|
||||
default: current_default,
|
||||
},
|
||||
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
||||
});
|
||||
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.
|
||||
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() {
|
||||
NewDappsPolicy::AllAccounts => None,
|
||||
NewDappsPolicy::AllAccounts { .. } => None,
|
||||
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.
|
||||
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
|
||||
Ok(self.dapps_settings.read().recent_dapps())
|
||||
@ -272,41 +335,74 @@ impl AccountProvider {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets addresses visile for dapp.
|
||||
pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
||||
let dapps = self.dapps_settings.read();
|
||||
/// Gets addresses visible for given dapp.
|
||||
pub fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
||||
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 {
|
||||
Some(accounts) => Ok(accounts),
|
||||
None => match dapps.policy() {
|
||||
NewDappsPolicy::AllAccounts => self.accounts(),
|
||||
NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts),
|
||||
}
|
||||
Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)),
|
||||
Some((Some(accounts), None)) => self.filter_addresses(accounts),
|
||||
Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)),
|
||||
_ => self.new_dapps_addresses_list(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns default account for particular dapp falling back to other allowed accounts if necessary.
|
||||
pub fn default_address(&self, dapp: DappId) -> Result<Address, Error> {
|
||||
self.dapps_addresses(dapp)?
|
||||
pub fn dapp_default_address(&self, dapp: DappId) -> Result<Address, Error> {
|
||||
let dapp_default = self.dapp_addresses(dapp)?
|
||||
.get(0)
|
||||
.cloned()
|
||||
.ok_or(SSError::InvalidAccount)
|
||||
.cloned();
|
||||
|
||||
match dapp_default {
|
||||
Some(default) => Ok(default),
|
||||
None => self.new_dapps_default_address(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets addresses visile for dapp.
|
||||
pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> {
|
||||
let addresses = self.filter_addresses(addresses)?;
|
||||
self.dapps_settings.write().set_accounts(dapp, addresses);
|
||||
/// Sets default address for given dapp.
|
||||
/// Does not alter dapp addresses, but this account will always be returned as the first one.
|
||||
pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> {
|
||||
if !self.valid_addresses()?.contains(&address) {
|
||||
return Err(SSError::InvalidAccount.into());
|
||||
}
|
||||
|
||||
self.dapps_settings.write().set_default(dapp, address);
|
||||
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.
|
||||
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
|
||||
let valid = self.addresses_info().into_iter()
|
||||
.map(|(address, _)| address)
|
||||
.chain(self.accounts()?)
|
||||
.collect::<HashSet<_>>();
|
||||
let valid = self.valid_addresses()?;
|
||||
|
||||
Ok(addresses.into_iter()
|
||||
.filter(|a| valid.contains(&a))
|
||||
@ -743,44 +839,92 @@ mod tests {
|
||||
}
|
||||
|
||||
#[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
|
||||
let ap = AccountProvider::transient_provider();
|
||||
let app = DappId("app1".into());
|
||||
// set `AllAccounts` policy
|
||||
ap.set_new_dapps_whitelist(None).unwrap();
|
||||
ap.set_new_dapps_addresses(None).unwrap();
|
||||
// add accounts to address book
|
||||
ap.set_address_name(1.into(), "1".into());
|
||||
ap.set_address_name(2.into(), "2".into());
|
||||
|
||||
// when
|
||||
ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap();
|
||||
ap.set_dapp_addresses(app.clone(), Some(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
|
||||
assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
||||
// when setting empty list
|
||||
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]
|
||||
fn should_set_dapps_policy() {
|
||||
fn should_set_dapps_policy_and_default_account() {
|
||||
// given
|
||||
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();
|
||||
ap.set_address_name(1.into(), "1".into());
|
||||
|
||||
// When returning nothing
|
||||
ap.set_new_dapps_whitelist(Some(vec![])).unwrap();
|
||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
||||
// Default account set to first account by default
|
||||
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
|
||||
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
|
||||
ap.set_new_dapps_whitelist(None).unwrap();
|
||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]);
|
||||
ap.set_new_dapps_addresses(None).unwrap();
|
||||
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
||||
|
||||
// change to non-existent account
|
||||
ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap();
|
||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]);
|
||||
ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap();
|
||||
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
||||
|
||||
// change to a whitelist
|
||||
ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap();
|
||||
assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]);
|
||||
// change to a addresses
|
||||
ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap();
|
||||
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)]
|
||||
pub struct DappsSettings {
|
||||
/// 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 {
|
||||
fn from(s: JsonSettings) -> Self {
|
||||
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 {
|
||||
fn from(s: DappsSettings) -> Self {
|
||||
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
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum NewDappsPolicy {
|
||||
AllAccounts,
|
||||
AllAccounts {
|
||||
default: Address,
|
||||
},
|
||||
Whitelist(Vec<Address>),
|
||||
}
|
||||
|
||||
impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
||||
fn from(s: JsonNewDappsPolicy) -> Self {
|
||||
match s {
|
||||
JsonNewDappsPolicy::AllAccounts => NewDappsPolicy::AllAccounts,
|
||||
JsonNewDappsPolicy::AllAccounts { default } => NewDappsPolicy::AllAccounts {
|
||||
default: default.into(),
|
||||
},
|
||||
JsonNewDappsPolicy::Whitelist(accounts) => NewDappsPolicy::Whitelist(
|
||||
accounts.into_iter().map(Into::into).collect()
|
||||
),
|
||||
@ -132,7 +140,9 @@ impl From<JsonNewDappsPolicy> for NewDappsPolicy {
|
||||
impl From<NewDappsPolicy> for JsonNewDappsPolicy {
|
||||
fn from(s: NewDappsPolicy) -> Self {
|
||||
match s {
|
||||
NewDappsPolicy::AllAccounts => JsonNewDappsPolicy::AllAccounts,
|
||||
NewDappsPolicy::AllAccounts { default } => JsonNewDappsPolicy::AllAccounts {
|
||||
default: default.into(),
|
||||
},
|
||||
NewDappsPolicy::Whitelist(accounts) => JsonNewDappsPolicy::Whitelist(
|
||||
accounts.into_iter().map(Into::into).collect()
|
||||
),
|
||||
@ -230,7 +240,9 @@ impl DappsSettingsStore {
|
||||
|
||||
/// Returns current new dapps policy
|
||||
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
|
||||
@ -266,13 +278,22 @@ impl DappsSettingsStore {
|
||||
}
|
||||
|
||||
/// 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);
|
||||
settings.accounts = accounts;
|
||||
}
|
||||
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
|
||||
@ -385,13 +406,14 @@ mod tests {
|
||||
let mut b = DappsSettingsStore::new(&path);
|
||||
|
||||
// when
|
||||
b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]);
|
||||
b.set_accounts("dappOne".into(), Some(vec![1.into(), 2.into()]));
|
||||
|
||||
// then
|
||||
let b = DappsSettingsStore::new(&path);
|
||||
assert_eq!(b.settings(), hash_map![
|
||||
"dappOne".into() => DappsSettings {
|
||||
accounts: vec![1.into(), 2.into()],
|
||||
accounts: Some(vec![1.into(), 2.into()]),
|
||||
default: None,
|
||||
}
|
||||
]);
|
||||
}
|
||||
@ -422,7 +444,9 @@ mod tests {
|
||||
let mut store = DappsSettingsStore::new(&path);
|
||||
|
||||
// Test default policy
|
||||
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);
|
||||
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts {
|
||||
default: 0.into(),
|
||||
});
|
||||
|
||||
// when
|
||||
store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]));
|
||||
|
@ -191,7 +191,7 @@ pub struct BlockChain {
|
||||
blocks_blooms: RwLock<HashMap<LogGroupPosition, BloomGroup>>,
|
||||
block_receipts: RwLock<HashMap<H256, BlockReceipts>>,
|
||||
|
||||
db: Arc<Database>,
|
||||
db: Arc<KeyValueDB>,
|
||||
|
||||
cache_man: Mutex<CacheManager<CacheId>>,
|
||||
|
||||
@ -421,7 +421,7 @@ impl<'a> Iterator for AncestryIter<'a> {
|
||||
|
||||
impl BlockChain {
|
||||
/// 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
|
||||
let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400);
|
||||
|
||||
@ -467,7 +467,7 @@ impl BlockChain {
|
||||
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_BODIES, &hash, &Self::block_to_body(genesis));
|
||||
|
||||
@ -1314,7 +1314,7 @@ impl BlockChain {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn db(&self) -> &Arc<Database> {
|
||||
pub fn db(&self) -> &Arc<KeyValueDB> {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
@ -1324,13 +1324,12 @@ mod tests {
|
||||
#![cfg_attr(feature="dev", allow(similar_names))]
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use util::{Database, DatabaseConfig};
|
||||
use util::kvdb::KeyValueDB;
|
||||
use util::hash::*;
|
||||
use util::sha3::Hashable;
|
||||
use receipt::Receipt;
|
||||
use blockchain::{BlockProvider, BlockChain, Config, ImportRoute};
|
||||
use tests::helpers::*;
|
||||
use devtools::*;
|
||||
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
||||
use blockchain::extras::TransactionAddress;
|
||||
use views::BlockView;
|
||||
@ -1339,11 +1338,11 @@ mod tests {
|
||||
use ethkey::Secret;
|
||||
use header::BlockNumber;
|
||||
|
||||
fn new_db(path: &str) -> Arc<Database> {
|
||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
||||
fn new_db() -> Arc<KeyValueDB> {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -1355,13 +1354,12 @@ mod tests {
|
||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||
let first = canon_chain.generate(&mut finalizer).unwrap();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
assert_eq!(bc.best_block_number(), 0);
|
||||
|
||||
// when
|
||||
let mut batch =db.transaction();
|
||||
let mut batch = db.transaction();
|
||||
bc.insert_block(&mut batch, &first, vec![]);
|
||||
assert_eq!(bc.best_block_number(), 0);
|
||||
bc.commit();
|
||||
@ -1381,8 +1379,7 @@ mod tests {
|
||||
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
||||
let first_hash = BlockView::new(&first).header_view().sha3();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.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_details(&genesis_hash).unwrap().children, vec![]);
|
||||
|
||||
let mut batch =db.transaction();
|
||||
let mut batch = db.transaction();
|
||||
bc.insert_block(&mut batch, &first, vec![]);
|
||||
db.write(batch).unwrap();
|
||||
bc.commit();
|
||||
@ -1412,8 +1409,7 @@ mod tests {
|
||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.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 b5a = canon_chain.generate(&mut finalizer).unwrap();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let mut batch =db.transaction();
|
||||
@ -1514,8 +1509,7 @@ mod tests {
|
||||
|
||||
let t1_hash = t1.hash();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let mut batch = db.transaction();
|
||||
@ -1602,8 +1596,7 @@ mod tests {
|
||||
let t2_hash = t2.hash();
|
||||
let t3_hash = t3.hash();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let mut batch = db.transaction();
|
||||
@ -1664,8 +1657,7 @@ mod tests {
|
||||
// b3a is a part of canon chain, whereas b3b is part of sidechain
|
||||
let best_block_hash = b3a_hash.clone();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let mut batch = db.transaction();
|
||||
@ -1778,10 +1770,9 @@ mod tests {
|
||||
let first = canon_chain.generate(&mut finalizer).unwrap();
|
||||
let genesis_hash = BlockView::new(&genesis).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());
|
||||
assert_eq!(bc.best_block_hash(), genesis_hash);
|
||||
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());
|
||||
|
||||
assert_eq!(bc.best_block_hash(), first_hash);
|
||||
@ -1846,8 +1836,7 @@ mod tests {
|
||||
let b1 = "f904a8f901faa0ce1f26f798dd03c8782d63b3e42e79a64eaea5694ea686ac5d7ce3df5171d1aea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0a65c2364cd0f1542d761823dc0109c6b072f14c20459598c5455c274601438f4a070616ebd7ad2ed6fb7860cf7e9df00163842351c38a87cac2c1cb193895035a2a05c5b4fc43c2d45787f54e1ae7d27afdb4ad16dfc567c5692070d5c4556e0b1d7b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000183023ec683021536845685109780a029f07836e4e59229b3a065913afc27702642c683bba689910b2b2fd45db310d3888957e6d004a31802f902a7f85f800a8255f094aaaf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca0575da4e21b66fa764be5f74da9389e67693d066fb0d1312e19e17e501da00ecda06baf5a5327595f6619dfc2fcb3f2e6fb410b5810af3cb52d0e7508038e91a188f85f010a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba04fa966bf34b93abc1bcd665554b7f316b50f928477b50be0f3285ead29d18c5ba017bba0eeec1625ab433746955e125d46d80b7fdc97386c51266f842d8e02192ef85f020a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca004377418ae981cc32b1312b4a427a1d69a821b28db8584f5f2bd8c6d42458adaa053a1dba1af177fac92f3b6af0a9fa46a22adf56e686c93794b6a012bf254abf5f85f030a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ca04fe13febd28a05f4fcb2f451d7ddc2dda56486d9f8c79a62b0ba4da775122615a0651b2382dd402df9ebc27f8cb4b2e0f3cea68dda2dca0ee9603608f0b6f51668f85f040a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba078e6a0ba086a08f8450e208a399bb2f2d2a0d984acd2517c7c7df66ccfab567da013254002cd45a97fac049ae00afbc43ed0d9961d0c56a3b2382c80ce41c198ddf85f050a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba0a7174d8f43ea71c8e3ca9477691add8d80ac8e0ed89d8d8b572041eef81f4a54a0534ea2e28ec4da3b5b944b18c51ec84a5cf35f5b3343c5fb86521fd2d388f506f85f060a82520894bbbf5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba034bd04065833536a10c77ee2a43a5371bc6d34837088b861dd9d4b7f44074b59a078807715786a13876d3455716a6b9cb2186b7a4887a5c31160fc877454958616c0".from_hex().unwrap();
|
||||
let b1_hash: H256 = "f53f268d23a71e85c7d6d83a9504298712b84c1a2ba220441c86eeda0bf0b6e3".into();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
let mut batch =db.transaction();
|
||||
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 res = bc.insert_block(&mut batch, bytes, receipts);
|
||||
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 b2 = canon_chain.with_transaction(t3).generate(&mut finalizer).unwrap();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
insert_block(&db, &bc, &b1, vec![Receipt {
|
||||
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 b2a = canon_chain.with_bloom(bloom_ba.clone()).generate(&mut finalizer).unwrap();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5);
|
||||
@ -2070,14 +2057,12 @@ mod tests {
|
||||
let mut finalizer = BlockFinalizer::default();
|
||||
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 uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
||||
|
||||
let mut batch =db.transaction();
|
||||
let mut batch = db.transaction();
|
||||
// create a longer fork
|
||||
for _ in 0..5 {
|
||||
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.
|
||||
let db = new_db(temp.as_str());
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
let bc = new_chain(&genesis, db);
|
||||
assert_eq!(bc.best_block_number(), 5);
|
||||
}
|
||||
|
||||
@ -2108,8 +2092,7 @@ mod tests {
|
||||
let first_hash = BlockView::new(&first).header_view().sha3();
|
||||
let second_hash = BlockView::new(&second).header_view().sha3();
|
||||
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let bc = new_chain(&genesis, db.clone());
|
||||
|
||||
let mut batch =db.transaction();
|
||||
|
@ -17,7 +17,6 @@
|
||||
use std::collections::{HashSet, HashMap, BTreeMap, VecDeque};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::path::{Path};
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::time::{Instant};
|
||||
@ -135,7 +134,7 @@ pub struct Client {
|
||||
engine: Arc<Engine>,
|
||||
config: ClientConfig,
|
||||
pruning: journaldb::Algorithm,
|
||||
db: RwLock<Arc<Database>>,
|
||||
db: RwLock<Arc<KeyValueDB>>,
|
||||
state_db: Mutex<StateDB>,
|
||||
block_queue: BlockQueue,
|
||||
report: RwLock<ClientReport>,
|
||||
@ -157,18 +156,16 @@ pub struct 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(
|
||||
config: ClientConfig,
|
||||
spec: &Spec,
|
||||
path: &Path,
|
||||
db: Arc<KeyValueDB>,
|
||||
miner: Arc<Miner>,
|
||||
message_channel: IoChannel<ClientIoMessage>,
|
||||
db_config: &DatabaseConfig,
|
||||
) -> 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 {
|
||||
true => TrieSpec::Fat,
|
||||
false => TrieSpec::Secure,
|
||||
@ -186,7 +183,7 @@ impl Client {
|
||||
if state_db.journal_db().is_empty() {
|
||||
// Sets the correct state root.
|
||||
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())?;
|
||||
db.write(batch).map_err(ClientError::Database)?;
|
||||
}
|
||||
@ -530,7 +527,7 @@ impl Client {
|
||||
|
||||
// Commit results
|
||||
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);
|
||||
// Final commit to the DB
|
||||
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 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
|
||||
// already-imported block of the same number.
|
||||
// TODO: Prove it with a test.
|
||||
@ -603,7 +600,7 @@ impl Client {
|
||||
trace!(target: "client", "Pruning state for ancient era {}", era);
|
||||
match chain.block_hash(era) {
|
||||
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)?;
|
||||
self.db.read().write_buffered(batch);
|
||||
state_db.journal_db().flush();
|
||||
@ -1691,7 +1688,7 @@ mod tests {
|
||||
let go_thread = go.clone();
|
||||
let another_client = client.reference().clone();
|
||||
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());
|
||||
go_thread.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
@ -19,7 +19,7 @@
|
||||
use std::ops::Deref;
|
||||
use std::hash::Hash;
|
||||
use std::collections::HashMap;
|
||||
use util::{DBTransaction, Database, RwLock};
|
||||
use util::{DBTransaction, KeyValueDB, RwLock};
|
||||
|
||||
use rlp;
|
||||
|
||||
@ -34,10 +34,12 @@ pub const COL_BODIES: Option<u32> = Some(2);
|
||||
pub const COL_EXTRA: Option<u32> = Some(3);
|
||||
/// Column for Traces
|
||||
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);
|
||||
/// 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
|
||||
pub const NUM_COLUMNS: Option<u32> = Some(6);
|
||||
pub const NUM_COLUMNS: Option<u32> = Some(7);
|
||||
|
||||
/// Modes for updating caches.
|
||||
#[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]> {
|
||||
let result = self.get(col, &key.key());
|
||||
|
||||
|
@ -220,8 +220,8 @@ impl Engine for AuthorityRound {
|
||||
});
|
||||
}
|
||||
|
||||
fn is_sealer(&self, author: &Address) -> Option<bool> {
|
||||
Some(self.validators.contains(author))
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.validators.contains(&self.signer.address()))
|
||||
}
|
||||
|
||||
/// Attempt to seal the block internally.
|
||||
|
@ -103,8 +103,8 @@ impl Engine for BasicAuthority {
|
||||
});
|
||||
}
|
||||
|
||||
fn is_sealer(&self, author: &Address) -> Option<bool> {
|
||||
Some(self.validators.contains(author))
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.validators.contains(&self.signer.address()))
|
||||
}
|
||||
|
||||
/// Attempt to seal the block internally.
|
||||
@ -268,7 +268,8 @@ mod tests {
|
||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.is_sealer(&Address::default()).unwrap());
|
||||
assert!(engine.is_sealer(&authority).unwrap());
|
||||
assert!(!engine.seals_internally().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)
|
||||
}
|
||||
|
||||
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 {
|
||||
Seal::Regular(Vec::new())
|
||||
|
@ -128,11 +128,10 @@ pub trait Engine : Sync + Send {
|
||||
/// Block transformation functions, after the transactions.
|
||||
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 indicates that this Engine never seals internally regardless of author (e.g. PoW).
|
||||
fn is_sealer(&self, _author: &Address) -> Option<bool> { None }
|
||||
/// Checks if default address is able to seal.
|
||||
fn is_default_sealer(&self) -> Option<bool> { self.is_sealer(&Default::default()) }
|
||||
/// None means that it requires external input (e.g. PoW) to seal a block.
|
||||
/// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator).
|
||||
/// Some(false) means that the node might seal internally but is not qualified now.
|
||||
fn seals_internally(&self) -> Option<bool> { None }
|
||||
/// Attempt to seal the block internally.
|
||||
///
|
||||
/// If `Some` is returned, then you get a valid seal.
|
||||
|
@ -410,8 +410,8 @@ impl Engine for Tendermint {
|
||||
}
|
||||
|
||||
/// Should this node participate.
|
||||
fn is_sealer(&self, address: &Address) -> Option<bool> {
|
||||
Some(self.is_authority(address))
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.is_authority(&self.signer.address()))
|
||||
}
|
||||
|
||||
/// Attempt to seal generate a proposal seal.
|
||||
@ -649,8 +649,7 @@ mod tests {
|
||||
use account_provider::AccountProvider;
|
||||
use spec::Spec;
|
||||
use engines::{Engine, EngineError, Seal};
|
||||
use super::{Step, View, Height, message_info_rlp, message_full_rlp};
|
||||
use super::message::VoteStep;
|
||||
use super::*;
|
||||
|
||||
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
|
||||
fn setup() -> (Spec, Arc<AccountProvider>) {
|
||||
|
@ -19,7 +19,6 @@ use client::{BlockChainClient, Client, ClientConfig};
|
||||
use block::Block;
|
||||
use ethereum;
|
||||
use tests::helpers::*;
|
||||
use devtools::*;
|
||||
use spec::Genesis;
|
||||
use ethjson;
|
||||
use miner::Miner;
|
||||
@ -58,16 +57,14 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
temp.as_path(),
|
||||
db,
|
||||
Arc::new(Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config,
|
||||
).unwrap();
|
||||
for b in &blockchain.blocks_rlp() {
|
||||
if Block::is_good(&b) {
|
||||
|
@ -26,3 +26,6 @@ pub use self::v9::Extract;
|
||||
|
||||
mod v10;
|
||||
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());
|
||||
|
||||
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()))?;
|
||||
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>,
|
||||
// for sealing...
|
||||
options: MinerOptions,
|
||||
/// Does the node perform internal (without work) sealing.
|
||||
pub seals_internally: bool,
|
||||
|
||||
gas_range_target: RwLock<(U256, U256)>,
|
||||
author: RwLock<Address>,
|
||||
@ -275,9 +273,8 @@ impl Miner {
|
||||
queue: UsingQueue::new(options.work_queue_size),
|
||||
enabled: options.force_sealing
|
||||
|| !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())),
|
||||
author: RwLock::new(Address::default()),
|
||||
extra_data: RwLock::new(Vec::new()),
|
||||
@ -455,7 +452,7 @@ impl Miner {
|
||||
let last_request = *self.sealing_block_last_request.lock();
|
||||
let should_disable_sealing = !self.forced_sealing()
|
||||
&& !has_local_transactions
|
||||
&& !self.seals_internally
|
||||
&& self.engine.seals_internally().is_none()
|
||||
&& best_block > last_request
|
||||
&& best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS;
|
||||
|
||||
@ -765,21 +762,21 @@ impl MinerService for Miner {
|
||||
}
|
||||
|
||||
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();
|
||||
sealing_work.enabled = self.engine.is_sealer(&author).unwrap_or(false);
|
||||
sealing_work.enabled = true;
|
||||
}
|
||||
*self.author.write() = author;
|
||||
}
|
||||
|
||||
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 {
|
||||
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
|
||||
// Limit the scope of the locks.
|
||||
{
|
||||
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;
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@ -914,7 +911,7 @@ impl MinerService for Miner {
|
||||
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.
|
||||
// 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)
|
||||
// or Engine might be able to seal internally,
|
||||
// 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();
|
||||
match self.options.pending_set {
|
||||
PendingSet::AlwaysQueue => queue.find(hash),
|
||||
@ -992,14 +989,14 @@ impl MinerService for Miner {
|
||||
self.from_pending_block(
|
||||
best_block,
|
||||
|| 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 => {
|
||||
self.from_pending_block(
|
||||
best_block,
|
||||
|| 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");
|
||||
let (block, original_work_hash) = self.prepare_block(chain);
|
||||
if self.seals_internally {
|
||||
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
||||
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);
|
||||
match self.engine.seals_internally() {
|
||||
Some(true) => {
|
||||
trace!(target: "miner", "update_sealing: engine indicates internal sealing");
|
||||
if self.seal_and_import_block_internally(chain, block) {
|
||||
trace!(target: "miner", "update_sealing: imported internally sealed block");
|
||||
}
|
||||
},
|
||||
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;
|
||||
|
||||
/// 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.
|
||||
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
||||
|
@ -1109,7 +1109,7 @@ impl TransactionQueue {
|
||||
r
|
||||
}
|
||||
|
||||
/// Return all ready transactions.
|
||||
/// Return all future transactions.
|
||||
pub fn future_transactions(&self) -> Vec<PendingTransaction> {
|
||||
self.future.by_priority
|
||||
.iter()
|
||||
@ -1137,8 +1137,8 @@ impl TransactionQueue {
|
||||
}
|
||||
|
||||
/// Finds transaction in the queue by hash (if any)
|
||||
pub fn find(&self, hash: &H256) -> Option<SignedTransaction> {
|
||||
self.by_hash.get(hash).map(|tx| tx.transaction.clone())
|
||||
pub fn find(&self, hash: &H256) -> Option<PendingTransaction> {
|
||||
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
|
||||
|
@ -57,6 +57,7 @@ pub struct ClientService {
|
||||
client: Arc<Client>,
|
||||
snapshot: Arc<SnapshotService>,
|
||||
panic_handler: Arc<PanicHandler>,
|
||||
database: Arc<Database>,
|
||||
_stop_guard: ::devtools::StopGuard,
|
||||
}
|
||||
|
||||
@ -88,8 +89,14 @@ impl ClientService {
|
||||
db_config.compaction = config.db_compaction.compaction_profile(client_path);
|
||||
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 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 {
|
||||
engine: spec.engine.clone(),
|
||||
@ -119,15 +126,11 @@ impl ClientService {
|
||||
client: client,
|
||||
snapshot: snapshot,
|
||||
panic_handler: panic_handler,
|
||||
database: db,
|
||||
_stop_guard: stop_guard,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a node to network
|
||||
pub fn add_node(&mut self, _enode: &str) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
/// Get general IO interface
|
||||
pub fn register_io_handler(&self, handler: Arc<IoHandler<ClientIoMessage> + Send>) -> Result<(), IoError> {
|
||||
self.io_service.register_handler(handler)
|
||||
@ -152,6 +155,9 @@ impl ClientService {
|
||||
pub fn add_notify(&self, notify: Arc<ChainNotify>) {
|
||||
self.client.add_notify(notify);
|
||||
}
|
||||
|
||||
/// Get a handle to the database.
|
||||
pub fn db(&self) -> Arc<KeyValueDB> { self.database.clone() }
|
||||
}
|
||||
|
||||
impl MayPanic for ClientService {
|
||||
|
@ -27,7 +27,7 @@ use tests::helpers::generate_dummy_client_with_spec_and_data;
|
||||
|
||||
use devtools::RandomTempPath;
|
||||
use io::IoChannel;
|
||||
use util::kvdb::DatabaseConfig;
|
||||
use util::kvdb::{Database, DatabaseConfig};
|
||||
|
||||
struct NoopDBRestore;
|
||||
|
||||
@ -54,15 +54,15 @@ fn restored_is_equivalent() {
|
||||
path.push("snapshot");
|
||||
|
||||
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 client2 = Client::new(
|
||||
Default::default(),
|
||||
&spec,
|
||||
&client_db,
|
||||
Arc::new(client_db),
|
||||
Arc::new(::miner::Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config,
|
||||
).unwrap();
|
||||
|
||||
let service_params = ServiceParams {
|
||||
|
@ -18,11 +18,12 @@ use std::collections::{VecDeque, HashSet};
|
||||
use lru_cache::LruCache;
|
||||
use util::cache::MemoryLruCache;
|
||||
use util::journaldb::JournalDB;
|
||||
use util::kvdb::KeyValueDB;
|
||||
use util::hash::{H256};
|
||||
use util::hashdb::HashDB;
|
||||
use state::Account;
|
||||
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 db::COL_ACCOUNT_BLOOM;
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
@ -116,7 +117,7 @@ impl StateDB {
|
||||
// TODO: make the cache size actually accurate by moving the account storage cache
|
||||
// into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`.
|
||||
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 code_cache_size = cache_size - acc_cache_size;
|
||||
let cache_items = acc_cache_size / ::std::mem::size_of::<Option<Account>>();
|
||||
@ -139,7 +140,7 @@ impl StateDB {
|
||||
|
||||
/// Loads accounts bloom from the database
|
||||
/// 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)
|
||||
.expect("Low-level database error");
|
||||
|
||||
@ -477,7 +478,7 @@ mod tests {
|
||||
let h2b = H256::random();
|
||||
let h3a = 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 ]
|
||||
// balance [ 5 5 4 3 2 2 ]
|
||||
|
@ -36,14 +36,14 @@ fn imports_from_empty() {
|
||||
let dir = RandomTempPath::new();
|
||||
let spec = get_test_spec();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
client.import_verified_blocks();
|
||||
client.flush_queue();
|
||||
@ -54,14 +54,14 @@ fn should_return_registrar() {
|
||||
let dir = RandomTempPath::new();
|
||||
let spec = ethereum::new_morden();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
let params = client.additional_params();
|
||||
let address = ¶ms["registrar"];
|
||||
@ -85,14 +85,14 @@ fn imports_good_block() {
|
||||
let dir = RandomTempPath::new();
|
||||
let spec = get_test_spec();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
let good_block = get_good_dummy_block();
|
||||
if client.import_block(good_block).is_err() {
|
||||
@ -110,14 +110,14 @@ fn query_none_block() {
|
||||
let dir = RandomTempPath::new();
|
||||
let spec = get_test_spec();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec(&spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
let non_existant = client.block_header(BlockId::Number(188));
|
||||
assert!(non_existant.is_none());
|
||||
@ -276,10 +276,19 @@ fn change_history_size() {
|
||||
let test_spec = Spec::new_null();
|
||||
let mut config = ClientConfig::default();
|
||||
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;
|
||||
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 {
|
||||
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);
|
||||
@ -290,7 +299,13 @@ fn change_history_size() {
|
||||
}
|
||||
let mut config = ClientConfig::default();
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -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 test_spec = get_test_spec();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&test_spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
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 test_spec = get_test_spec();
|
||||
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(
|
||||
ClientConfig::default(),
|
||||
&test_spec,
|
||||
dir.as_path(),
|
||||
client_db,
|
||||
Arc::new(Miner::with_spec(&test_spec)),
|
||||
IoChannel::disconnected(),
|
||||
&db_config
|
||||
).unwrap();
|
||||
|
||||
for block in &blocks {
|
||||
|
@ -20,7 +20,7 @@ use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
use bloomchain::{Number, Config as BloomConfig};
|
||||
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 trace::{LocalizedTrace, Config, Filter, Database as TraceDatabase, ImportRequest, DatabaseExtras};
|
||||
use db::{self, Key, Writable, Readable, CacheUpdatePolicy};
|
||||
@ -106,7 +106,7 @@ pub struct TraceDB<T> where T: DatabaseExtras {
|
||||
blooms: RwLock<HashMap<TraceGroupPosition, blooms::BloomGroup>>,
|
||||
cache_manager: RwLock<CacheManager<CacheId>>,
|
||||
// db
|
||||
tracesdb: Arc<Database>,
|
||||
tracesdb: Arc<KeyValueDB>,
|
||||
// config,
|
||||
bloom_config: BloomConfig,
|
||||
// tracing enabled
|
||||
@ -126,8 +126,8 @@ impl<T> BloomGroupDatabase for TraceDB<T> where T: DatabaseExtras {
|
||||
|
||||
impl<T> TraceDB<T> where T: DatabaseExtras {
|
||||
/// Creates new instance of `TraceDB`.
|
||||
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
|
||||
let mut batch = DBTransaction::new(&tracesdb);
|
||||
pub fn new(config: Config, tracesdb: Arc<KeyValueDB>, extras: Arc<T>) -> Self {
|
||||
let mut batch = DBTransaction::new();
|
||||
let genesis = extras.block_hash(0)
|
||||
.expect("Genesis block is always inserted upon extras db creation qed");
|
||||
batch.write(db::COL_TRACE, &genesis, &FlatBlockTraces::default());
|
||||
@ -404,8 +404,7 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use util::{Address, U256, H256, Database, DatabaseConfig, DBTransaction};
|
||||
use devtools::RandomTempPath;
|
||||
use util::{Address, U256, H256, DBTransaction};
|
||||
use header::BlockNumber;
|
||||
use trace::{Config, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest};
|
||||
use trace::{Filter, LocalizedTrace, AddressesFilter, TraceError};
|
||||
@ -455,14 +454,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_db(path: &str) -> Arc<Database> {
|
||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
||||
fn new_db() -> Arc<::util::kvdb::KeyValueDB> {
|
||||
Arc::new(::util::kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reopening_db_with_tracing_off() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
|
||||
// set autotracing
|
||||
@ -476,8 +474,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_reopening_db_with_tracing_on() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
|
||||
// set tracing on
|
||||
@ -555,8 +552,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_import_non_canon_traces() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
config.enabled = true;
|
||||
let block_0 = H256::from(0xa1);
|
||||
@ -574,7 +570,7 @@ mod tests {
|
||||
|
||||
// import block 0
|
||||
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);
|
||||
db.write(batch).unwrap();
|
||||
|
||||
@ -584,8 +580,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_import() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
config.enabled = true;
|
||||
let block_1 = H256::from(0xa1);
|
||||
@ -605,7 +600,7 @@ mod tests {
|
||||
|
||||
// import block 1
|
||||
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);
|
||||
db.write(batch).unwrap();
|
||||
|
||||
@ -621,7 +616,7 @@ mod tests {
|
||||
|
||||
// import block 2
|
||||
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);
|
||||
db.write(batch).unwrap();
|
||||
|
||||
@ -664,8 +659,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn query_trace_after_reopen() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
let mut extras = Extras::default();
|
||||
let block_0 = H256::from(0xa1);
|
||||
@ -684,7 +678,7 @@ mod tests {
|
||||
|
||||
// import block 1
|
||||
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);
|
||||
db.write(batch).unwrap();
|
||||
}
|
||||
@ -698,8 +692,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn query_genesis() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = new_db(temp.as_str());
|
||||
let db = new_db();
|
||||
let mut config = Config::default();
|
||||
let mut extras = Extras::default();
|
||||
let block_0 = H256::from(0xa1);
|
||||
|
@ -16,23 +16,33 @@ Ethereum key management.
|
||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
||||
|
||||
Usage:
|
||||
ethstore insert <secret> <password> [--dir DIR]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
||||
ethstore list [--dir DIR]
|
||||
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore import [--src DIR] [--dir DIR]
|
||||
ethstore import-wallet <path> <password> [--dir DIR]
|
||||
ethstore remove <address> <password> [--dir DIR]
|
||||
ethstore sign <address> <password> <message> [--dir DIR]
|
||||
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
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]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-test, get, geth-test
|
||||
or a path [default: geth].
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--vault VAULT Specify vault to use in this operation.
|
||||
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||
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:
|
||||
insert Save account with password.
|
||||
@ -42,16 +52,24 @@ Commands:
|
||||
import-wallet Import presale wallet.
|
||||
remove Remove account.
|
||||
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
|
||||
|
||||
#### `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.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 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 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.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<old-pwd>` - old 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
|
||||
- `[--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
|
||||
@ -91,10 +111,12 @@ true
|
||||
|
||||
--
|
||||
|
||||
#### `list [--dir DIR]`
|
||||
#### `list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]`
|
||||
*List secret store accounts.*
|
||||
|
||||
- `[--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
|
||||
@ -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.*
|
||||
|
||||
- `<path>` - presale wallet 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
|
||||
- `[--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
|
||||
@ -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.*
|
||||
|
||||
- `<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 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.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<password>` - account password, file path
|
||||
- `<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
|
||||
- `[--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
|
||||
@ -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
|
||||
*this project is a part of the ethcore toolchain*
|
||||
|
||||
|
@ -31,24 +31,33 @@ Ethereum key management.
|
||||
Copyright 2016, 2017 Parity Technologies (UK) Ltd
|
||||
|
||||
Usage:
|
||||
ethstore insert <secret> <password> [--dir DIR]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
|
||||
ethstore list [--dir DIR]
|
||||
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore import [--src DIR] [--dir DIR]
|
||||
ethstore import-wallet <path> <password> [--dir DIR]
|
||||
ethstore remove <address> <password> [--dir DIR]
|
||||
ethstore sign <address> <password> <message> [--dir DIR]
|
||||
ethstore public <address> <password>
|
||||
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
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]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-test, get, geth-test
|
||||
or a path [default: geth].
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
or a path [default: parity].
|
||||
--vault VAULT Specify vault to use in this operation.
|
||||
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||
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:
|
||||
insert Save account with password.
|
||||
@ -59,6 +68,11 @@ Commands:
|
||||
remove Remove account.
|
||||
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.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
@ -71,6 +85,11 @@ struct Args {
|
||||
cmd_remove: bool,
|
||||
cmd_sign: 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_password: String,
|
||||
arg_old_pwd: String,
|
||||
@ -78,8 +97,11 @@ struct Args {
|
||||
arg_address: String,
|
||||
arg_message: String,
|
||||
arg_path: String,
|
||||
arg_vault: String,
|
||||
flag_src: String,
|
||||
flag_dir: String,
|
||||
flag_vault: String,
|
||||
flag_vault_pwd: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -104,6 +126,23 @@ fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
|
||||
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 {
|
||||
accounts.iter()
|
||||
.enumerate()
|
||||
@ -112,10 +151,14 @@ fn format_accounts(accounts: &[Address]) -> String {
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn format_vaults(vaults: &[String]) -> String {
|
||||
vaults.join("\n")
|
||||
}
|
||||
|
||||
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();
|
||||
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
|
||||
let _ = password.pop();
|
||||
Ok(password)
|
||||
@ -131,17 +174,24 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
return if args.cmd_insert {
|
||||
let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?;
|
||||
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))
|
||||
} else if args.cmd_change_pwd {
|
||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||
let old_pwd = load_password(&args.arg_old_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))
|
||||
} else if args.cmd_list {
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
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))
|
||||
} else if args.cmd_import {
|
||||
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 password = load_password(&args.arg_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))
|
||||
} else if args.cmd_remove {
|
||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||
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))
|
||||
} else if args.cmd_sign {
|
||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||
let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?;
|
||||
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))
|
||||
} else if args.cmd_public {
|
||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||
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))
|
||||
} 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 {
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
|
@ -90,11 +90,8 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// all accounts found in keys directory
|
||||
fn files(&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 = fs::read_dir(&self.path)?
|
||||
fn files(&self) -> Result<Vec<PathBuf>, Error> {
|
||||
Ok(fs::read_dir(&self.path)?
|
||||
.flat_map(Result::ok)
|
||||
.filter(|entry| {
|
||||
let metadata = entry.metadata().ok();
|
||||
@ -102,14 +99,34 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
let name = file_name.to_string_lossy();
|
||||
// filter directories
|
||||
metadata.map_or(false, |m| !m.is_dir()) &&
|
||||
// hidden files
|
||||
!name.starts_with(".") &&
|
||||
// other ignored files
|
||||
!IGNORED_FILES.contains(&&*name)
|
||||
// hidden files
|
||||
!name.starts_with(".") &&
|
||||
// other ignored files
|
||||
!IGNORED_FILES.contains(&&*name)
|
||||
})
|
||||
.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
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
@ -166,7 +183,7 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
|
||||
|
||||
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
let accounts = self.files()?
|
||||
let accounts = self.files_content()?
|
||||
.into_iter()
|
||||
.map(|(_, account)| account)
|
||||
.collect();
|
||||
@ -191,7 +208,7 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
// enumerate all entries in keystore
|
||||
// and find entry with given address
|
||||
let to_remove = self.files()?
|
||||
let to_remove = self.files_content()?
|
||||
.into_iter()
|
||||
.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> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
self.files_hash()
|
||||
}
|
||||
}
|
||||
|
||||
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 res = directory.insert(account);
|
||||
|
||||
|
||||
// then
|
||||
assert!(res.is_ok(), "Should save account succesfuly.");
|
||||
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 == "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> {
|
||||
self.dir.remove(account)
|
||||
}
|
||||
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
self.dir.unique_repr()
|
||||
}
|
||||
}
|
||||
|
@ -63,5 +63,12 @@ impl KeyDirectory for MemoryDirectory {
|
||||
}
|
||||
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 }
|
||||
/// Return vault provider, if available
|
||||
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { None }
|
||||
/// Unique representation of directory account collection
|
||||
fn unique_repr(&self) -> Result<u64, Error>;
|
||||
}
|
||||
|
||||
/// Vaults provider
|
||||
|
@ -74,4 +74,8 @@ impl KeyDirectory for ParityDirectory {
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
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
|
||||
cache: RwLock<BTreeMap<StoreAccountRef, Vec<SafeAccount>>>,
|
||||
vaults: Mutex<HashMap<String, Box<VaultKeyDirectory>>>,
|
||||
dir_hash: Mutex<Option<u64>>,
|
||||
}
|
||||
|
||||
impl EthMultiStore {
|
||||
@ -244,11 +245,23 @@ impl EthMultiStore {
|
||||
vaults: Mutex::new(HashMap::new()),
|
||||
iterations: iterations,
|
||||
cache: Default::default(),
|
||||
dir_hash: Default::default(),
|
||||
};
|
||||
store.reload_accounts()?;
|
||||
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> {
|
||||
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 accounts = cache.get(account).ok_or(Error::InvalidAccount)?;
|
||||
if accounts.is_empty() {
|
||||
@ -431,7 +444,7 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
}
|
||||
|
||||
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
||||
self.reload_accounts()?;
|
||||
self.reload_if_changed()?;
|
||||
self.cache.read().keys()
|
||||
.find(|r| &r.address == address)
|
||||
.cloned()
|
||||
@ -439,7 +452,7 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
}
|
||||
|
||||
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
|
||||
self.reload_accounts()?;
|
||||
self.reload_if_changed()?;
|
||||
Ok(self.cache.read().keys().cloned().collect())
|
||||
}
|
||||
|
||||
|
@ -74,4 +74,8 @@ impl KeyDirectory for TransientDir {
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
self.dir.remove(account)
|
||||
}
|
||||
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
self.dir.unique_repr()
|
||||
}
|
||||
}
|
||||
|
@ -50,12 +50,31 @@ pub enum Error {
|
||||
/// Computed hash
|
||||
got: H256,
|
||||
},
|
||||
/// Server didn't respond with OK status.
|
||||
InvalidStatus,
|
||||
/// IO Error while validating hash.
|
||||
IO(io::Error),
|
||||
/// Error during fetch.
|
||||
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 {
|
||||
fn from(error: FetchError) -> Self {
|
||||
Error::Fetch(error)
|
||||
@ -115,6 +134,10 @@ impl<F: Fetch + 'static> HashFetch for Client<F> {
|
||||
let future = self.fetch.fetch(&url).then(move |result| {
|
||||
fn validate_hash(path: PathBuf, hash: H256, result: Result<Response, FetchError>) -> Result<PathBuf, Error> {
|
||||
let response = result?;
|
||||
if !response.is_success() {
|
||||
return Err(Error::InvalidStatus);
|
||||
}
|
||||
|
||||
// Read the response
|
||||
let mut reader = io::BufReader::new(response);
|
||||
let mut writer = io::BufWriter::new(fs::File::create(&path)?);
|
||||
@ -160,3 +183,119 @@ fn random_temp_path() -> PathBuf {
|
||||
path.push(file);
|
||||
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)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
@ -273,16 +273,16 @@ mod tests {
|
||||
use super::guess_mime_type;
|
||||
use util::{Bytes, Address, Mutex, ToPretty};
|
||||
|
||||
struct FakeRegistrar {
|
||||
pub struct FakeRegistrar {
|
||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
||||
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
||||
}
|
||||
|
||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||
pub const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||
pub const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||
|
||||
impl FakeRegistrar {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
FakeRegistrar {
|
||||
calls: Arc::new(Mutex::new(Vec::new())),
|
||||
responses: Mutex::new(
|
||||
|
@ -230,7 +230,7 @@ impl Manager {
|
||||
if result.len() != 65 {
|
||||
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 s = H256::from_slice(&result[33..65]);
|
||||
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 chunk_size = handle.read(&mut 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"));
|
||||
}
|
||||
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-style: normal;
|
||||
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;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -11,7 +11,7 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -19,7 +19,7 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* greek */
|
||||
@ -27,7 +27,7 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -35,7 +35,7 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -43,7 +43,7 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* latin */
|
||||
@ -51,6 +51,6 @@
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -11,7 +11,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -19,7 +19,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* greek */
|
||||
@ -27,7 +27,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -35,7 +35,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -43,7 +43,7 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
/* latin */
|
||||
@ -51,6 +51,6 @@
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
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;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parity.js",
|
||||
"version": "0.3.89",
|
||||
"version": "0.3.94",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
|
@ -170,18 +170,30 @@ export default class Parity {
|
||||
.execute('parity_generateSecretPhrase');
|
||||
}
|
||||
|
||||
getDappsAddresses (dappId) {
|
||||
getDappAddresses (dappId) {
|
||||
return this._transport
|
||||
.execute('parity_getDappsAddresses', dappId)
|
||||
.execute('parity_getDappAddresses', dappId)
|
||||
.then(outAddresses);
|
||||
}
|
||||
|
||||
getNewDappsWhitelist () {
|
||||
getDappDefaultAddress (dappId) {
|
||||
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);
|
||||
}
|
||||
|
||||
getNewDappsDefaultAddress () {
|
||||
return this._transport
|
||||
.execute('parity_getNewDappsDefaultAddress')
|
||||
.then(outAddress);
|
||||
}
|
||||
|
||||
getVaultMeta (vaultName) {
|
||||
return this._transport
|
||||
.execute('parity_getVaultMeta', vaultName)
|
||||
@ -391,9 +403,14 @@ export default class Parity {
|
||||
.execute('parity_setAuthor', inAddress(address));
|
||||
}
|
||||
|
||||
setDappsAddresses (dappId, addresses) {
|
||||
setDappAddresses (dappId, addresses) {
|
||||
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) {
|
||||
@ -431,9 +448,14 @@ export default class Parity {
|
||||
.execute('parity_setMode', mode);
|
||||
}
|
||||
|
||||
setNewDappsWhitelist (addresses) {
|
||||
setNewDappsAddresses (addresses) {
|
||||
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) {
|
||||
|
@ -119,13 +119,15 @@ export default class Personal {
|
||||
case 'parity_removeAddress':
|
||||
case 'parity_setAccountName':
|
||||
case 'parity_setAccountMeta':
|
||||
case 'parity_changeVault':
|
||||
this._accountsInfo();
|
||||
return;
|
||||
|
||||
case 'parity_setDappsAddresses':
|
||||
case 'parity_setNewDappsWhitelist':
|
||||
case 'parity_setDappAddresses':
|
||||
case 'parity_setDappDefaultAddress':
|
||||
case 'parity_setNewDappsAddresses':
|
||||
case 'parity_setNewDappsDefaultAddress':
|
||||
this._defaultAccount(true);
|
||||
this._listAccounts();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
@ -1186,9 +1186,9 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
setDappsAddresses: {
|
||||
setDappAddresses: {
|
||||
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: [
|
||||
{
|
||||
type: String,
|
||||
@ -1197,7 +1197,7 @@ export default {
|
||||
},
|
||||
{
|
||||
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']
|
||||
}
|
||||
],
|
||||
@ -1208,7 +1208,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getDappsAddresses: {
|
||||
getDappAddresses: {
|
||||
subdoc: SUBDOC_ACCOUNTS,
|
||||
desc: 'Returns the list of accounts available to a specific dapp.',
|
||||
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,
|
||||
desc: 'Sets the list of accounts available to new dapps.',
|
||||
params: [
|
||||
{
|
||||
type: Array,
|
||||
desc: 'List of accounts available by default.',
|
||||
desc: 'List of accounts available by default or `null` for all accounts.',
|
||||
example: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1']
|
||||
}
|
||||
],
|
||||
@ -1242,7 +1281,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getNewDappsWhitelist: {
|
||||
getNewDappsAddresses: {
|
||||
subdoc: SUBDOC_ACCOUNTS,
|
||||
desc: 'Returns the list of accounts available to a new dapps.',
|
||||
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: {
|
||||
subdoc: SUBDOC_ACCOUNTS,
|
||||
desc: 'Returns a list of the most recent active dapps.',
|
||||
|
@ -19,23 +19,24 @@
|
||||
line-height: 1.618em;
|
||||
}
|
||||
|
||||
.password {
|
||||
flex: 0 1 50%;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:nth-child(odd) {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: 2 column layout can be made generic, now duplicated in Vaults */
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.identities, .selector {
|
||||
|
@ -18,37 +18,44 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default {
|
||||
duplicateName: (
|
||||
<FormattedMessage
|
||||
id='errors.duplicateName'
|
||||
defaultMessage='the name already exists'
|
||||
/>
|
||||
),
|
||||
|
||||
noFile: (
|
||||
<FormattedMessage
|
||||
id='createAccount.error.noFile'
|
||||
id='errors.noFile'
|
||||
defaultMessage='select a valid wallet file to import'
|
||||
/>
|
||||
),
|
||||
|
||||
noKey: (
|
||||
<FormattedMessage
|
||||
id='createAccount.error.noKey'
|
||||
id='errors.noKey'
|
||||
defaultMessage='you need to provide the raw private key'
|
||||
/>
|
||||
),
|
||||
|
||||
noMatchPassword: (
|
||||
<FormattedMessage
|
||||
id='createAccount.error.noMatchPassword'
|
||||
id='errors.noMatchPassword'
|
||||
defaultMessage='the supplied passwords does not match'
|
||||
/>
|
||||
),
|
||||
|
||||
noName: (
|
||||
<FormattedMessage
|
||||
id='createAccount.error.noName'
|
||||
defaultMessage='you need to specify a valid name for the account'
|
||||
id='errors.noName'
|
||||
defaultMessage='you need to specify a valid name'
|
||||
/>
|
||||
),
|
||||
|
||||
invalidKey: (
|
||||
<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"'
|
||||
/>
|
||||
)
|
||||
|
@ -102,7 +102,7 @@ export default class Store {
|
||||
|
||||
loadWhitelist () {
|
||||
return this._api.parity
|
||||
.getNewDappsWhitelist()
|
||||
.getNewDappsAddresses()
|
||||
.then((whitelist) => {
|
||||
this.setWhitelist(whitelist);
|
||||
})
|
||||
@ -113,7 +113,7 @@ export default class Store {
|
||||
|
||||
updateWhitelist (whitelist) {
|
||||
return this._api.parity
|
||||
.setNewDappsWhitelist(whitelist)
|
||||
.setNewDappsAddresses(whitelist)
|
||||
.then(() => {
|
||||
this.setWhitelist(whitelist);
|
||||
})
|
||||
|
@ -31,8 +31,8 @@ let store;
|
||||
function create () {
|
||||
api = {
|
||||
parity: {
|
||||
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
||||
setNewDappsWhitelist: sinon.stub().resolves(true)
|
||||
getNewDappsAddresses: sinon.stub().resolves(WHITELIST),
|
||||
setNewDappsAddresses: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
|
||||
@ -46,7 +46,7 @@ describe('modals/DappPermissions/store', () => {
|
||||
|
||||
describe('constructor', () => {
|
||||
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', () => {
|
||||
@ -79,12 +79,12 @@ describe('modals/DappPermissions/store', () => {
|
||||
store.closeModal();
|
||||
});
|
||||
|
||||
it('calls setNewDappsWhitelist', () => {
|
||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
|
||||
it('calls setNewDappsAddresses', () => {
|
||||
expect(api.parity.setNewDappsAddresses).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
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
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import AddAddress from './AddAddress';
|
||||
import AddContract from './AddContract';
|
||||
import CreateAccount from './CreateAccount';
|
||||
import CreateWallet from './CreateWallet';
|
||||
import DappPermissions from './DappPermissions';
|
||||
import DappsVisible from './AddDapps';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import DeployContract from './DeployContract';
|
||||
import EditMeta from './EditMeta';
|
||||
import ExecuteContract from './ExecuteContract';
|
||||
import FirstRun from './FirstRun';
|
||||
import LoadContract from './LoadContract';
|
||||
import SaveContract from './SaveContract';
|
||||
import Shapeshift from './Shapeshift';
|
||||
import Verification from './Verification';
|
||||
import Transfer from './Transfer';
|
||||
import PasswordManager from './PasswordManager';
|
||||
import UpgradeParity from './UpgradeParity';
|
||||
import WalletSettings from './WalletSettings';
|
||||
|
||||
export {
|
||||
AddAddress,
|
||||
AddContract,
|
||||
CreateAccount,
|
||||
CreateWallet,
|
||||
DappPermissions,
|
||||
DappsVisible,
|
||||
DeleteAccount,
|
||||
DeployContract,
|
||||
EditMeta,
|
||||
ExecuteContract,
|
||||
FirstRun,
|
||||
LoadContract,
|
||||
SaveContract,
|
||||
Shapeshift,
|
||||
Verification,
|
||||
Transfer,
|
||||
PasswordManager,
|
||||
UpgradeParity,
|
||||
WalletSettings
|
||||
};
|
||||
export AddAddress from './AddAddress';
|
||||
export AddContract from './AddContract';
|
||||
export CreateAccount from './CreateAccount';
|
||||
export CreateWallet from './CreateWallet';
|
||||
export DappPermissions from './DappPermissions';
|
||||
export DappsVisible from './AddDapps';
|
||||
export DeleteAccount from './DeleteAccount';
|
||||
export DeployContract from './DeployContract';
|
||||
export EditMeta from './EditMeta';
|
||||
export ExecuteContract from './ExecuteContract';
|
||||
export FirstRun from './FirstRun';
|
||||
export LoadContract from './LoadContract';
|
||||
export PasswordManager from './PasswordManager';
|
||||
export SaveContract from './SaveContract';
|
||||
export Shapeshift from './Shapeshift';
|
||||
export Transfer from './Transfer';
|
||||
export UpgradeParity from './UpgradeParity';
|
||||
export VaultAccounts from './VaultAccounts';
|
||||
export VaultCreate from './VaultCreate';
|
||||
export VaultLock from './VaultLock';
|
||||
export VaultUnlock from './VaultUnlock';
|
||||
export Verification from './Verification';
|
||||
export WalletSettings from './WalletSettings';
|
||||
|
@ -74,7 +74,7 @@ $codeColor: #93a1a1;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0.5em;
|
||||
background-color: #$codeBackground;
|
||||
background-color: $codeBackground;
|
||||
color: $codeColor;
|
||||
font-size: 0.75em;
|
||||
|
||||
@ -86,5 +86,6 @@ $codeColor: #93a1a1;
|
||||
.component {
|
||||
flex: 3;
|
||||
padding-left: 0.5em;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import AccountCard from '~/ui/AccountCard/accountCard.example';
|
||||
import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example';
|
||||
import QrCode from '~/ui/QrCode/qrCode.example';
|
||||
import SectionList from '~/ui/SectionList/sectionList.example';
|
||||
@ -25,6 +26,7 @@ import Portal from '~/ui/Portal/portal.example';
|
||||
import PlaygroundStore from './store';
|
||||
import styles from './playground.css';
|
||||
|
||||
PlaygroundStore.register(<AccountCard />);
|
||||
PlaygroundStore.register(<CurrencySymbol />);
|
||||
PlaygroundStore.register(<QrCode />);
|
||||
PlaygroundStore.register(<SectionList />);
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
Contract, Contracts, Dapp, Dapps, HistoryStore, Home,
|
||||
Settings, SettingsBackground, SettingsParity, SettingsProxy,
|
||||
SettingsViews, Signer, Status,
|
||||
Wallet, Web, WriteContract
|
||||
Vaults, Wallet, Web, WriteContract
|
||||
} from '~/views';
|
||||
import builtinDapps from '~/views/Dapps/builtin.json';
|
||||
|
||||
@ -57,6 +57,7 @@ const accountsRoutes = [
|
||||
accountsHistory.add(params.address, 'account');
|
||||
}
|
||||
},
|
||||
{ path: '/vaults', component: Vaults },
|
||||
{
|
||||
path: '/wallet/:address',
|
||||
component: Wallet,
|
||||
|
@ -16,18 +16,14 @@
|
||||
*/
|
||||
|
||||
.account {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
align-items: stretch;
|
||||
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;
|
||||
transform: scale(1);
|
||||
overflow: hidden;
|
||||
|
||||
&.copied {
|
||||
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 {
|
||||
display: flex;
|
||||
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 }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
>
|
||||
<div className={ styles.infoContainer }>
|
||||
<IdentityIcon address={ address } />
|
||||
<div className={ styles.accountInfo }>
|
||||
<div className={ styles.accountName }>
|
||||
<IdentityName
|
||||
address={ address }
|
||||
name={ name }
|
||||
unknown
|
||||
/>
|
||||
<div className={ styles.mainContainer }>
|
||||
<div className={ styles.infoContainer }>
|
||||
<IdentityIcon address={ address } />
|
||||
<div className={ styles.accountInfo }>
|
||||
<div className={ styles.accountName }>
|
||||
<IdentityName
|
||||
address={ address }
|
||||
name={ name }
|
||||
unknown
|
||||
/>
|
||||
</div>
|
||||
{ this.renderDescription(description) }
|
||||
{ this.renderAddress(address) }
|
||||
</div>
|
||||
{ this.renderDescription(description) }
|
||||
{ this.renderAddress(address) }
|
||||
</div>
|
||||
|
||||
<Balance
|
||||
balance={ balance }
|
||||
className={ styles.balance }
|
||||
showOnlyEth
|
||||
showZeroValues
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Tags tags={ tags } />
|
||||
<Balance
|
||||
balance={ balance }
|
||||
className={ styles.balance }
|
||||
showOnlyEth
|
||||
showZeroValues
|
||||
/>
|
||||
{
|
||||
tags && tags.length > 0
|
||||
? (
|
||||
<div className={ styles.tagsContainer }>
|
||||
<div className={ styles.tags }>
|
||||
<Tags
|
||||
floating={ false }
|
||||
horizontal
|
||||
tags={ tags }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ function render (props = {}) {
|
||||
address: TEST_ADDRESS,
|
||||
description: 'testDescription',
|
||||
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
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.body {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import Button from '../Button';
|
||||
import Modal from '../Modal';
|
||||
import Portal from '../Portal';
|
||||
import { CancelIcon, CheckIcon } from '../Icons';
|
||||
|
||||
import styles from './confirmDialog.css';
|
||||
@ -42,47 +42,58 @@ export default class ConfirmDialog extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string,
|
||||
disabledConfirm: PropTypes.bool,
|
||||
disabledDeny: PropTypes.bool,
|
||||
busy: PropTypes.bool,
|
||||
iconConfirm: PropTypes.node,
|
||||
iconDeny: PropTypes.node,
|
||||
labelConfirm: PropTypes.string,
|
||||
labelDeny: PropTypes.string,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onDeny: PropTypes.func.isRequired,
|
||||
open: PropTypes.bool,
|
||||
title: nodeOrStringProptype().isRequired,
|
||||
visible: PropTypes.bool.isRequired
|
||||
visible: PropTypes.bool
|
||||
}
|
||||
|
||||
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 (
|
||||
<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 }
|
||||
actions={ this.renderActions() }
|
||||
isSmallModal
|
||||
onClose={ onDeny }
|
||||
title={ title }
|
||||
visible={ visible }
|
||||
open
|
||||
>
|
||||
<div className={ styles.body }>
|
||||
{ children }
|
||||
</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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React, { PropTypes } from 'react';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import muiTheme from '../Theme';
|
||||
|
||||
import ConfirmDialog from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let onConfirm;
|
||||
let onDeny;
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
settings: {
|
||||
backgroundSeed: 'xyz'
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
onConfirm = sinon.stub();
|
||||
onDeny = sinon.stub();
|
||||
|
||||
if (props.visible === undefined) {
|
||||
props.visible = true;
|
||||
if (props.open === undefined) {
|
||||
props.open = true;
|
||||
}
|
||||
|
||||
const baseComponent = shallow(
|
||||
component = shallow(
|
||||
<ConfirmDialog
|
||||
{ ...props }
|
||||
title='test title'
|
||||
@ -62,57 +45,54 @@ function render (props = {}) {
|
||||
</ConfirmDialog>
|
||||
);
|
||||
|
||||
instance = baseComponent.instance();
|
||||
component = baseComponent.find('Connect(Modal)').shallow({
|
||||
childContextTypes: {
|
||||
muiTheme: PropTypes.object,
|
||||
store: PropTypes.object
|
||||
},
|
||||
context: {
|
||||
muiTheme,
|
||||
store: createRedux()
|
||||
}
|
||||
});
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/ConfirmDialog', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
beforeEach(() => {
|
||||
props = render().props();
|
||||
});
|
||||
|
||||
it('passes the actions', () => {
|
||||
expect(props.actions).to.deep.equal(instance.renderActions());
|
||||
props = component.find('Portal').props();
|
||||
});
|
||||
|
||||
it('passes title', () => {
|
||||
expect(props.title).to.equal('test title');
|
||||
});
|
||||
|
||||
it('passes visiblity flag', () => {
|
||||
expect(props.visible).to.be.true;
|
||||
it('passes open flag', () => {
|
||||
expect(props.open).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderActions', () => {
|
||||
describe('defaults', () => {
|
||||
it('passes the small flag', () => {
|
||||
expect(props.isSmallModal).to.be.true;
|
||||
});
|
||||
|
||||
it('maps onClose to onDeny', () => {
|
||||
expect(props.onClose).to.equal(onDeny);
|
||||
});
|
||||
|
||||
describe('buttons', () => {
|
||||
let buttons;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
buttons = instance.renderActions();
|
||||
buttons = component.props().buttons;
|
||||
});
|
||||
|
||||
it('passes the buttons', () => {
|
||||
expect(buttons.length).to.equal(2);
|
||||
});
|
||||
|
||||
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[1].props.icon.type.displayName).to.equal('NavigationCheck');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
let buttons;
|
||||
|
||||
beforeEach(() => {
|
||||
render({
|
||||
labelConfirm: 'labelConfirm',
|
||||
labelDeny: 'labelDeny',
|
||||
iconConfirm: 'iconConfirm',
|
||||
iconDeny: 'iconDeny'
|
||||
describe('overrides', () => {
|
||||
beforeEach(() => {
|
||||
render({
|
||||
labelConfirm: 'labelConfirm',
|
||||
labelDeny: 'labelDeny',
|
||||
iconConfirm: 'iconConfirm',
|
||||
iconDeny: 'iconDeny'
|
||||
});
|
||||
buttons = component.props().buttons;
|
||||
});
|
||||
buttons = instance.renderActions();
|
||||
});
|
||||
|
||||
it('renders supplied labels', () => {
|
||||
expect(buttons[0].props.label).to.equal('labelDeny');
|
||||
expect(buttons[1].props.label).to.equal('labelConfirm');
|
||||
});
|
||||
it('renders supplied labels', () => {
|
||||
expect(buttons[0].props.label).to.equal('labelDeny');
|
||||
expect(buttons[1].props.label).to.equal('labelConfirm');
|
||||
});
|
||||
|
||||
it('renders supplied icons', () => {
|
||||
expect(buttons[0].props.icon).to.equal('iconDeny');
|
||||
expect(buttons[1].props.icon).to.equal('iconConfirm');
|
||||
it('renders supplied icons', () => {
|
||||
expect(buttons[0].props.icon).to.equal('iconDeny');
|
||||
expect(buttons[1].props.icon).to.equal('iconConfirm');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -26,6 +26,7 @@ $smallFontSize: 0.75rem;
|
||||
color: $bylineColor;
|
||||
display: -webkit-box;
|
||||
line-height: $bylineLineHeight;
|
||||
min-height: $bylineMaxHeight;
|
||||
max-height: $bylineMaxHeight;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -45,5 +46,8 @@ $smallFontSize: 0.75rem;
|
||||
.title {
|
||||
line-height: $titleLineHeight;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -16,33 +16,39 @@
|
||||
*/
|
||||
|
||||
$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 {
|
||||
background: $background;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding: 0em;
|
||||
transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
position: relative;
|
||||
transition: $transitionAll;
|
||||
width: 100%;
|
||||
|
||||
.hoverOverlay {
|
||||
background: $backgroundOverlay;
|
||||
display: none;
|
||||
background: $background;
|
||||
left: 0;
|
||||
margin-top: -1.5em;
|
||||
opacity: inherit;
|
||||
padding: 0 1.5em 1.5em 1.5em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
transition: $transitionAll;
|
||||
transform: scale(0.5, 0);
|
||||
transform-origin: top center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $backgroundOverlay;
|
||||
background: $backgroundHover;
|
||||
|
||||
.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
|
||||
// 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 AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
|
||||
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 EditIcon from 'material-ui/svg-icons/content/create';
|
||||
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 LockedIcon from 'material-ui/svg-icons/action/lock';
|
||||
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 StarIcon from 'material-ui/svg-icons/toggle/star';
|
||||
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 VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -31,22 +31,20 @@ export default class Page extends Component {
|
||||
|
||||
render () {
|
||||
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 (
|
||||
<div>
|
||||
{ actionbar }
|
||||
<div className={ classes }>
|
||||
{
|
||||
title || buttons
|
||||
? (
|
||||
<Actionbar
|
||||
buttons={ buttons }
|
||||
title={ title }
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<div className={ [styles.layout, className].join(' ') }>
|
||||
{ children }
|
||||
</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;
|
||||
$modalPadding: 1.5em;
|
||||
$modalPaddingChild: 3em;
|
||||
$modalBackZ: 2500;
|
||||
|
||||
/* This should be the default case, the Portal used as a stand-alone modal */
|
||||
@ -50,7 +51,7 @@ $popoverZ: 3600;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: 0.25;
|
||||
z-index: -1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@ -67,11 +68,24 @@ $popoverZ: 3600;
|
||||
}
|
||||
|
||||
&.modal {
|
||||
bottom: $modalBottom;
|
||||
left: $modalLeft;
|
||||
right: $modalRight;
|
||||
top: $modalTop;
|
||||
z-index: $modalZ;
|
||||
&:not(.small) {
|
||||
bottom: $modalBottom;
|
||||
left: $modalLeft;
|
||||
right: $modalRight;
|
||||
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 {
|
||||
@ -100,8 +114,11 @@ $popoverZ: 3600;
|
||||
|
||||
.childContainer {
|
||||
flex: 1;
|
||||
margin: 0 -$modalPadding;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 $modalPaddingChild;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
|
@ -43,6 +43,7 @@ export default class Portal extends Component {
|
||||
className: PropTypes.string,
|
||||
hideClose: PropTypes.bool,
|
||||
isChildModal: PropTypes.bool,
|
||||
isSmallModal: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
steps: PropTypes.array,
|
||||
title: nodeOrStringProptype()
|
||||
@ -63,7 +64,7 @@ export default class Portal extends Component {
|
||||
}
|
||||
|
||||
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) {
|
||||
return null;
|
||||
@ -85,6 +86,9 @@ export default class Portal extends Component {
|
||||
isChildModal
|
||||
? styles.popover
|
||||
: styles.modal,
|
||||
isSmallModal
|
||||
? styles.small
|
||||
: null,
|
||||
className
|
||||
].join(' ')
|
||||
}
|
||||
|
@ -46,10 +46,18 @@ export default class SectionList extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rendered = items
|
||||
.map(this.renderItem)
|
||||
.filter((item) => item);
|
||||
|
||||
if (!rendered.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={ [styles.section, className].join(' ') }>
|
||||
{ this.renderOverlay() }
|
||||
{ chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) }
|
||||
{ chunkArray(rendered, ITEMS_PER_ROW).map(this.renderRow) }
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -74,11 +82,7 @@ export default class SectionList extends Component {
|
||||
className={ styles.row }
|
||||
key={ `row_${index}` }
|
||||
>
|
||||
{
|
||||
row
|
||||
.map(this.renderItem)
|
||||
.filter((item) => item)
|
||||
}
|
||||
{ row }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -74,10 +74,6 @@ describe('SectionList', () => {
|
||||
it('adds a key for the row', () => {
|
||||
expect(row.key).to.be.ok;
|
||||
});
|
||||
|
||||
it('calls renderItem for the items', () => {
|
||||
expect(instance.renderItem).to.have.been.calledTwice;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderItem', () => {
|
||||
|
@ -18,16 +18,39 @@
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: absolute;
|
||||
right: 0.25rem;
|
||||
top: 0;
|
||||
|
||||
&.floating {
|
||||
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 {
|
||||
font-size: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
border-radius: 16px;
|
||||
margin: 0.75em 0.5em 0 0;
|
||||
margin: 0 0.5em;
|
||||
padding: 0.25em 1em;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-out;
|
||||
|
@ -22,20 +22,37 @@ import styles from './tags.css';
|
||||
|
||||
export default class Tags extends Component {
|
||||
static propTypes = {
|
||||
floating: PropTypes.bool,
|
||||
horizontal: PropTypes.bool,
|
||||
handleAddSearchToken: PropTypes.func,
|
||||
setRefs: PropTypes.func,
|
||||
tags: arrayOrObjectProptype()
|
||||
}
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
horizontal: false,
|
||||
floating: true
|
||||
};
|
||||
|
||||
render () {
|
||||
const { tags } = this.props;
|
||||
const { floating, horizontal, tags } = this.props;
|
||||
|
||||
if (!tags || tags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classes = [ styles.tags ];
|
||||
|
||||
if (floating) {
|
||||
classes.push(styles.floating);
|
||||
}
|
||||
|
||||
if (horizontal) {
|
||||
classes.push(styles.horizontal);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.tags }>
|
||||
<div className={ classes.join(' ') }>
|
||||
{ this.renderTags() }
|
||||
</div>
|
||||
);
|
||||
@ -66,7 +83,7 @@ export default class Tags extends Component {
|
||||
onClick={ onClick }
|
||||
ref={ setRef }
|
||||
>
|
||||
{ tag }
|
||||
<span className={ styles.text }>{ tag }</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -15,12 +15,17 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.title {
|
||||
.title,
|
||||
.subtitle {
|
||||
.steps {
|
||||
margin: -0.5em 0 -1em 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
static propTypes = {
|
||||
activeStep: PropTypes.number,
|
||||
description: nodeOrStringProptype(),
|
||||
busy: PropTypes.bool,
|
||||
busySteps: PropTypes.array,
|
||||
byline: nodeOrStringProptype(),
|
||||
className: PropTypes.string,
|
||||
isSubTitle: PropTypes.bool,
|
||||
steps: PropTypes.array,
|
||||
title: nodeOrStringProptype()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { activeStep, className, steps, title } = this.props;
|
||||
const { activeStep, byline, className, description, isSubTitle, steps, title } = this.props;
|
||||
|
||||
if (!title && !steps) {
|
||||
return null;
|
||||
@ -47,10 +50,17 @@ export default class Title extends Component {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
[styles.title, className].join(' ')
|
||||
[
|
||||
isSubTitle
|
||||
? styles.subtitle
|
||||
: styles.title,
|
||||
className
|
||||
].join(' ')
|
||||
}
|
||||
>
|
||||
<ContainerTitle
|
||||
byline={ byline }
|
||||
description={ description }
|
||||
title={
|
||||
steps
|
||||
? 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