Merge branch 'master' into lightrpc

This commit is contained in:
Robert Habermeier 2017-02-20 18:01:29 +01:00
commit d8b1cfe082
181 changed files with 7675 additions and 901 deletions

View File

@ -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
View File

@ -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"

View File

@ -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"

View File

@ -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"]

View File

@ -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);
}
}

View File

@ -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()]));

View File

@ -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();

View File

@ -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);
});

View File

@ -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());

View File

@ -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.

View File

@ -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());
}
}

View File

@ -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())

View File

@ -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.

View File

@ -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>) {

View File

@ -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) {

View File

@ -26,3 +26,6 @@ pub use self::v9::Extract;
mod v10;
pub use self::v10::ToV10;
mod v11;
pub use self::v11::ToV11;

View File

@ -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)?;

View File

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

View File

@ -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")
}
}
}

View File

@ -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>;

View File

@ -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

View File

@ -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 {

View File

@ -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 {
@ -140,4 +140,4 @@ fn guards_delete_folders() {
drop(service);
assert!(!path.exists());
}
}

View File

@ -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 ]

View File

@ -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 = &params["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());
}

View File

@ -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 {

View File

@ -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);

View File

@ -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*

View File

@ -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))
}

View File

@ -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");
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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())
}

View File

@ -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()
}
}

View File

@ -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);
}
}

View File

@ -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(

View File

@ -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
View 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
View 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
View 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
View 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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>",

View File

@ -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) {

View File

@ -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;
}
});

View File

@ -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.',

View File

@ -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 {

View File

@ -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"'
/>
)

View File

@ -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);
})

View File

@ -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']);
});
});

View File

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

View File

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

View 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);

View 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;
});
});
});
});

View File

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

View File

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

View 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);

View 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');
});
});
});
});

View File

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

View 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);

View 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;
});
});
});
});

View File

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

View File

@ -0,0 +1,27 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.passwordHint {
color: rgba(255, 255, 255, 0.5);
font-size: 0.75em;
text-align: left;
}
.textbox {
line-height: 1.5em;
margin-bottom: 1.5em;
}

View 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);

View 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');
});
});
});
});

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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 />);

View File

@ -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,

View File

@ -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;

View 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>
);
}
}

View File

@ -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>
);
}

View File

@ -33,7 +33,9 @@ function render (props = {}) {
address: TEST_ADDRESS,
description: 'testDescription',
name: TEST_NAME,
meta: {}
meta: {
tags: [ 'tag 1', 'tag 2' ]
}
};
}

View File

@ -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;
}

View File

@ -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 }
/>
];
}
}

View File

@ -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');
});
});
});
});

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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';

View File

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

View File

@ -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;
}

View File

@ -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>

View 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);
});
});
});
});

View File

@ -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 {

View File

@ -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(' ')
}

View File

@ -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>
);
}

View File

@ -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', () => {

View File

@ -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;

View File

@ -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>
);
});

View File

@ -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;
}

View File

@ -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]

View File

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

View File

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

View File

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

View 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>
);
}
}

View File

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

View File

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

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