diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 618764d5eac23ed2d122907d7e47f13e4d4415ef..32cb7e9477ce12b566a93859287d9761814740c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,11 +22,20 @@ linux-stable: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS + - 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/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/evm deb/usr/bin/evm + - cp target/release/ethstore deb/usr/bin/ethstore + - cp target/release/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" @@ -46,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 @@ -57,11 +69,20 @@ linux-stable-debian: - triggers script: - cargo build -j $(nproc) --release --features final $CARGOFLAGS + - 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/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/evm deb/usr/bin/evm + - cp target/release/ethstore deb/usr/bin/ethstore + - cp target/release/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" @@ -422,7 +443,7 @@ windows: - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% - - rustup default 1.14.0-x86_64-pc-windows-msvc + - rustup default stable-x86_64-pc-windows-msvc - cargo build --features final --release #%CARGOFLAGS% - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe - target\release\parity.exe tools hash target\release\parity.exe > parity.sha3 @@ -439,9 +460,8 @@ windows: - zip win-installer.zip InstallParity.exe InstallParity.exe.md5 - md5sums win-installer.zip > win-installer.zip.md5 - cd ..\target\release\ - - md5sums parity.exe parity.pdb > parity.md5 - md5sums parity.exe > parity.exe.md5 - - zip parity.zip parity.exe parity.pdb parity.md5 + - zip parity.zip parity.exe parity.md5 - md5sums parity.zip > parity.zip.md5 - cd ..\.. - aws configure set aws_access_key_id %s3_key% @@ -474,13 +494,13 @@ docker-build: stage: build only: - tags + - triggers before_script: - docker info script: - - cd docker/hub + - if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; 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 + - sh scripts/docker-build.sh $DOCKER_TAG tags: - docker test-darwin: diff --git a/Cargo.lock b/Cargo.lock index 18d4177b6e06dce37b56521df77510d9f836b56a..6924cfe00d1adedc34a1232c2fc353423df391de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,33 +9,36 @@ dependencies = [ "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-dapps 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", + "ethcore 1.7.0", + "ethcore-dapps 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", "ethcore-ipc-hypervisor 1.2.0", - "ethcore-ipc-nano 1.6.0", + "ethcore-ipc-nano 1.7.0", "ethcore-ipc-tests 0.1.0", - "ethcore-light 1.6.0", - "ethcore-logger 1.6.0", - "ethcore-rpc 1.6.0", - "ethcore-signer 1.6.0", - "ethcore-stratum 1.6.0", - "ethcore-util 1.6.0", - "ethsync 1.6.0", + "ethcore-light 1.7.0", + "ethcore-logger 1.7.0", + "ethcore-rpc 1.7.0", + "ethcore-secretstore 1.0.0", + "ethcore-signer 1.7.0", + "ethcore-stratum 1.7.0", + "ethcore-util 1.7.0", + "ethsync 1.7.0", + "evmbin 0.1.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", + "parity-hash-fetch 1.7.0", + "parity-ipfs-api 1.7.0", + "parity-local-store 0.1.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", - "parity-updater 1.6.0", + "parity-updater 1.7.0", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -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" @@ -326,7 +344,7 @@ dependencies = [ [[package]] name = "eth-secp256k1" version = "0.5.6" -source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16" +source = "git+https://github.com/ethcore/rust-secp256k1#98ad9b9ecae44a563efdd64273bcebc6b4ed81c6" dependencies = [ "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -349,7 +367,7 @@ dependencies = [ [[package]] name = "ethash" -version = "1.6.0" +version = "1.7.0" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -359,7 +377,7 @@ dependencies = [ [[package]] name = "ethcore" -version = "1.6.0" +version = "1.7.0" dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -368,20 +386,20 @@ dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.6.0", + "ethash 1.7.0", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-stratum 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-stratum 1.7.0", + "ethcore-util 1.7.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "evmjit 1.6.0", - "hardware-wallet 1.6.0", + "evmjit 1.7.0", + "hardware-wallet 1.7.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -393,6 +411,7 @@ dependencies = [ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -417,27 +436,27 @@ dependencies = [ [[package]] name = "ethcore-dapps" -version = "1.6.0" +version = "1.7.0" dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", + "parity-hash-fetch 1.7.0", "parity-reactor 0.1.0", - "parity-ui 1.6.0", + "parity-ui 1.7.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -451,14 +470,14 @@ dependencies = [ [[package]] name = "ethcore-devtools" -version = "1.6.0" +version = "1.7.0" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-io" -version = "1.6.0" +version = "1.7.0" dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -469,17 +488,17 @@ dependencies = [ [[package]] name = "ethcore-ipc" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-devtools 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-util 1.7.0", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-ipc-codegen" -version = "1.6.0" +version = "1.7.0" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -492,9 +511,9 @@ dependencies = [ name = "ethcore-ipc-hypervisor" version = "1.2.0" dependencies = [ - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -503,9 +522,9 @@ dependencies = [ [[package]] name = "ethcore-ipc-nano" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-ipc 1.6.0", + "ethcore-ipc 1.7.0", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", @@ -515,11 +534,11 @@ dependencies = [ name = "ethcore-ipc-tests" version = "0.1.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", + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -527,29 +546,30 @@ dependencies = [ [[package]] name = "ethcore-light" -version = "1.6.0" -dependencies = [ - "ethcore 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-network 1.6.0", - "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +version = "1.7.0" +dependencies = [ + "ethcore 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-network 1.7.0", + "ethcore-util 1.7.0", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "smallvec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-logger" -version = "1.6.0" +version = "1.7.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -559,13 +579,13 @@ dependencies = [ [[package]] name = "ethcore-network" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -584,55 +604,73 @@ dependencies = [ [[package]] name = "ethcore-rpc" -version = "1.6.0" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.6.0", - "ethcore 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-light 1.6.0", - "ethcore-util 1.6.0", + "ethash 1.7.0", + "ethcore 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-light 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "ethsync 1.6.0", + "ethsync 1.7.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-ipc-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", - "parity-updater 1.6.0", + "parity-updater 1.7.0", "rlp 0.1.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", + "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-secretstore" +version = "1.0.0" +dependencies = [ + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", + "ethcrypto 0.1.0", + "ethkey 0.2.0", + "hyper 0.10.4 (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" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-ui 1.6.0", + "parity-ui 1.7.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)", @@ -640,18 +678,18 @@ dependencies = [ [[package]] name = "ethcore-stratum" -version = "1.6.0" +version = "1.7.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "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", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-tcp-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", @@ -661,7 +699,7 @@ dependencies = [ [[package]] name = "ethcore-util" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -671,7 +709,7 @@ dependencies = [ "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.6.0", + "ethcore-devtools 1.7.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -710,7 +748,7 @@ dependencies = [ name = "ethjson" version = "0.1.0" dependencies = [ - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -737,8 +775,8 @@ name = "ethstore" version = "0.1.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -759,19 +797,19 @@ dependencies = [ [[package]] name = "ethsync" -version = "1.6.0" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-light 1.6.0", - "ethcore-network 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-light 1.7.0", + "ethcore-network 1.7.0", + "ethcore-util 1.7.0", "ethkey 0.2.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -782,9 +820,19 @@ dependencies = [ "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "evmbin" +version = "0.1.0" +dependencies = [ + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.7.0", + "ethcore-util 1.7.0", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "evmjit" -version = "1.6.0" +version = "1.7.0" dependencies = [ "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -801,7 +849,7 @@ dependencies = [ name = "fetch" version = "0.1.0" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -820,7 +868,7 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -832,7 +880,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -865,7 +913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hardware-wallet" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethcore-bigint 0.1.2", "ethkey 0.2.0", @@ -907,7 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.9.14" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -928,7 +976,7 @@ dependencies = [ [[package]] name = "hyper" version = "0.10.0-a.0" -source = "git+https://github.com/ethcore/hyper#6baea9d444dd1652220ee9b4aeadaebb3de6a955" +source = "git+https://github.com/ethcore/hyper#453c683b52208fefc32d29e4ac7c863439b2321f" dependencies = [ "cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -988,20 +1036,25 @@ name = "igd" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "integer-encoding" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ipc-common-types" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-util 1.6.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-util 1.7.0", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1030,10 +1083,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" -version = "5.1.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -1043,11 +1096,11 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" -version = "7.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1055,12 +1108,12 @@ dependencies = [ [[package]] name = "jsonrpc-ipc-server" -version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1071,20 +1124,20 @@ dependencies = [ [[package]] name = "jsonrpc-macros" -version = "0.2.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "jsonrpc-tcp-server" -version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#cac47f72090c3db78e83d56d333ada52a22dd3a9" +version = "6.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#86d7a89c85f324b5f6671315d9b71010ca995300" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1306,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" @@ -1548,12 +1618,12 @@ dependencies = [ [[package]] name = "parity-hash-fetch" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "fetch 0.1.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1562,11 +1632,40 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-ipfs-api" +version = "1.7.0" +dependencies = [ + "cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.7.0", + "ethcore-util 1.7.0", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "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.7.0", + "ethcore-io 1.7.0", + "ethcore-util 1.7.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" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1574,11 +1673,11 @@ dependencies = [ name = "parity-rpc-client" version = "1.4.0" dependencies = [ - "ethcore-rpc 1.6.0", - "ethcore-signer 1.6.0", - "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)", + "ethcore-rpc 1.7.0", + "ethcore-signer 1.7.0", + "ethcore-util 1.7.0", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1590,7 +1689,7 @@ dependencies = [ [[package]] name = "parity-ui" -version = "1.6.0" +version = "1.7.0" dependencies = [ "parity-ui-dev 1.4.0", "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", @@ -1607,24 +1706,24 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#6f37aa3a80d2a6250302cd12eaae05632810aa23" +source = "git+https://github.com/ethcore/js-precompiled.git#9eef2b78d363560fe942062caaaa7f6b1d64dd17" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parity-updater" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-util 1.6.0", - "ethsync 1.6.0", - "ipc-common-types 1.6.0", + "ethcore 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-util 1.7.0", + "ethsync 1.7.0", + "ipc-common-types 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", + "parity-hash-fetch 1.7.0", "parity-reactor 0.1.0", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1817,6 +1916,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" @@ -1884,9 +1992,9 @@ name = "rpc-cli" version = "1.4.0" dependencies = [ "ethcore-bigint 0.1.2", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc-client 1.4.0", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2119,6 +2227,13 @@ name = "stable-heap" version = "0.1.0" source = "git+https://github.com/carllerche/stable-heap?rev=3c5cd1ca47#3c5cd1ca4706f167a1de85658b5af0d6d3e65165" +[[package]] +name = "stats" +version = "0.1.0" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.3.0" @@ -2230,7 +2345,7 @@ name = "tokio-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2242,7 +2357,7 @@ name = "tokio-proto" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2258,7 +2373,7 @@ name = "tokio-service" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2326,6 +2441,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" @@ -2450,6 +2570,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" @@ -2464,6 +2585,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)" = "" "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" @@ -2484,7 +2606,7 @@ dependencies = [ "checksum ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d8f6cc4c1acd005f48e1d17b06a461adac8fb6eeeb331fbf19a0e656fba91cd" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" -"checksum futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bad0a2ac64b227fdc10c254051ae5af542cf19c9328704fd4092f7914196897" +"checksum futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c1913eb7083840b1bbcbf9631b7fda55eaf35fe7ead13cca034e8946f9e2bc41" "checksum futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bb982bb25cd8fa5da6a8eb3a460354c984ff1113da82bcb4f0b0862b5795db82" "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" @@ -2496,18 +2618,19 @@ dependencies = [ "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "" "checksum hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "220407e5a263f110ec30a071787c9535918fdfc97def5680c90013c3f30c38c1" -"checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7" +"checksum hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9bf64f730d6ee4b0528a5f0a316363da9d8104318731509d4ccc86248f82b3" "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" -"checksum jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-ipc-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-tcp-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" @@ -2531,6 +2654,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)" = "" "checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "" "checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09" @@ -2581,6 +2706,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)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2642,6 +2768,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" diff --git a/Cargo.toml b/Cargo.toml index f304c917ba8958244ba88ef26d40c3c419cfc8a9..28af3c1f29f2278db92168b106297f937e346c41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ number_prefix = "0.2" rpassword = "0.2.1" semver = "0.5" ansi_term = "0.7" -lazy_static = "0.2" regex = "0.1" isatty = "0.1" toml = "0.2" @@ -24,7 +23,7 @@ serde = "0.9" serde_json = "0.9" app_dirs = "1.1.1" fdlimit = "0.1" -hyper = { version = "0.9", default-features = false } +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } ethsync = { path = "sync" } @@ -40,14 +39,18 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-light = { path = "ethcore/light" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } +evmbin = { path = "evmbin" } 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" diff --git a/README.md b/README.md index 23a1c6f73413bf43ba515d8002bf10721002bc27..2511e7a4f7f3ca7aa81196e38f6155cc169b56e3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Be sure to check out [our wiki][wiki-url] for more information. [doc-url]: https://ethcore.github.io/parity/ethcore/index.html [wiki-url]: https://github.com/ethcore/parity/wiki -**Parity requires Rust version 1.14.0 to build** +**Parity requires Rust version 1.15.0 to build** ---- @@ -32,7 +32,7 @@ Be sure to check out [our wiki][wiki-url] for more information. Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs. -Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It +Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) simply go to http://127.0.0.1:8080/. It includes various functionality allowing you to: - create and manage your Ethereum accounts; - manage your Ether and any Ethereum tokens; @@ -100,7 +100,11 @@ $ cargo build --release ``` This will produce an executable in the `./target/release` subdirectory. +Note: if cargo fails to parse manifest try: +```bash +$ ~/.cargo/bin/cargo build --release +``` ---- ## Simple one-line installer for Mac and Ubuntu diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 2c99dde4f5c937c18dab369e59a223921d96bcba..508fbc1a0b57ff8ac4d7e2f16f823d92d0fce0e6 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity Dapps crate" name = "ethcore-dapps" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] diff --git a/dapps/js-glue/Cargo.toml b/dapps/js-glue/Cargo.toml index 1074330bea094524fcd98162b79abc7676a35c3e..b53b158c74ced4c8af0e5325fe08d1fddd8be58c 100644 --- a/dapps/js-glue/Cargo.toml +++ b/dapps/js-glue/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Base Package for all Parity built-in dapps" name = "parity-dapps-glue" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index b521c0ba150d755db1ba6beb70cf2b1476ec5e38..9106e0d70ba2d920af9b32931ce3357191454dee 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -39,7 +39,11 @@ pub struct RestApi { impl RestApi { pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { - cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), + cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() { + "all" | "*" | "any" => AccessControlAllowOrigin::Any, + "null" => AccessControlAllowOrigin::Null, + other => AccessControlAllowOrigin::Value(other.into()), + }).collect()), endpoints: endpoints, fetcher: fetcher, }) diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index bda5b305cefcde6977d4f36a02c5b95410557dae..30c62a031bae080e76fd592c2f711e5e621ad9a4 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -111,6 +111,7 @@ pub struct ServerBuilder { web_proxy_tokens: Arc, signer_address: Option<(String, u16)>, allowed_hosts: Option>, + extra_cors: Option>, remote: Remote, fetch: Option, } @@ -126,6 +127,7 @@ impl ServerBuilder { web_proxy_tokens: Arc::new(|_| false), signer_address: None, allowed_hosts: Some(vec![]), + extra_cors: None, remote: remote, fetch: None, } @@ -143,6 +145,7 @@ impl ServerBuilder { web_proxy_tokens: self.web_proxy_tokens, signer_address: self.signer_address, allowed_hosts: self.allowed_hosts, + extra_cors: self.extra_cors, remote: self.remote, fetch: Some(fetch), } @@ -174,6 +177,13 @@ impl ServerBuilder { self } + /// Extra cors headers. + /// `None` - no additional CORS URLs + pub fn extra_cors_headers(mut self, cors: Option>) -> Self { + self.extra_cors = cors; + self + } + /// Change extra dapps paths (apart from `dapps_path`) pub fn extra_dapps>(mut self, extra_dapps: &[P]) -> Self { self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); @@ -187,6 +197,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, NoAuth, handler, self.dapps_path, @@ -207,6 +218,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, HttpBasicAuth::single_user(username, password), handler, self.dapps_path, @@ -251,8 +263,8 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { - match signer_address { + fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option>) -> Vec { + let basic_cors = match signer_address { Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), @@ -260,15 +272,20 @@ impl Server { format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("https://{}", address(&signer_address)), - ], None => vec![], + }; + + match extra_cors { + None => basic_cors, + Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(), } } fn start_http>( addr: &SocketAddr, hosts: Option>, + extra_cors: Option>, authorization: A, handler: RpcHandler, dapps_path: PathBuf, @@ -297,11 +314,11 @@ impl Server { remote.clone(), fetch.clone(), )); - let cors_domains = Self::cors_domains(signer_address.clone()); + let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors); let special = Arc::new({ let mut special = HashMap::new(); - special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); + special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone())); special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert( router::SpecialEndpoint::Api, @@ -413,8 +430,9 @@ mod util_tests { // given // when - let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); + let none = Server::cors_domains(None, None); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None); + let extra = Server::cors_domains(None, Some(vec!["all".to_owned()])); // then assert_eq!(none, Vec::::new()); @@ -425,7 +443,7 @@ mod util_tests { "https://parity.web3.site".into(), "https://parity.web3.site:18180".into(), "https://127.0.0.1:18180".into() - ]); + assert_eq!(extra, vec!["all".to_owned()]); } } diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index bf1b1dc933c04883dc362abf4042a7e4d0a04767..cc6f4d81a302062f3e66219c326cc6cc51ed11b0 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -25,13 +25,14 @@ use endpoint::{Endpoint, EndpointPath, Handler}; pub fn rpc>( handler: RpcHandler, + cors_domains: Vec, panic_handler: Arc () + Send>>>>, ) -> Box { Box::new(RpcEndpoint { handler: handler, meta_extractor: Arc::new(MetadataExtractor), panic_handler: panic_handler, - cors_domain: None, + cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. allowed_hosts: None, }) @@ -76,8 +77,7 @@ impl HttpMetaExtractor for MetadataExtractor { }) }); Metadata { - dapp_id: dapp_id, - origin: Origin::Dapps, + origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()), } } } diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index 0930aa0ce50ae394dd49c659e1931447d0159cd7..1b9f64b7fcbc6a44af60dead944b615fee081bd8 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; +use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers}; #[test] fn should_return_error() { @@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() { ); } +#[test] +fn should_return_extra_cors_headers() { + // given + let server = serve_extra_cors(Some(vec!["all".to_owned()])); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://somedomain.io\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + response.assert_status("HTTP/1.1 200 OK"); + response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io"); +} + diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 9df98c343b7910359dbaa0ed6db4cb6034c2279c..d1a1e9900548a94ee74d789ecd84e75f58afb895 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option>) -> ServerLoop { init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 } +pub fn serve_extra_cors(extra_cors: Option>) -> ServerLoop { + init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0 +} + pub fn serve_with_registrar() -> (ServerLoop, Arc) { init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) } diff --git a/dapps/src/tests/rpc.rs b/dapps/src/tests/rpc.rs index 7c24860999fa2bcf2c9e5bd410c4d0003f833cce..0dbba384c261696489ae6a5b73d14348c8b7e72d 100644 --- a/dapps/src/tests/rpc.rs +++ b/dapps/src/tests/rpc.rs @@ -55,8 +55,8 @@ fn should_extract_metadata() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); @@ -89,8 +89,8 @@ fn should_extract_metadata_from_custom_header() { // given let mut io = MetaIoHandler::default(); io.add_method_with_meta("rpc_test", |_params, meta: Metadata| { - assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned())); - assert_eq!(meta.origin, Origin::Dapps); + assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into())); + assert_eq!(meta.dapp_id(), "https://parity.io/".into()); future::ok(Value::String("Hello World!".into())).boxed() }); let server = serve_with_rpc(io); diff --git a/dapps/ui/Cargo.toml b/dapps/ui/Cargo.toml index e17948204463cfd8312c82cb15f2a589f01eda0f..2ab128ad5b67a121fff46c7701c9ddbde5af4325 100644 --- a/dapps/ui/Cargo.toml +++ b/dapps/ui/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Parity UI" homepage = "http://parity.io" license = "GPL-3.0" name = "parity-ui" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [build-dependencies] diff --git a/db/Cargo.toml b/db/Cargo.toml index fcceaa17d01d5b6f011f823c1d9de976c756088d..a3fe0804c3db844af586742c06927f1f8de4ce14 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Database" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-db" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 0cf5a6b2e086687927478099309dcabd98fd79d9..8759b81c982ea536ca9214da0778da4834800176 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-devtools" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 7ab94b3cc22a3bb85b26d57171d55169850e3dd6..81ec9213352cef82e8a905a173a952f3d802c286 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -1,5 +1,10 @@ FROM ubuntu:14.04 +MAINTAINER Parity Technologies WORKDIR /build +#ENV for build TAG +ARG BUILD_TAG +ENV BUILD_TAG ${BUILD_TAG:-master} +RUN echo $BUILD_TAG # install tools and dependencies RUN apt-get update && \ apt-get install -y --force-yes --no-install-recommends \ @@ -25,44 +30,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 - + git pull&& \ + git checkout $BUILD_TAG && \ + 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 + 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 ["/build/parity/target/release/parity"] +ENTRYPOINT ["/parity/parity"] diff --git a/ethash/Cargo.toml b/ethash/Cargo.toml index 36909a52523a3efc6d716c6ac38fc9f5371b0b6b..8be24f9aef21c8101b8b154f0aba6e5df894d2a7 100644 --- a/ethash/Cargo.toml +++ b/ethash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethash" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [lib] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 442f8b78596b9a6b73f2321852bbf1b9b92d78ac..64010fadf90449461da3c6a74ef9e5f1956c0080 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" @@ -43,6 +43,7 @@ rlp = { path = "../util/rlp" } ethcore-stratum = { path = "../stratum" } ethcore-bloom-journal = { path = "../util/bloom" } hardware-wallet = { path = "../hw" } +stats = { path = "../util/stats" } [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index d2444dd59a9989e07fe6a9d2e56ec298b81b1c6e..d8844dc3f8ff10c79b124b1d94e3a5d5c65f4b34 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -3,7 +3,7 @@ description = "Parity LES primitives" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-light" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" @@ -23,6 +23,7 @@ smallvec = "0.3.1" futures = "0.1" rand = "0.3" itertools = "0.5" +stats = { path = "../../util/stats" } [features] default = [] diff --git a/ethcore/light/src/cache.rs b/ethcore/light/src/cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..185007a1b6646c886f515622d817e119d84b67c1 --- /dev/null +++ b/ethcore/light/src/cache.rs @@ -0,0 +1,175 @@ +// 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 . + +//! Cache for data fetched from the network. +//! +//! Stores ancient block headers, bodies, receipts, and total difficulties. +//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted +//! vector of all gas prices from a recent range of blocks. + +use ethcore::encoded; +use ethcore::header::BlockNumber; +use ethcore::receipt::Receipt; + +use stats::Corpus; +use time::{SteadyTime, Duration}; +use util::{U256, H256}; +use util::cache::MemoryLruCache; + +/// Configuration for how much data to cache. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CacheSizes { + /// Maximum size, in bytes, of cached headers. + pub headers: usize, + /// Maximum size, in bytes, of cached canonical hashes. + pub canon_hashes: usize, + /// Maximum size, in bytes, of cached block bodies. + pub bodies: usize, + /// Maximum size, in bytes, of cached block receipts. + pub receipts: usize, + /// Maximum size, in bytes, of cached chain score for the block. + pub chain_score: usize, +} + +impl Default for CacheSizes { + fn default() -> Self { + const MB: usize = 1024 * 1024; + CacheSizes { + headers: 10 * MB, + canon_hashes: 3 * MB, + bodies: 20 * MB, + receipts: 10 * MB, + chain_score: 7 * MB, + } + } +} + +/// The light client data cache. +/// +/// Note that almost all getter methods take `&mut self` due to the necessity to update +/// the underlying LRU-caches on read. +pub struct Cache { + headers: MemoryLruCache, + canon_hashes: MemoryLruCache, + bodies: MemoryLruCache, + receipts: MemoryLruCache>, + chain_score: MemoryLruCache, + corpus: Option<(Corpus, SteadyTime)>, + corpus_expiration: Duration, +} + +impl Cache { + /// Create a new data cache with the given sizes and gas price corpus expiration time. + pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self { + Cache { + headers: MemoryLruCache::new(sizes.headers), + canon_hashes: MemoryLruCache::new(sizes.canon_hashes), + bodies: MemoryLruCache::new(sizes.bodies), + receipts: MemoryLruCache::new(sizes.receipts), + chain_score: MemoryLruCache::new(sizes.chain_score), + corpus: None, + corpus_expiration: corpus_expiration, + } + } + + /// Query header by hash. + pub fn block_header(&mut self, hash: &H256) -> Option { + self.headers.get_mut(hash).map(|x| x.clone()) + } + + /// Query hash by number. + pub fn block_hash(&mut self, num: &BlockNumber) -> Option { + self.canon_hashes.get_mut(num).map(|x| x.clone()) + } + + /// Query block body by block hash. + pub fn block_body(&mut self, hash: &H256) -> Option { + self.bodies.get_mut(hash).map(|x| x.clone()) + } + + /// Query block receipts by block hash. + pub fn block_receipts(&mut self, hash: &H256) -> Option> { + self.receipts.get_mut(hash).map(|x| x.clone()) + } + + /// Query chain score by block hash. + pub fn chain_score(&mut self, hash: &H256) -> Option { + self.chain_score.get_mut(hash).map(|x| x.clone()) + } + + /// Cache the given header. + pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) { + self.headers.insert(hash, hdr); + } + + /// Cache the given canonical block hash. + pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) { + self.canon_hashes.insert(num, hash); + } + + /// Cache the given block body. + pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) { + self.bodies.insert(hash, body); + } + + /// Cache the given block receipts. + pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec) { + self.receipts.insert(hash, receipts); + } + + /// Cache the given chain scoring. + pub fn insert_chain_score(&mut self, hash: H256, score: U256) { + self.chain_score.insert(hash, score); + } + + /// Get gas price corpus, if recent enough. + pub fn gas_price_corpus(&self) -> Option> { + let now = SteadyTime::now(); + + self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| { + if *tm + self.corpus_expiration >= now { + Some(corpus.clone()) + } else { + None + } + }) + } + + /// Set the cached gas price corpus. + pub fn set_gas_price_corpus(&mut self, corpus: Corpus) { + self.corpus = Some((corpus, SteadyTime::now())) + } +} + +#[cfg(test)] +mod tests { + use super::Cache; + use time::Duration; + + #[test] + fn corpus_inaccessible() { + let mut cache = Cache::new(Default::default(), Duration::hours(5)); + + cache.set_gas_price_corpus(vec![].into()); + assert_eq!(cache.gas_price_corpus(), Some(vec![].into())); + + { + let corpus_time = &mut cache.corpus.as_mut().unwrap().1; + *corpus_time = *corpus_time - Duration::hours(6); + } + assert!(cache.gas_price_corpus().is_none()); + } +} diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index 403d3555d8592a248d996749186347c64145c96a..575938cd5b347e9bb59a6b08f376bff443b593b7 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -241,6 +241,14 @@ impl HeaderChain { self.block_header(BlockId::Latest).expect("Header for best block always stored; qed") } + /// Get an iterator over a block and its ancestry. + pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter { + AncestryIter { + next: self.block_header(start), + chain: self, + } + } + /// Get the nth CHT root, if it's been computed. /// /// CHT root 0 is from block `1..2048`. @@ -295,6 +303,25 @@ impl HeapSizeOf for HeaderChain { } } +/// Iterator over a block's ancestry. +pub struct AncestryIter<'a> { + next: Option, + chain: &'a HeaderChain, +} + +impl<'a> Iterator for AncestryIter<'a> { + type Item = encoded::Header; + + fn next(&mut self) -> Option { + let next = self.next.take(); + if let Some(p_hash) = next.as_ref().map(|hdr| hdr.parent_hash()) { + self.next = self.chain.block_header(BlockId::Hash(p_hash)); + } + + next + } +} + #[cfg(test)] mod tests { use super::HeaderChain; diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index ea4660abc777e695e98e36e88db859a36b6eca20..2872e0eecc0f1629519eeb629a86ee813a294b63 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -16,26 +16,24 @@ //! Light client implementation. Stores data from light sync +use std::sync::Arc; + use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; -use ethcore::client::ClientReport; +use ethcore::client::{ClientReport, EnvInfo}; +use ethcore::engines::Engine; use ethcore::ids::BlockId; use ethcore::header::Header; use ethcore::verification::queue::{self, HeaderQueue}; -use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition}; use ethcore::blockchain_info::BlockChainInfo; use ethcore::spec::Spec; use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; -use util::hash::{H256, H256FastMap}; -use util::{Bytes, Mutex, RwLock}; - -use provider::Provider; -use request; +use util::{Bytes, DBValue, H256, Mutex, RwLock}; -use self::header_chain::HeaderChain; +use self::header_chain::{AncestryIter, HeaderChain}; pub use self::service::Service; @@ -58,6 +56,18 @@ pub trait LightChainClient: Send + Sync { /// parent queued prior. fn queue_header(&self, header: Header) -> Result; + /// Attempt to get block header by block id. + fn block_header(&self, id: BlockId) -> Option; + + /// Get the best block header. + fn best_block_header(&self) -> encoded::Header; + + /// Get an iterator over a block and its ancestry. + fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a>; + + /// Get the signing network ID. + fn signing_network_id(&self) -> Option; + /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; @@ -74,11 +84,26 @@ pub trait LightChainClient: Send + Sync { fn cht_root(&self, i: usize) -> Option; } +/// Something which can be treated as a `LightChainClient`. +pub trait AsLightClient { + /// The kind of light client this can be treated as. + type Client: LightChainClient; + + /// Access the underlying light client. + fn as_light_client(&self) -> &Self::Client; +} + +impl AsLightClient for T { + type Client = Self; + + fn as_light_client(&self) -> &Self { self } +} + /// Light client implementation. pub struct Client { queue: HeaderQueue, + engine: Arc, chain: HeaderChain, - tx_pool: Mutex>, report: RwLock, import_lock: Mutex<()>, } @@ -88,8 +113,8 @@ impl Client { pub fn new(config: Config, spec: &Spec, io_channel: IoChannel) -> Self { Client { queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true), + engine: spec.engine.clone(), chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())), - tx_pool: Mutex::new(Default::default()), report: RwLock::new(ClientReport::default()), import_lock: Mutex::new(()), } @@ -100,25 +125,6 @@ impl Client { self.queue.import(header).map_err(Into::into) } - /// Import a local transaction. - pub fn import_own_transaction(&self, tx: PendingTransaction) { - self.tx_pool.lock().insert(tx.transaction.hash(), tx); - } - - /// Fetch a vector of all pending transactions. - pub fn ready_transactions(&self) -> Vec { - let best = self.chain.best_header(); - self.tx_pool.lock() - .values() - .filter(|t| match t.condition { - Some(TransactionCondition::Number(x)) => x <= best.number(), - Some(TransactionCondition::Timestamp(x)) => x <= best.timestamp(), - None => true, - }) - .cloned() - .collect() - } - /// Inquire about the status of a given header. pub fn status(&self, hash: &H256) -> BlockStatus { match self.queue.status(hash) { @@ -159,6 +165,21 @@ impl Client { self.chain.block_header(id) } + /// Get the best block header. + pub fn best_block_header(&self) -> encoded::Header { + self.chain.best_header() + } + + /// Get an iterator over a block and its ancestry. + pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter { + self.chain.ancestry_iter(start) + } + + /// Get the signing network id. + pub fn signing_network_id(&self) -> Option { + self.engine.signing_network_id(&self.latest_env_info()) + } + /// Flush the header queue. pub fn flush_queue(&self) { self.queue.flush() @@ -207,6 +228,48 @@ impl Client { self.chain.heap_size_of_children() } + + /// Get a handle to the verification engine. + pub fn engine(&self) -> &Arc { + &self.engine + } + + /// Get the latest environment info. + pub fn latest_env_info(&self) -> EnvInfo { + self.env_info(BlockId::Latest) + .expect("Best block header and recent hashes always stored; qed") + } + + /// Get environment info for a given block. + pub fn env_info(&self, id: BlockId) -> Option { + let header = match self.block_header(id) { + Some(hdr) => hdr, + None => return None, + }; + + Some(EnvInfo { + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: Default::default(), + gas_limit: header.gas_limit(), + }) + } + + fn build_last_hashes(&self, mut parent_hash: H256) -> Arc> { + let mut v = Vec::with_capacity(256); + for _ in 0..255 { + v.push(parent_hash); + match self.block_header(BlockId::Hash(parent_hash)) { + Some(header) => parent_hash = header.hash(), + None => break, + } + } + + Arc::new(v) + } } impl LightChainClient for Client { @@ -216,6 +279,22 @@ impl LightChainClient for Client { self.import_header(header) } + fn block_header(&self, id: BlockId) -> Option { + Client::block_header(self, id) + } + + fn best_block_header(&self) -> encoded::Header { + Client::best_block_header(self) + } + + fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box + 'a> { + Box::new(Client::ancestry_iter(self, start)) + } + + fn signing_network_id(&self) -> Option { + Client::signing_network_id(self) + } + fn is_known(&self, hash: &H256) -> bool { self.status(hash) == BlockStatus::InChain } @@ -237,8 +316,8 @@ impl LightChainClient for Client { } } -// dummy implementation -- may draw from canonical cache further on. -impl Provider for Client { +// dummy implementation, should be removed when a `TestClient` is added. +impl ::provider::Provider for Client { fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) } @@ -263,19 +342,23 @@ impl Provider for Client { None } - fn state_proof(&self, _req: request::StateProof) -> Vec { + fn state_proof(&self, _req: ::request::StateProof) -> Vec { Vec::new() } - fn contract_code(&self, _req: request::ContractCode) -> Bytes { + fn contract_code(&self, _req: ::request::ContractCode) -> Bytes { Vec::new() } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + fn header_proof(&self, _req: ::request::HeaderProof) -> Option<(encoded::Header, Vec)> { + None + } + + fn transaction_proof(&self, _req: ::request::TransactionProof) -> Option> { None } - fn ready_transactions(&self) -> Vec { + fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> { Vec::new() } } diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 6236ba1180b34bbb0d023d5717573f88a602166b..b6e06a02b5504309858362d8dd72b0c5362ba30e 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -36,6 +36,8 @@ pub mod client; pub mod cht; pub mod net; pub mod on_demand; +pub mod transaction_queue; +pub mod cache; #[cfg(not(feature = "ipc"))] pub mod provider; @@ -54,6 +56,7 @@ pub mod remote { mod types; pub use self::provider::Provider; +pub use self::transaction_queue::TransactionQueue; pub use types::les_request as request; #[macro_use] @@ -69,6 +72,7 @@ extern crate time; extern crate futures; extern crate rand; extern crate itertools; +extern crate stats; #[cfg(feature = "ipc")] extern crate ethcore_ipc as ipc; diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index 627a7ef0f90c688c07ff1c26f0a98b40005a10fa..dda78e0b64f73da8203484ed729da3bd49778c92 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -44,8 +44,8 @@ pub enum Error { Rlp(DecoderError), /// A network error. Network(NetworkError), - /// Out of buffer. - BufferEmpty, + /// Out of credits. + NoCredits, /// Unrecognized packet code. UnrecognizedPacket(u8), /// Unexpected handshake. @@ -72,7 +72,7 @@ impl Error { match *self { Error::Rlp(_) => Punishment::Disable, Error::Network(_) => Punishment::None, - Error::BufferEmpty => Punishment::Disable, + Error::NoCredits => Punishment::Disable, Error::UnrecognizedPacket(_) => Punishment::Disconnect, Error::UnexpectedHandshake => Punishment::Disconnect, Error::WrongNetwork => Punishment::Disable, @@ -103,7 +103,7 @@ impl fmt::Display for Error { match *self { Error::Rlp(ref err) => err.fmt(f), Error::Network(ref err) => err.fmt(f), - Error::BufferEmpty => write!(f, "Out of buffer"), + Error::NoCredits => write!(f, "Out of request credits"), Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), Error::WrongNetwork => write!(f, "Wrong network"), diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 898934965dfe8119ef7f52a75429d4026fee6958..181f95e9519ae94e4cf5a3d9c0394beebb96b318 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -19,14 +19,14 @@ //! This uses a "Provider" to answer requests. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -use ethcore::transaction::UnverifiedTransaction; +use ethcore::transaction::{Action, UnverifiedTransaction}; use ethcore::receipt::Receipt; use io::TimerToken; use network::{NetworkProtocolHandler, NetworkContext, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::{Bytes, Mutex, RwLock, U256}; +use util::{Bytes, DBValue, Mutex, RwLock, U256}; use time::{Duration, SteadyTime}; use std::collections::HashMap; @@ -37,7 +37,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, HashOrNumber, Request}; -use self::buffer_flow::{Buffer, FlowParams}; +use self::request_credits::{Credits, FlowParams}; use self::context::{Ctx, TickCtx}; use self::error::Punishment; use self::request_set::RequestSet; @@ -51,7 +51,7 @@ mod request_set; #[cfg(test)] mod tests; -pub mod buffer_flow; +pub mod request_credits; pub use self::error::Error; pub use self::context::{BasicContext, EventContext, IoContext}; @@ -73,7 +73,7 @@ pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; pub const MAX_PROTOCOL_VERSION: u8 = 1; /// Packet count for LES. -pub const PACKET_COUNT: u8 = 15; +pub const PACKET_COUNT: u8 = 17; // packet ID definitions. mod packet { @@ -109,6 +109,10 @@ mod packet { // request and response for header proofs in a CHT. pub const GET_HEADER_PROOFS: u8 = 0x0d; pub const HEADER_PROOFS: u8 = 0x0e; + + // request and response for transaction proof. + pub const GET_TRANSACTION_PROOF: u8 = 0x0f; + pub const TRANSACTION_PROOF: u8 = 0x10; } // timeouts for different kinds of requests. all values are in milliseconds. @@ -121,6 +125,7 @@ mod timeout { pub const PROOFS: i64 = 4000; pub const CONTRACT_CODES: i64 = 5000; pub const HEADER_PROOFS: i64 = 3500; + pub const TRANSACTION_PROOF: i64 = 5000; } /// A request id. @@ -143,10 +148,10 @@ struct PendingPeer { /// Relevant data to each peer. Not accessible publicly, only `pub` due to /// limitations of the privacy system. pub struct Peer { - local_buffer: Buffer, // their buffer relative to us + local_credits: Credits, // their credits relative to us status: Status, capabilities: Capabilities, - remote_flow: Option<(Buffer, FlowParams)>, + remote_flow: Option<(Credits, FlowParams)>, sent_head: H256, // last chain head we've given them. last_update: SteadyTime, pending_requests: RequestSet, @@ -155,21 +160,21 @@ pub struct Peer { impl Peer { // check the maximum cost of a request, returning an error if there's - // not enough buffer left. + // not enough credits left. // returns the calculated maximum cost. fn deduct_max(&mut self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result { - flow_params.recharge(&mut self.local_buffer); + flow_params.recharge(&mut self.local_credits); let max_cost = flow_params.compute_cost(kind, max); - self.local_buffer.deduct_cost(max_cost)?; + self.local_credits.deduct_cost(max_cost)?; Ok(max_cost) } - // refund buffer for a request. returns new buffer amount. + // refund credits for a request. returns new amount of credits. fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 { - flow_params.refund(&mut self.local_buffer, amount); + flow_params.refund(&mut self.local_credits, amount); - self.local_buffer.current() + self.local_credits.current() } } @@ -206,6 +211,8 @@ pub trait Handler: Send + Sync { /// Called when a peer responds with header proofs. Each proof should be a block header coupled /// with a series of trie nodes is ascending order by distance from the root. fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec)]) { } + /// Called when a peer responds with a transaction proof. Each proof is a vector of state items. + fn on_transaction_proof(&self, _ctx: &EventContext, _req_id: ReqId, _state_items: &[DBValue]) { } /// Called to "tick" the handler periodically. fn tick(&self, _ctx: &BasicContext) { } /// Called on abort. This signals to handlers that they should clean up @@ -218,7 +225,7 @@ pub trait Handler: Send + Sync { pub struct Params { /// Network id. pub network_id: u64, - /// Buffer flow parameters. + /// Request credits parameters. pub flow_params: FlowParams, /// Initial capabilities. pub capabilities: Capabilities, @@ -322,16 +329,26 @@ impl LightProtocol { .map(|peer| peer.lock().status.clone()) } + /// Get number of (connected, active) peers. + pub fn peer_count(&self) -> (usize, usize) { + let num_pending = self.pending_peers.read().len(); + let peers = self.peers.read(); + ( + num_pending + peers.len(), + peers.values().filter(|p| !p.lock().pending_requests.is_empty()).count(), + ) + } + /// Check the maximum amount of requests of a specific type /// which a peer would be able to serve. Returns zero if the - /// peer is unknown or has no buffer flow parameters. + /// peer is unknown or has no credit parameters. fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize { self.peers.read().get(&peer).and_then(|peer| { let mut peer = peer.lock(); match peer.remote_flow { - Some((ref mut buf, ref flow)) => { - flow.recharge(buf); - Some(flow.max_amount(&*buf, kind)) + Some((ref mut c, ref flow)) => { + flow.recharge(c); + Some(flow.max_amount(&*c, kind)) } None => None, } @@ -341,7 +358,7 @@ impl LightProtocol { /// Make a request to a peer. /// /// Fails on: nonexistent peer, network error, peer not server, - /// insufficient buffer. Does not check capabilities before sending. + /// insufficient credits. Does not check capabilities before sending. /// On success, returns a request id which can later be coordinated /// with an event. pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result { @@ -350,10 +367,10 @@ impl LightProtocol { let mut peer = peer.lock(); match peer.remote_flow { - Some((ref mut buf, ref flow)) => { - flow.recharge(buf); + Some((ref mut c, ref flow)) => { + flow.recharge(c); let max = flow.compute_cost(request.kind(), request.amount()); - buf.deduct_cost(max)?; + c.deduct_cost(max)?; } None => return Err(Error::NotServer), } @@ -370,6 +387,7 @@ impl LightProtocol { request::Kind::StateProofs => packet::GET_PROOFS, request::Kind::Codes => packet::GET_CONTRACT_CODES, request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, + request::Kind::TransactionProof => packet::GET_TRANSACTION_PROOF, }; io.send(*peer_id, packet_id, packet_data); @@ -454,7 +472,7 @@ impl LightProtocol { // - check whether request kinds match fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result { let req_id = ReqId(raw.val_at(0)?); - let cur_buffer: U256 = raw.val_at(1)?; + let cur_credits: U256 = raw.val_at(1)?; trace!(target: "les", "pre-verifying response from peer {}, kind={:?}", peer, kind); @@ -470,9 +488,9 @@ impl LightProtocol { (Some(request), Some(flow_info)) => { had_req = true; - let &mut (ref mut buf, ref mut flow) = flow_info; - let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit()); - buf.update_to(actual_buffer); + let &mut (ref mut c, ref mut flow) = flow_info; + let actual_credits = ::std::cmp::min(cur_credits, *flow.limit()); + c.update_to(actual_credits); if request.kind() != kind { Some(Error::UnsolicitedResponse) @@ -529,6 +547,9 @@ impl LightProtocol { packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + packet::GET_TRANSACTION_PROOF => self.get_transaction_proof(peer, io, rlp), + packet::TRANSACTION_PROOF => self.transaction_proof(peer, io, rlp), + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), other => { @@ -675,10 +696,10 @@ impl LightProtocol { return Err(Error::BadProtocolVersion); } - let remote_flow = flow_params.map(|params| (params.create_buffer(), params)); + let remote_flow = flow_params.map(|params| (params.create_credits(), params)); self.peers.write().insert(*peer, Mutex::new(Peer { - local_buffer: self.flow_params.create_buffer(), + local_credits: self.flow_params.create_credits(), status: status.clone(), capabilities: capabilities.clone(), remote_flow: remote_flow, @@ -783,10 +804,10 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_HEADERS, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for header in response { stream.append_raw(&header.into_inner(), 1); @@ -845,11 +866,11 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_BODIES, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for body in response { match body { @@ -911,11 +932,11 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::Receipts, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::RECEIPTS, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for receipts in response { stream.append_raw(&receipts, 1); @@ -985,11 +1006,11 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::StateProofs, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::PROOFS, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for proof in response { stream.append_raw(&proof, 1); @@ -1057,11 +1078,11 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::Codes, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::CONTRACT_CODES, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for code in response { stream.append(&code); @@ -1130,11 +1151,11 @@ impl LightProtocol { let actual_cost = self.flow_params.compute_cost(request::Kind::HeaderProofs, response_len); assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::HEADER_PROOFS, { let mut stream = RlpStream::new_list(3); - stream.append(&req_id).append(&cur_buffer).begin_list(response.len()); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); for proof in response { stream.append_raw(&proof, 1); @@ -1172,6 +1193,90 @@ impl LightProtocol { Ok(()) } + // Receive a request for proof-of-execution. + fn get_transaction_proof(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + // refuse to execute more than this amount of gas at once. + // this is appx. the point at which the proof of execution would no longer fit in + // a single Devp2p packet. + const MAX_GAS: usize = 50_000_000; + use util::Uint; + + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, + None => { + debug!(target: "les", "Ignoring request from unknown peer"); + return Ok(()) + } + }; + let mut peer = peer.lock(); + + let req_id: u64 = raw.val_at(0)?; + + let req = { + let req_rlp = raw.at(1)?; + request::TransactionProof { + at: req_rlp.val_at(0)?, + from: req_rlp.val_at(1)?, + action: if req_rlp.at(2)?.is_empty() { + Action::Create + } else { + Action::Call(req_rlp.val_at(2)?) + }, + gas: ::std::cmp::min(req_rlp.val_at(3)?, MAX_GAS.into()), + gas_price: req_rlp.val_at(4)?, + value: req_rlp.val_at(5)?, + data: req_rlp.val_at(6)?, + } + }; + + // always charge the peer for all the gas. + peer.deduct_max(&self.flow_params, request::Kind::TransactionProof, req.gas.low_u64() as usize)?; + + let response = match self.provider.transaction_proof(req) { + Some(res) => res, + None => vec![], + }; + + let cur_credits = peer.local_credits.current(); + + io.respond(packet::TRANSACTION_PROOF, { + let mut stream = RlpStream::new_list(3); + stream.append(&req_id).append(&cur_credits).begin_list(response.len()); + + for state_item in response { + stream.append(&&state_item[..]); + } + + stream.out() + }); + + Ok(()) + } + + // Receive a response for proof-of-execution. + fn transaction_proof(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let id_guard = self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)?; + let raw_proof: Vec = raw.at(2)?.iter() + .map(|rlp| { + let mut db_val = DBValue::new(); + db_val.append_slice(rlp.data()?); + Ok(db_val) + }) + .collect::, ::rlp::DecoderError>>()?; + + let req_id = id_guard.defuse(); + for handler in &self.handlers { + handler.on_transaction_proof(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_proof); + } + + Ok(()) + } + // Receive a set of transactions to relay. fn relay_transactions(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_TRANSACTIONS: usize = 256; @@ -1320,6 +1425,25 @@ fn encode_request(req: &Request, req_id: usize) -> Vec { .append(&proof_req.from_level); } + stream.out() + } + Request::TransactionProof(ref request) => { + let mut stream = RlpStream::new_list(2); + stream.append(&req_id).begin_list(7) + .append(&request.at) + .append(&request.from); + + match request.action { + Action::Create => stream.append_empty_data(), + Action::Call(ref to) => stream.append(to), + }; + + stream + .append(&request.gas) + .append(&request.gas_price) + .append(&request.value) + .append(&request.data); + stream.out() } } diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/request_credits.rs similarity index 77% rename from ethcore/light/src/net/buffer_flow.rs rename to ethcore/light/src/net/request_credits.rs index cce54da593f12d8b9595f78cc5bb379d5df92cab..97aa9b4315ae057aeea4a0d4b105b5bfcf801594 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/request_credits.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! LES buffer flow management. +//! Request credit management. //! -//! Every request in the LES protocol leads to a reduction -//! of the requester's buffer value as a rate-limiting mechanism. -//! This buffer value will recharge at a set rate. +//! Every request in the light protocol leads to a reduction +//! of the requester's amount of credits as a rate-limiting mechanism. +//! The amount of credits will recharge at a set rate. //! -//! This module provides an interface for configuration of buffer -//! flow costs and recharge rates. +//! This module provides an interface for configuration of +//! costs and recharge rates of request credits. //! //! Current default costs are picked completely arbitrarily, not based //! on any empirical timings or mathematical models. @@ -38,19 +38,19 @@ use time::{Duration, SteadyTime}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Cost(pub U256, pub U256); -/// Buffer value. +/// Credits value. /// /// Produced and recharged using `FlowParams`. /// Definitive updates can be made as well -- these will reset the recharge /// point to the time of the update. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Buffer { +pub struct Credits { estimate: U256, recharge_point: SteadyTime, } -impl Buffer { - /// Get the current buffer value. +impl Credits { + /// Get the current amount of credits.. pub fn current(&self) -> U256 { self.estimate.clone() } /// Make a definitive update. @@ -61,7 +61,7 @@ impl Buffer { self.recharge_point = SteadyTime::now(); } - /// Attempt to apply the given cost to the buffer. + /// Attempt to apply the given cost to the amount of credits. /// /// If successful, the cost will be deducted successfully. /// @@ -69,7 +69,7 @@ impl Buffer { /// error will be produced. pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> { match cost > self.estimate { - true => Err(Error::BufferEmpty), + true => Err(Error::NoCredits), false => { self.estimate = self.estimate - cost; Ok(()) @@ -81,12 +81,13 @@ impl Buffer { /// A cost table, mapping requests to base and per-request costs. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CostTable { - headers: Cost, + headers: Cost, // cost per header bodies: Cost, receipts: Cost, state_proofs: Cost, contract_codes: Cost, header_proofs: Cost, + transaction_proof: Cost, // cost per gas. } impl Default for CostTable { @@ -99,6 +100,7 @@ impl Default for CostTable { state_proofs: Cost(250000.into(), 25000.into()), contract_codes: Cost(200000.into(), 20000.into()), header_proofs: Cost(150000.into(), 15000.into()), + transaction_proof: Cost(100000.into(), 2.into()), } } } @@ -112,7 +114,7 @@ impl RlpEncodable for CostTable { .append(&cost.1); } - s.begin_list(6); + s.begin_list(7); append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers); append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies); @@ -120,6 +122,7 @@ impl RlpEncodable for CostTable { append_cost(s, packet::GET_PROOFS, &self.state_proofs); append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes); append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs); + append_cost(s, packet::GET_TRANSACTION_PROOF, &self.transaction_proof); } } @@ -133,6 +136,7 @@ impl RlpDecodable for CostTable { let mut state_proofs = None; let mut contract_codes = None; let mut header_proofs = None; + let mut transaction_proof = None; for row in rlp.iter() { let msg_id: u8 = row.val_at(0)?; @@ -150,6 +154,7 @@ impl RlpDecodable for CostTable { packet::GET_PROOFS => state_proofs = Some(cost), packet::GET_CONTRACT_CODES => contract_codes = Some(cost), packet::GET_HEADER_PROOFS => header_proofs = Some(cost), + packet::GET_TRANSACTION_PROOF => transaction_proof = Some(cost), _ => return Err(DecoderError::Custom("Unrecognized message in cost table")), } } @@ -161,11 +166,12 @@ impl RlpDecodable for CostTable { state_proofs: state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))?, contract_codes: contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))?, header_proofs: header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))?, + transaction_proof: transaction_proof.ok_or(DecoderError::Custom("No transaction proof gas cost specified"))?, }) } } -/// A buffer-flow manager handles costs, recharge, limits +/// Handles costs, recharge, limits of request credits. #[derive(Debug, Clone, PartialEq)] pub struct FlowParams { costs: CostTable, @@ -175,7 +181,7 @@ pub struct FlowParams { impl FlowParams { /// Create new flow parameters from a request cost table, - /// buffer limit, and (minimum) rate of recharge. + /// credit limit, and (minimum) rate of recharge. pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self { FlowParams { costs: costs, @@ -197,11 +203,12 @@ impl FlowParams { state_proofs: free_cost.clone(), contract_codes: free_cost.clone(), header_proofs: free_cost.clone(), + transaction_proof: free_cost, } } } - /// Get a reference to the buffer limit. + /// Get a reference to the credit limit. pub fn limit(&self) -> &U256 { &self.limit } /// Get a reference to the cost table. @@ -220,6 +227,7 @@ impl FlowParams { request::Kind::StateProofs => &self.costs.state_proofs, request::Kind::Codes => &self.costs.contract_codes, request::Kind::HeaderProofs => &self.costs.header_proofs, + request::Kind::TransactionProof => &self.costs.transaction_proof, }; let amount: U256 = amount.into(); @@ -227,10 +235,10 @@ impl FlowParams { } /// Compute the maximum number of costs of a specific kind which can be made - /// with the given buffer. + /// with the given amount of credits /// Saturates at `usize::max()`. This is not a problem in practice because /// this amount of requests is already prohibitively large. - pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize { + pub fn max_amount(&self, credits: &Credits, kind: request::Kind) -> usize { use util::Uint; use std::usize; @@ -241,9 +249,10 @@ impl FlowParams { request::Kind::StateProofs => &self.costs.state_proofs, request::Kind::Codes => &self.costs.contract_codes, request::Kind::HeaderProofs => &self.costs.header_proofs, + request::Kind::TransactionProof => &self.costs.transaction_proof, }; - let start = buffer.current(); + let start = credits.current(); if start <= cost.0 { return 0; @@ -259,36 +268,36 @@ impl FlowParams { } } - /// Create initial buffer parameter. - pub fn create_buffer(&self) -> Buffer { - Buffer { + /// Create initial credits.. + pub fn create_credits(&self) -> Credits { + Credits { estimate: self.limit, recharge_point: SteadyTime::now(), } } - /// Recharge the buffer based on time passed since last + /// Recharge the given credits based on time passed since last /// update. - pub fn recharge(&self, buf: &mut Buffer) { + pub fn recharge(&self, credits: &mut Credits) { let now = SteadyTime::now(); // recompute and update only in terms of full seconds elapsed // in order to keep the estimate as an underestimate. - let elapsed = (now - buf.recharge_point).num_seconds(); - buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed); + let elapsed = (now - credits.recharge_point).num_seconds(); + credits.recharge_point = credits.recharge_point + Duration::seconds(elapsed); let elapsed: U256 = elapsed.into(); - buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); + credits.estimate = ::std::cmp::min(self.limit, credits.estimate + (elapsed * self.recharge)); } - /// Refund some buffer which was previously deducted. + /// Refund some credits which were previously deducted. /// Does not update the recharge timestamp. - pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) { - buf.estimate = buf.estimate + refund_amount; + pub fn refund(&self, credits: &mut Credits, refund_amount: U256) { + credits.estimate = credits.estimate + refund_amount; - if buf.estimate > self.limit { - buf.estimate = self.limit + if credits.estimate > self.limit { + credits.estimate = self.limit } } } @@ -318,20 +327,20 @@ mod tests { } #[test] - fn buffer_mechanism() { + fn credits_mechanism() { use std::thread; use std::time::Duration; let flow_params = FlowParams::new(100.into(), Default::default(), 20.into()); - let mut buffer = flow_params.create_buffer(); + let mut credits = flow_params.create_credits(); - assert!(buffer.deduct_cost(101.into()).is_err()); - assert!(buffer.deduct_cost(10.into()).is_ok()); + assert!(credits.deduct_cost(101.into()).is_err()); + assert!(credits.deduct_cost(10.into()).is_ok()); thread::sleep(Duration::from_secs(1)); - flow_params.recharge(&mut buffer); + flow_params.recharge(&mut credits); - assert_eq!(buffer.estimate, 100.into()); + assert_eq!(credits.estimate, 100.into()); } } diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs index c9f278776713c9f6303a2d708630b351deb6ffed..e6d4068da6a9d7d3db93162a31d0ad4459de4bd5 100644 --- a/ethcore/light/src/net/request_set.rs +++ b/ethcore/light/src/net/request_set.rs @@ -101,6 +101,7 @@ impl RequestSet { request::Kind::StateProofs => timeout::PROOFS, request::Kind::Codes => timeout::CONTRACT_CODES, request::Kind::HeaderProofs => timeout::HEADER_PROOFS, + request::Kind::TransactionProof => timeout::TRANSACTION_PROOF, }; base + Duration::milliseconds(kind_timeout) <= now @@ -110,6 +111,14 @@ impl RequestSet { pub fn collect_ids(&self) -> F where F: FromIterator { self.ids.keys().cloned().collect() } + + /// Number of requests in the set. + pub fn len(&self) -> usize { + self.ids.len() + } + + /// Whether the set is empty. + pub fn is_empty(&self) -> bool { self.len() == 0 } } #[cfg(test)] diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 655dc404f94f2c98c0095ebb617329e3feb6f113..3e32f6609439c772eb9e0c19aa392f4a29175c16 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -19,7 +19,7 @@ use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View}; use util::{H256, U256}; -use super::buffer_flow::FlowParams; +use super::request_credits::FlowParams; // recognized handshake/announcement keys. // unknown keys are to be skipped, known keys have a defined order. @@ -207,7 +207,7 @@ impl Capabilities { /// Attempt to parse a handshake message into its three parts: /// - chain status /// - serving capabilities -/// - buffer flow parameters +/// - request credit parameters pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option), DecoderError> { let mut parser = Parser { pos: 0, @@ -300,7 +300,7 @@ pub struct Announcement { pub serve_chain_since: Option, /// optional new transaction-relay capability. false means "no change" pub tx_relay: bool, - // TODO: changes in buffer flow? + // TODO: changes in request credits. } /// Parse an announcement. @@ -372,7 +372,7 @@ pub fn write_announcement(announcement: &Announcement) -> Vec { #[cfg(test)] mod tests { use super::*; - use super::super::buffer_flow::FlowParams; + use super::super::request_credits::FlowParams; use util::{U256, H256, FixedHash}; use rlp::{RlpStream, Stream ,UntrustedRlp, View}; diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 47d73aef2931ab2cf0eada826b24443b9c78496a..6a9de1467767977870b10491bb93b4e339819bd1 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -20,11 +20,11 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{EachBlockWith, TestBlockChainClient}; use ethcore::ids::BlockId; -use ethcore::transaction::PendingTransaction; +use ethcore::transaction::{Action, PendingTransaction}; use ethcore::encoded; use network::{PeerId, NodeId}; -use net::buffer_flow::FlowParams; +use net::request_credits::FlowParams; use net::context::IoContext; use net::status::{Capabilities, Status, write_handshake}; use net::{encode_request, LightProtocol, Params, packet, Peer}; @@ -32,7 +32,7 @@ use provider::Provider; use request::{self, Request, Headers}; use rlp::*; -use util::{Bytes, H256, U256}; +use util::{Address, Bytes, DBValue, H256, U256}; use std::sync::Arc; @@ -127,6 +127,10 @@ impl Provider for TestProvider { None } + fn transaction_proof(&self, _req: request::TransactionProof) -> Option> { + None + } + fn ready_transactions(&self) -> Vec { self.0.client.ready_transactions() } @@ -203,7 +207,7 @@ fn genesis_mismatch() { } #[test] -fn buffer_overflow() { +fn credit_overflow() { let flow_params = make_flow_params(); let capabilities = capabilities(); @@ -268,11 +272,11 @@ fn get_block_headers() { let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect(); assert_eq!(headers.len(), 10); - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10); + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10); let mut response_stream = RlpStream::new_list(3); - response_stream.append(&req_id).append(&new_buf).begin_list(10); + response_stream.append(&req_id).append(&new_creds).begin_list(10); for header in headers { response_stream.append_raw(&header.into_inner(), 1); } @@ -317,11 +321,11 @@ fn get_block_bodies() { let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockId::Number(i + 1)).unwrap()).collect(); assert_eq!(bodies.len(), 10); - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); let mut response_stream = RlpStream::new_list(3); - response_stream.append(&req_id).append(&new_buf).begin_list(10); + response_stream.append(&req_id).append(&new_creds).begin_list(10); for body in bodies { response_stream.append_raw(&body.into_inner(), 1); } @@ -371,11 +375,11 @@ fn get_block_receipts() { .map(|hash| provider.client.block_receipts(hash).unwrap()) .collect(); - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); let mut response_stream = RlpStream::new_list(3); - response_stream.append(&req_id).append(&new_buf).begin_list(receipts.len()); + response_stream.append(&req_id).append(&new_creds).begin_list(receipts.len()); for block_receipts in receipts { response_stream.append_raw(&block_receipts, 1); } @@ -420,11 +424,11 @@ fn get_state_proofs() { vec![::util::sha3::SHA3_NULL_RLP.to_vec()], ]; - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2); + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2); let mut response_stream = RlpStream::new_list(3); - response_stream.append(&req_id).append(&new_buf).begin_list(2); + response_stream.append(&req_id).append(&new_creds).begin_list(2); for proof in proofs { response_stream.begin_list(proof.len()); for node in proof { @@ -472,11 +476,11 @@ fn get_contract_code() { key2.iter().chain(key2.iter()).cloned().collect(), ]; - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2); + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2); let mut response_stream = RlpStream::new_list(3); - response_stream.append(&req_id).append(&new_buf).begin_list(2); + response_stream.append(&req_id).append(&new_creds).begin_list(2); for code in codes { response_stream.append(&code); } @@ -488,6 +492,56 @@ fn get_contract_code() { proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body); } +#[test] +fn proof_of_execution() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let mut request = Request::TransactionProof (request::TransactionProof { + at: H256::default(), + from: Address::default(), + action: Action::Call(Address::default()), + gas: 100.into(), + gas_price: 0.into(), + value: 0.into(), + data: Vec::new(), + }); + + // first: a valid amount to request execution of. + let request_body = encode_request(&request, req_id); + let response = { + let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::TransactionProof, 100); + + let mut response_stream = RlpStream::new_list(3); + response_stream.append(&req_id).append(&new_creds).begin_list(0); + + response_stream.out() + }; + + let expected = Expect::Respond(packet::TRANSACTION_PROOF, response); + proto.handle_packet(&expected, &1, packet::GET_TRANSACTION_PROOF, &request_body); + + // next: way too much requested gas. + if let Request::TransactionProof(ref mut req) = request { + req.gas = 100_000_000.into(); + } + let req_id = 113; + let request_body = encode_request(&request, req_id); + let expected = Expect::Punish(1); + proto.handle_packet(&expected, &1, packet::GET_TRANSACTION_PROOF, &request_body); +} + #[test] fn id_guard() { use super::request_set::RequestSet; @@ -515,10 +569,10 @@ fn id_guard() { pending_requests.insert(req_id_2, req, ::time::SteadyTime::now()); proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer { - local_buffer: flow_params.create_buffer(), + local_credits: flow_params.create_credits(), status: status(provider.client.chain_info()), capabilities: capabilities.clone(), - remote_flow: Some((flow_params.create_buffer(), flow_params)), + remote_flow: Some((flow_params.create_credits(), flow_params)), sent_head: provider.client.chain_info().best_block_hash, last_update: ::time::SteadyTime::now(), pending_requests: pending_requests, diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index c34e2d92259473de53983a0c0f72657399deefe6..25cde402be1f793027a322f51b1fe910e3e9b49d 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -19,19 +19,23 @@ //! will take the raw data received here and extract meaningful results from it. use std::collections::HashMap; +use std::sync::Arc; use ethcore::basic_account::BasicAccount; use ethcore::encoded; use ethcore::receipt::Receipt; +use ethcore::state::ProvedExecution; +use ethcore::executed::{Executed, ExecutionError}; use futures::{Async, Poll, Future}; use futures::sync::oneshot::{self, Sender, Receiver}; use network::PeerId; use rlp::{RlpStream, Stream}; -use util::{Bytes, RwLock, U256}; +use util::{Bytes, DBValue, RwLock, Mutex, U256}; use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId}; +use cache::Cache; use types::les_request::{self as les_request, Request as LesRequest}; pub mod request; @@ -42,14 +46,22 @@ struct Peer { capabilities: Capabilities, } +// Which portions of a CHT proof should be sent. +enum ChtProofSender { + Both(Sender<(encoded::Header, U256)>), + Header(Sender), + ChainScore(Sender), +} + // Attempted request info and sender to put received value. enum Pending { - HeaderByNumber(request::HeaderByNumber, Sender<(encoded::Header, U256)>), // num + CHT root + HeaderByNumber(request::HeaderByNumber, ChtProofSender), HeaderByHash(request::HeaderByHash, Sender), Block(request::Body, Sender), BlockReceipts(request::BlockReceipts, Sender>), Account(request::Account, Sender), Code(request::Code, Sender), + TxProof(request::TransactionProof, Sender>), } /// On demand request service. See module docs for more details. @@ -58,30 +70,77 @@ enum Pending { pub struct OnDemand { peers: RwLock>, pending_requests: RwLock>, + cache: Arc>, orphaned_requests: RwLock>, } -impl Default for OnDemand { - fn default() -> Self { +impl OnDemand { + /// Create a new `OnDemand` service with the given cache. + pub fn new(cache: Arc>) -> Self { OnDemand { peers: RwLock::new(HashMap::new()), pending_requests: RwLock::new(HashMap::new()), + cache: cache, orphaned_requests: RwLock::new(Vec::new()), } } -} -impl OnDemand { /// Request a header by block number and CHT root hash. - /// Returns the header and the total difficulty. - pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<(encoded::Header, U256)> { + /// Returns the header. + pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver { + let (sender, receiver) = oneshot::channel(); + let cached = { + let mut cache = self.cache.lock(); + cache.block_hash(&req.num()).and_then(|hash| cache.block_header(&hash)) + }; + + match cached { + Some(hdr) => sender.complete(hdr), + None => self.dispatch_header_by_number(ctx, req, ChtProofSender::Header(sender)), + } + receiver + } + + /// Request a canonical block's chain score. + /// Returns the chain score. + pub fn chain_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver { let (sender, receiver) = oneshot::channel(); - self.dispatch_header_by_number(ctx, req, sender); + let cached = { + let mut cache = self.cache.lock(); + cache.block_hash(&req.num()).and_then(|hash| cache.chain_score(&hash)) + }; + + match cached { + Some(score) => sender.complete(score), + None => self.dispatch_header_by_number(ctx, req, ChtProofSender::ChainScore(sender)), + } + + receiver + } + + /// Request a canonical block's chain score. + /// Returns the header and chain score. + pub fn header_and_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<(encoded::Header, U256)> { + let (sender, receiver) = oneshot::channel(); + let cached = { + let mut cache = self.cache.lock(); + let hash = cache.block_hash(&req.num()); + ( + hash.clone().and_then(|hash| cache.block_header(&hash)), + hash.and_then(|hash| cache.chain_score(&hash)), + ) + }; + + match cached { + (Some(hdr), Some(score)) => sender.complete((hdr, score)), + _ => self.dispatch_header_by_number(ctx, req, ChtProofSender::Both(sender)), + } + receiver } // dispatch the request, completing the request if no peers available. - fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender<(encoded::Header, U256)>) { + fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: ChtProofSender) { let num = req.num(); let cht_num = req.cht_num(); @@ -123,7 +182,10 @@ impl OnDemand { /// it as easily. pub fn header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash) -> Receiver { let (sender, receiver) = oneshot::channel(); - self.dispatch_header_by_hash(ctx, req, sender); + match self.cache.lock().block_header(&req.0) { + Some(hdr) => sender.complete(hdr), + None => self.dispatch_header_by_hash(ctx, req, sender), + } receiver } @@ -181,7 +243,16 @@ impl OnDemand { sender.complete(encoded::Block::new(stream.out())) } else { - self.dispatch_block(ctx, req, sender); + match self.cache.lock().block_body(&req.hash) { + Some(body) => { + let mut stream = RlpStream::new_list(3); + stream.append_raw(&req.header.into_inner(), 1); + stream.append_raw(&body.into_inner(), 2); + + sender.complete(encoded::Block::new(stream.out())); + } + None => self.dispatch_block(ctx, req, sender), + } } receiver } @@ -224,7 +295,10 @@ impl OnDemand { if req.0.receipts_root() == SHA3_NULL_RLP { sender.complete(Vec::new()) } else { - self.dispatch_block_receipts(ctx, req, sender); + match self.cache.lock().block_receipts(&req.0.hash()) { + Some(receipts) => sender.complete(receipts), + None => self.dispatch_block_receipts(ctx, req, sender), + } } receiver @@ -347,6 +421,50 @@ impl OnDemand { self.orphaned_requests.write().push(pending) } + /// Request proof-of-execution for a transaction. + pub fn transaction_proof(&self, ctx: &BasicContext, req: request::TransactionProof) -> Receiver> { + let (sender, receiver) = oneshot::channel(); + + self.dispatch_transaction_proof(ctx, req, sender); + + receiver + } + + fn dispatch_transaction_proof(&self, ctx: &BasicContext, req: request::TransactionProof, sender: Sender>) { + let num = req.header.number(); + let les_req = LesRequest::TransactionProof(les_request::TransactionProof { + at: req.header.hash(), + from: req.tx.sender(), + gas: req.tx.gas, + gas_price: req.tx.gas_price, + action: req.tx.action.clone(), + value: req.tx.value, + data: req.tx.data.clone(), + }); + let pending = Pending::TxProof(req, sender); + + // we're looking for a peer with serveStateSince(num) + for (id, peer) in self.peers.read().iter() { + if peer.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= num) { + match ctx.request_from(*id, les_req.clone()) { + Ok(req_id) => { + trace!(target: "on_demand", "Assigning request to peer {}", id); + self.pending_requests.write().insert( + req_id, + pending + ); + return + } + Err(e) => + trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e), + } + } + } + + trace!(target: "on_demand", "No suitable peer for request"); + self.orphaned_requests.write().push(pending) + } + // dispatch orphaned requests, and discard those for which the corresponding // receiver has been dropped. fn dispatch_orphaned(&self, ctx: &BasicContext) { @@ -378,8 +496,15 @@ impl OnDemand { for orphaned in to_dispatch { match orphaned { - Pending::HeaderByNumber(req, mut sender) => - if !check_hangup(&mut sender) { self.dispatch_header_by_number(ctx, req, sender) }, + Pending::HeaderByNumber(req, mut sender) => { + let hangup = match sender { + ChtProofSender::Both(ref mut s) => check_hangup(s), + ChtProofSender::Header(ref mut s) => check_hangup(s), + ChtProofSender::ChainScore(ref mut s) => check_hangup(s), + }; + + if !hangup { self.dispatch_header_by_number(ctx, req, sender) } + } Pending::HeaderByHash(req, mut sender) => if !check_hangup(&mut sender) { self.dispatch_header_by_hash(ctx, req, sender) }, Pending::Block(req, mut sender) => @@ -390,6 +515,8 @@ impl OnDemand { if !check_hangup(&mut sender) { self.dispatch_account(ctx, req, sender) }, Pending::Code(req, mut sender) => if !check_hangup(&mut sender) { self.dispatch_code(ctx, req, sender) }, + Pending::TxProof(req, mut sender) => + if !check_hangup(&mut sender) { self.dispatch_transaction_proof(ctx, req, sender) } } } } @@ -439,8 +566,19 @@ impl Handler for OnDemand { Pending::HeaderByNumber(req, sender) => { if let Some(&(ref header, ref proof)) = proofs.get(0) { match req.check_response(header, proof) { - Ok(header) => { - sender.complete(header); + Ok((header, score)) => { + let mut cache = self.cache.lock(); + let hash = header.hash(); + cache.insert_block_header(hash, header.clone()); + cache.insert_block_hash(header.number(), hash); + cache.insert_chain_score(hash, score); + + match sender { + ChtProofSender::Both(sender) => sender.complete((header, score)), + ChtProofSender::Header(sender) => sender.complete(header), + ChtProofSender::ChainScore(sender) => sender.complete(score), + } + return } Err(e) => { @@ -468,6 +606,7 @@ impl Handler for OnDemand { if let Some(ref header) = headers.get(0) { match req.check_response(header) { Ok(header) => { + self.cache.lock().insert_block_header(req.0, header.clone()); sender.complete(header); return } @@ -493,9 +632,11 @@ impl Handler for OnDemand { match req { Pending::Block(req, sender) => { - if let Some(ref block) = bodies.get(0) { - match req.check_response(block) { + if let Some(ref body) = bodies.get(0) { + match req.check_response(body) { Ok(block) => { + let body = encoded::Body::new(body.to_vec()); + self.cache.lock().insert_block_body(req.hash, body); sender.complete(block); return } @@ -524,6 +665,8 @@ impl Handler for OnDemand { if let Some(ref receipts) = receipts.get(0) { match req.check_response(receipts) { Ok(receipts) => { + let hash = req.0.hash(); + self.cache.lock().insert_block_receipts(hash, receipts.clone()); sender.complete(receipts); return } @@ -596,6 +739,36 @@ impl Handler for OnDemand { } } + fn on_transaction_proof(&self, ctx: &EventContext, req_id: ReqId, items: &[DBValue]) { + let peer = ctx.peer(); + let req = match self.pending_requests.write().remove(&req_id) { + Some(req) => req, + None => return, + }; + + match req { + Pending::TxProof(req, sender) => { + match req.check_response(items) { + ProvedExecution::Complete(executed) => { + sender.complete(Ok(executed)); + return + } + ProvedExecution::Failed(err) => { + sender.complete(Err(err)); + return + } + ProvedExecution::BadProof => { + warn!("Error handling response for transaction proof request"); + ctx.disable_peer(peer); + } + } + + self.dispatch_transaction_proof(ctx.as_basic(), req, sender); + } + _ => panic!("Only transaction proof request dispatches transaction proof requests; qed"), + } + } + fn tick(&self, ctx: &BasicContext) { self.dispatch_orphaned(ctx) } @@ -604,10 +777,16 @@ impl Handler for OnDemand { #[cfg(test)] mod tests { use super::*; + + use std::sync::Arc; + + use cache::Cache; use net::{Announcement, BasicContext, ReqId, Error as LesError}; use request::{Request as LesRequest, Kind as LesRequestKind}; + use network::{PeerId, NodeId}; - use util::H256; + use time::Duration; + use util::{H256, Mutex}; struct FakeContext; @@ -624,7 +803,8 @@ mod tests { #[test] fn detects_hangup() { - let on_demand = OnDemand::default(); + let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6)))); + let on_demand = OnDemand::new(cache); let result = on_demand.header_by_hash(&FakeContext, request::HeaderByHash(H256::default())); assert!(on_demand.orphaned_requests.read().len() == 1); diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index 3964137d9c4f79012d94b4c29335af05d669143e..3a72db51d9c6243b6823093b43139d471eb0bed9 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -16,12 +16,18 @@ //! Request types, verification, and verification errors. +use std::sync::Arc; + use ethcore::basic_account::BasicAccount; use ethcore::encoded; +use ethcore::engines::Engine; +use ethcore::env_info::EnvInfo; use ethcore::receipt::Receipt; +use ethcore::state::{self, ProvedExecution}; +use ethcore::transaction::SignedTransaction; use rlp::{RlpStream, Stream, UntrustedRlp, View}; -use util::{Address, Bytes, HashDB, H256, U256}; +use util::{Address, Bytes, DBValue, HashDB, H256, U256}; use util::memorydb::MemoryDB; use util::sha3::Hashable; use util::trie::{Trie, TrieDB, TrieError}; @@ -231,6 +237,33 @@ impl Code { } } +/// Request for transaction execution, along with the parts necessary to verify the proof. +pub struct TransactionProof { + /// The transaction to request proof of. + pub tx: SignedTransaction, + /// Block header. + pub header: encoded::Header, + /// Transaction environment info. + pub env_info: EnvInfo, + /// Consensus engine. + pub engine: Arc, +} + +impl TransactionProof { + /// Check the proof, returning the proved execution or indicate that the proof was bad. + pub fn check_response(&self, state_items: &[DBValue]) -> ProvedExecution { + let root = self.header.state_root(); + + state::check_proof( + state_items, + root, + &self.tx, + &*self.engine, + &self.env_info, + ) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 4721caa73d27ad260a0ae7c97915045ef3cf9ddf..3f55a6b99d59f3f4468051d9685d169d3fe78a5b 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -17,15 +17,19 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. +use std::sync::Arc; + use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; use ethcore::encoded; +use util::{Bytes, DBValue, RwLock, H256}; use cht::{self, BlockInfo}; +use client::{LightChainClient, AsLightClient}; +use transaction_queue::TransactionQueue; -use util::{Bytes, H256}; use request; @@ -189,6 +193,10 @@ pub trait Provider: Send + Sync { /// Provide pending transactions. fn ready_transactions(&self) -> Vec; + + /// Provide a proof-of-execution for the given transaction proof request. + /// Returns a vector of all state items necessary to execute the transaction. + fn transaction_proof(&self, req: request::TransactionProof) -> Option>; } // Implementation of a light client data provider for a client. @@ -279,11 +287,104 @@ impl Provider for T { } } + fn transaction_proof(&self, req: request::TransactionProof) -> Option> { + use ethcore::transaction::Transaction; + + let id = BlockId::Hash(req.at); + let nonce = match self.nonce(&req.from, id.clone()) { + Some(nonce) => nonce, + None => return None, + }; + let transaction = Transaction { + nonce: nonce, + gas: req.gas, + gas_price: req.gas_price, + action: req.action, + value: req.value, + data: req.data, + }.fake_sign(req.from); + + self.prove_transaction(transaction, id) + } + fn ready_transactions(&self) -> Vec { BlockChainClient::ready_transactions(self) } } +/// The light client "provider" implementation. This wraps a `LightClient` and +/// a light transaction queue. +pub struct LightProvider { + client: Arc, + txqueue: Arc>, +} + +impl LightProvider { + /// Create a new `LightProvider` from the given client and transaction queue. + pub fn new(client: Arc, txqueue: Arc>) -> Self { + LightProvider { + client: client, + txqueue: txqueue, + } + } +} + +// TODO: draw from cache (shared between this and the RPC layer) +impl Provider for LightProvider { + fn chain_info(&self) -> BlockChainInfo { + self.client.as_light_client().chain_info() + } + + fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { + None + } + + fn earliest_state(&self) -> Option { + None + } + + fn block_header(&self, id: BlockId) -> Option { + self.client.as_light_client().block_header(id) + } + + fn block_body(&self, _id: BlockId) -> Option { + None + } + + fn block_receipts(&self, _hash: &H256) -> Option { + None + } + + fn state_proof(&self, _req: request::StateProof) -> Vec { + Vec::new() + } + + fn contract_code(&self, _req: request::ContractCode) -> Bytes { + Vec::new() + } + + fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + None + } + + fn transaction_proof(&self, _req: request::TransactionProof) -> Option> { + None + } + + fn ready_transactions(&self) -> Vec { + let chain_info = self.chain_info(); + self.txqueue.read().ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) + } +} + +impl AsLightClient for LightProvider { + type Client = L::Client; + + fn as_light_client(&self) -> &L::Client { + self.client.as_light_client() + } +} + #[cfg(test)] mod tests { use ethcore::client::{EachBlockWith, TestBlockChainClient}; diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..d17a863f511c108a9aa23798cb31e09b8ddad2b0 --- /dev/null +++ b/ethcore/light/src/transaction_queue.rs @@ -0,0 +1,517 @@ +// 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 . + +//! Light Transaction Queue. +//! +//! Manages local transactions, +//! but stores all local transactions, removing only on invalidated nonce. +//! +//! Under the assumption that light nodes will have a relatively limited set of +//! accounts for which they create transactions, this queue is structured in an +//! address-wise manner. + +use std::collections::{BTreeMap, HashMap}; +use std::collections::hash_map::Entry; + +use ethcore::error::TransactionError; +use ethcore::transaction::{Condition, PendingTransaction, SignedTransaction}; +use ethcore::transaction_import::TransactionImportResult; +use util::{Address, U256, H256, H256FastMap}; + +// Knowledge of an account's current nonce. +#[derive(Debug, Clone, PartialEq, Eq)] +enum CurrentNonce { + // Assumed current nonce. + Assumed(U256), + // Known current nonce. + Known(U256), +} + +impl CurrentNonce { + // whether this nonce is assumed + fn is_assumed(&self) -> bool { + match *self { + CurrentNonce::Assumed(_) => true, + CurrentNonce::Known(_) => false, + } + } + + // whether this nonce is known for certain from an external source. + fn is_known(&self) -> bool { + !self.is_assumed() + } + + // the current nonce's value. + fn value(&self) -> &U256 { + match *self { + CurrentNonce::Assumed(ref val) => val, + CurrentNonce::Known(ref val) => val, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct TransactionInfo { + hash: H256, + nonce: U256, + condition: Option, +} + +impl<'a> From<&'a PendingTransaction> for TransactionInfo { + fn from(tx: &'a PendingTransaction) -> Self { + TransactionInfo { + hash: tx.hash(), + nonce: tx.nonce.clone(), + condition: tx.condition.clone(), + } + } +} + +// transactions associated with a specific account. +#[derive(Debug, Clone, PartialEq, Eq)] +struct AccountTransactions { + // believed current nonce (gotten from initial given TX or `cull` calls). + cur_nonce: CurrentNonce, + current: Vec, // ordered "current" transactions (cur_nonce onwards) + future: BTreeMap, // "future" transactions. +} + +impl AccountTransactions { + fn is_empty(&self) -> bool { + self.current.is_empty() && self.future.is_empty() + } + + fn next_nonce(&self) -> U256 { + self.current.last().map(|last| last.nonce + 1.into()) + .unwrap_or_else(|| *self.cur_nonce.value()) + } + + // attempt to move transactions from the future queue into the current queue. + fn adjust_future(&mut self) { + let mut next_nonce = self.next_nonce(); + + loop { + match self.future.remove(&next_nonce) { + Some(tx) => self.current.push(tx), + None => break, + } + + next_nonce = next_nonce + 1.into(); + } + } +} + +/// Light transaction queue. See module docs for more details. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct TransactionQueue { + by_account: HashMap, + by_hash: H256FastMap, +} + +impl TransactionQueue { + /// Import a pending transaction to be queued. + pub fn import(&mut self, tx: PendingTransaction) -> Result { + let sender = tx.sender(); + let hash = tx.hash(); + let nonce = tx.nonce; + let tx_info = TransactionInfo::from(&tx); + + if self.by_hash.contains_key(&hash) { return Err(TransactionError::AlreadyImported) } + + let res = match self.by_account.entry(sender) { + Entry::Vacant(entry) => { + entry.insert(AccountTransactions { + cur_nonce: CurrentNonce::Assumed(nonce), + current: vec![tx_info], + future: BTreeMap::new(), + }); + + TransactionImportResult::Current + } + Entry::Occupied(mut entry) => { + let acct_txs = entry.get_mut(); + if &nonce < acct_txs.cur_nonce.value() { + // don't accept txs from before known current nonce. + if acct_txs.cur_nonce.is_known() { + return Err(TransactionError::Old) + } + + // lower our assumption until corrected later. + acct_txs.cur_nonce = CurrentNonce::Assumed(nonce); + } + + match acct_txs.current.binary_search_by(|x| x.nonce.cmp(&nonce)) { + Ok(idx) => { + trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}", + sender, nonce); + + let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info); + self.by_hash.remove(&old.hash); + + TransactionImportResult::Current + } + Err(idx) => { + let cur_len = acct_txs.current.len(); + let incr_nonce = nonce + 1.into(); + + // current is sorted with one tx per nonce, + // so if a tx with given nonce wasn't found that means it is either + // earlier in nonce than all other "current" transactions or later. + assert!(idx == 0 || idx == cur_len); + + if idx == 0 && acct_txs.current.first().map_or(false, |f| f.nonce != incr_nonce) { + let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx_info]); + + trace!(target: "txqueue", "Moving {} transactions with nonce > {} to future", + old_cur.len(), incr_nonce); + + for future in old_cur { + let future_nonce = future.nonce; + acct_txs.future.insert(future_nonce, future); + } + + TransactionImportResult::Current + } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { + trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); + let future_nonce = nonce; + acct_txs.future.insert(future_nonce, tx_info); + + TransactionImportResult::Future + } else { + trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); + + // insert, then check if we've filled any gaps. + acct_txs.current.insert(idx, tx_info); + acct_txs.adjust_future(); + + TransactionImportResult::Current + } + } + } + } + }; + + self.by_hash.insert(hash, tx); + Ok(res) + } + + /// Get pending transaction by hash. + pub fn transaction(&self, hash: &H256) -> Option { + self.by_hash.get(hash).map(|tx| (&**tx).clone()) + } + + /// Get the next nonce for a given address based on what's within the queue. + /// If the address has no queued transactions, then `None` will be returned + /// and the next nonce will have to be deduced via other means. + pub fn next_nonce(&self, address: &Address) -> Option { + self.by_account.get(address).map(AccountTransactions::next_nonce) + } + + /// Get all transactions ready to be propagated. + /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally + /// propagated transactions. + /// + /// Returned transactions are batched by sender, in order of ascending nonce. + pub fn ready_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { + self.by_account.values() + .flat_map(|acct_txs| { + acct_txs.current.iter().take_while(|tx| match tx.condition { + None => true, + Some(Condition::Number(blk_num)) => blk_num <= best_block_number, + Some(Condition::Timestamp(time)) => time <= best_block_timestamp, + }).map(|info| info.hash) + }) + .filter_map(|hash| match self.by_hash.get(&hash) { + Some(tx) => Some(tx.clone()), + None => { + warn!(target: "txqueue", "Inconsistency detected between `by_hash` and `by_account`: {} not stored.", + hash); + None + } + }) + .collect() + } + + /// Get all transactions not ready to be propagated. + /// `best_block_number` and `best_block_timestamp` are used to filter out conditionally + /// propagated transactions. + /// + /// Returned transactions are batched by sender, in order of ascending nonce. + pub fn future_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec { + self.by_account.values() + .flat_map(|acct_txs| { + acct_txs.current.iter().skip_while(|tx| match tx.condition { + None => true, + Some(Condition::Number(blk_num)) => blk_num <= best_block_number, + Some(Condition::Timestamp(time)) => time <= best_block_timestamp, + }).chain(acct_txs.future.values()).map(|info| info.hash) + }) + .filter_map(|hash| match self.by_hash.get(&hash) { + Some(tx) => Some(tx.clone()), + None => { + warn!(target: "txqueue", "Inconsistency detected between `by_hash` and `by_account`: {} not stored.", + hash); + None + } + }) + .collect() + } + + /// Addresses for which we store transactions. + pub fn queued_senders(&self) -> Vec
{ + self.by_account.keys().cloned().collect() + } + + /// Cull out all transactions by the given address which are invalidated by the given nonce. + pub fn cull(&mut self, address: Address, cur_nonce: U256) { + let mut removed_hashes = vec![]; + if let Entry::Occupied(mut entry) = self.by_account.entry(address) { + { + let acct_txs = entry.get_mut(); + acct_txs.cur_nonce = CurrentNonce::Known(cur_nonce); + + // cull old "future" keys. + let old_future: Vec<_> = acct_txs.future.keys().take_while(|&&k| k < cur_nonce).cloned().collect(); + + for old in old_future { + let hash = acct_txs.future.remove(&old) + .expect("key extracted from keys iterator; known to exist; qed") + .hash; + removed_hashes.push(hash); + } + + // then cull from "current". + let valid_pos = acct_txs.current.iter().position(|tx| tx.nonce >= cur_nonce); + match valid_pos { + None => + removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash)), + Some(valid) => + removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash)), + } + + // now try and move stuff out of future into current. + acct_txs.adjust_future(); + } + + if entry.get_mut().is_empty() { + trace!(target: "txqueue", "No more queued transactions for {} after nonce {}", + address, cur_nonce); + entry.remove(); + } + } + + trace!(target: "txqueue", "Culled {} old transactions from sender {} (nonce={})", + removed_hashes.len(), address, cur_nonce); + + for hash in removed_hashes { + self.by_hash.remove(&hash); + } + } +} + +#[cfg(test)] +mod tests { + use super::TransactionQueue; + use util::Address; + use ethcore::transaction::{Transaction, PendingTransaction, Condition}; + + #[test] + fn queued_senders() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + let tx = Transaction::default().fake_sign(sender); + + txq.import(tx.into()).unwrap(); + + assert_eq!(txq.queued_senders(), vec![sender]); + + txq.cull(sender, 1.into()); + + assert_eq!(txq.queued_senders(), vec![]); + assert!(txq.by_hash.is_empty()); + } + + #[test] + fn next_nonce() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in (0..5).chain(10..15) { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + // current: 0..5, future: 10..15 + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 5.into()); + + txq.cull(sender, 8.into()); + + // current: empty, future: 10..15 + assert_eq!(txq.ready_transactions(0, 0).len(), 0); + assert_eq!(txq.next_nonce(&sender).unwrap(), 8.into()); + + txq.cull(sender, 10.into()); + + // current: 10..15, future: empty + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 15.into()); + } + + #[test] + fn current_to_future() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in 5..10 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 5); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + + for i in 0..3 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 3); + assert_eq!(txq.next_nonce(&sender).unwrap(), 3.into()); + + for i in 3..5 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 10); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + } + + #[test] + fn conditional() { + let mut txq = TransactionQueue::default(); + let sender = Address::default(); + + for i in 0..5 { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + let tx = tx.fake_sign(sender); + + txq.import(match i { + 3 => PendingTransaction::new(tx, Some(Condition::Number(100))), + 4 => PendingTransaction::new(tx, Some(Condition::Timestamp(1234))), + _ => tx.into(), + }).unwrap(); + } + + assert_eq!(txq.ready_transactions(0, 0).len(), 3); + assert_eq!(txq.ready_transactions(0, 1234).len(), 3); + assert_eq!(txq.ready_transactions(100, 0).len(), 4); + assert_eq!(txq.ready_transactions(100, 1234).len(), 5); + } + + #[test] + fn cull_from_future() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in (0..1).chain(3..10) { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + txq.cull(sender, 6.into()); + + assert_eq!(txq.ready_transactions(0, 0).len(), 4); + assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into()); + } + + #[test] + fn import_old() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + let mut tx_a = Transaction::default(); + tx_a.nonce = 3.into(); + + let mut tx_b = Transaction::default(); + tx_b.nonce = 2.into(); + + txq.import(tx_a.fake_sign(sender).into()).unwrap(); + txq.cull(sender, 3.into()); + + assert!(txq.import(tx_b.fake_sign(sender).into()).is_err()) + } + + #[test] + fn replace_is_removed() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + let tx_b: PendingTransaction = Transaction::default().fake_sign(sender).into(); + let tx_a: PendingTransaction = { + let mut tx_a = Transaction::default(); + tx_a.gas_price = tx_b.gas_price + 1.into(); + tx_a.fake_sign(sender).into() + }; + + let hash = tx_a.hash(); + + txq.import(tx_a).unwrap(); + txq.import(tx_b).unwrap(); + + assert!(txq.transaction(&hash).is_none()); + } + + #[test] + fn future_transactions() { + let sender = Address::default(); + let mut txq = TransactionQueue::default(); + + for i in (0..1).chain(3..10) { + let mut tx = Transaction::default(); + tx.nonce = i.into(); + + let tx = tx.fake_sign(sender); + + txq.import(tx.into()).unwrap(); + } + + assert_eq!(txq.future_transactions(0, 0).len(), 7); + assert_eq!(txq.next_nonce(&sender).unwrap(), 1.into()); + } +} diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index b4940980e08750b4904a8e0c3e32c26979e524b8..dbff19eb5479b80d706b81daf569b53cd6b89195 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -16,7 +16,8 @@ //! LES request types. -use util::H256; +use ethcore::transaction::Action; +use util::{Address, H256, U256, Uint}; /// Either a hash or a number. #[derive(Debug, Clone, PartialEq, Eq)] @@ -134,6 +135,26 @@ pub struct HeaderProofs { pub requests: Vec, } +/// A request for proof of (simulated) transaction execution. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct TransactionProof { + /// Block hash to request for. + pub at: H256, + /// Address to treat as the caller. + pub from: Address, + /// Action to take: either a call or a create. + pub action: Action, + /// Amount of gas to request proof-of-execution for. + pub gas: U256, + /// Price for each gas. + pub gas_price: U256, + /// Value to simulate sending. + pub value: U256, + /// Transaction data. + pub data: Vec, +} + /// Kinds of requests. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "ipc", binary)] @@ -150,6 +171,8 @@ pub enum Kind { Codes, /// Requesting header proofs (from the CHT). HeaderProofs, + /// Requesting proof of transaction execution. + TransactionProof, } /// Encompasses all possible types of requests in a single structure. @@ -168,6 +191,8 @@ pub enum Request { Codes(ContractCodes), /// Requesting header proofs. HeaderProofs(HeaderProofs), + /// Requesting proof of transaction execution. + TransactionProof(TransactionProof), } impl Request { @@ -180,10 +205,12 @@ impl Request { Request::StateProofs(_) => Kind::StateProofs, Request::Codes(_) => Kind::Codes, Request::HeaderProofs(_) => Kind::HeaderProofs, + Request::TransactionProof(_) => Kind::TransactionProof, } } /// Get the amount of requests being made. + /// In the case of `TransactionProof`, this is the amount of gas being requested. pub fn amount(&self) -> usize { match *self { Request::Headers(ref req) => req.max, @@ -192,6 +219,10 @@ impl Request { Request::StateProofs(ref req) => req.requests.len(), Request::Codes(ref req) => req.code_requests.len(), Request::HeaderProofs(ref req) => req.requests.len(), + Request::TransactionProof(ref req) => match req.gas > usize::max_value().into() { + true => usize::max_value(), + false => req.gas.low_u64() as usize, + } } } } diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index ac7eb50417275334af14cd3ce2b397b17dd6cb56..dba7e28a5af10b0dd8facc8bba26d2c2e207dce4 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -33,7 +33,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index b165fe169179da84ee3199b4fd7fddf5ad8473a0..33c954f2cbd98fe940b3246c80b3519cb7050f73 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -48,18 +48,12 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ - "enode://08c7ee6a4f861ff0664a49532bcc86de1363acd608999d1b76609bb9bc278649906f069057630fd9493924a368b5d1dc9b8f8bf13ac26df72512f6d1fabd8c95@45.32.7.81:30303", "enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303", - "enode://687be94c3a7beaa3d2fde82fa5046cdeb3e8198354e05b29d6e0d4e276713e3707ac10f784a7904938b06b46c764875c241b0337dd853385a4d8bfcbf8190647@95.183.51.229:30303", "enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303", - "enode://ca5ae4eca09ba6787e29cf6d86f7634d07aae6b9e6317a59aff675851c0bf445068173208cf8ef7f5cd783d8e29b85b2fa3fa358124cf0546823149724f9bde1@138.68.1.16:30303", - "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303", - "enode://fa20444ef991596ce99b81652ac4e61de1eddc4ff21d3cd42762abd7ed47e7cf044d3c9ccddaf6035d39725e4eb372806787829ccb9a08ec7cb71883cb8c3abd@50.149.116.182:30303", - "enode://4bd6a4df3612c718333eb5ea7f817923a8cdf1bed89cee70d1710b45a0b6b77b2819846440555e451a9b602ad2efa2d2facd4620650249d8468008946887820a@71.178.232.20:30304", - "enode://921cf8e4c345fe8db913c53964f9cadc667644e7f20195a0b7d877bd689a5934e146ff2c2259f1bae6817b6585153a007ceb67d260b720fa3e6fc4350df25c7f@51.255.49.170:30303", - "enode://ffea3b01c000cdd89e1e9229fea3e80e95b646f9b2aa55071fc865e2f19543c9b06045cc2e69453e6b78100a119e66be1b5ad50b36f2ffd27293caa28efdd1b2@128.199.93.177:3030", - "enode://ee3da491ce6a155eb132708eb0e8d04b0637926ec0ae1b79e63fc97cb9fc3818f49250a0ae0d7f79ed62b66ec677f408c4e01741504dc7a051e274f1e803d454@91.121.65.179:40404", - "enode://48e063a6cf5f335b1ef2ed98126bf522cf254396f850c7d442fe2edbbc23398787e14cd4de7968a00175a82762de9cbe9e1407d8ccbcaeca5004d65f8398d759@159.203.255.59:30303" + "enode://5fbfb426fbb46f8b8c1bd3dd140f5b511da558cd37d60844b525909ab82e13a25ee722293c829e52cb65c2305b1637fa9a2ea4d6634a224d5f400bfe244ac0de@162.243.55.45:30303", + "enode://42d8f29d1db5f4b2947cd5c3d76c6d0d3697e6b9b3430c3d41e46b4bb77655433aeedc25d4b4ea9d8214b6a43008ba67199374a9b53633301bca0cd20c6928ab@104.155.176.151:30303", + "enode://814920f1ec9510aa9ea1c8f79d8b6e6a462045f09caa2ae4055b0f34f7416fca6facd3dd45f1cf1673c0209e0503f02776b8ff94020e98b6679a0dc561b4eba0@104.154.136.117:30303", + "enode://72e445f4e89c0f476d404bc40478b0df83a5b500d2d2e850e08eb1af0cd464ab86db6160d0fde64bd77d5f0d33507ae19035671b3c74fec126d6e28787669740@104.198.71.200:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/foundation.json similarity index 99% rename from ethcore/res/ethereum/frontier.json rename to ethcore/res/ethereum/foundation.json index 2ab8bbe5b8a8f281d41fd0a060d6dbf605ea7dc6..7338b2f2b774c91b49955dbb244caf4d2af1e510 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/foundation.json @@ -1,5 +1,5 @@ { - "name": "Frontier/Homestead", + "name": "Foundation", "dataDir": "ethereum", "engine": { "Ethash": { @@ -9,7 +9,7 @@ "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea", + "registrar" : "0xe3389675d0338462dC76C6f9A3e432550c36A142", "homesteadTransition": "0x118c30", "daoHardforkTransition": "0x1d4c00", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", diff --git a/ethcore/res/ethereum/kovan.json b/ethcore/res/ethereum/kovan.json new file mode 100644 index 0000000000000000000000000000000000000000..e9c059aaac6c7aec0eaef98444032fe424c1ba62 --- /dev/null +++ b/ethcore/res/ethereum/kovan.json @@ -0,0 +1,59 @@ +{ + "name": "Kovan", + "dataDir": "kovan", + "engine": { + "authorityRound": { + "params": { + "gasLimitBoundDivisor": "0x400", + "registrar" : "0xfAb104398BBefbd47752E7702D9fE23047E1Bca3", + "stepDuration": "4", + "blockReward": "0x4563918244F40000", + "validators" : { + "list": [ + "0x00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED", + "0x00427feae2419c15b89d1c21af10d1b6650a4d3d", + "0x4Ed9B08e6354C70fE6F8CB0411b0d3246b424d6c", + "0x0020ee4Be0e2027d76603cB751eE069519bA81A1", + + "0x0010f94b296a852aaac52ea6c5ac72e03afd032d", + + "0x007733a1FE69CF3f2CF989F81C7b4cAc1693387A", + "0x00E6d2b931F55a3f1701c7389d592a7778897879", + "0x00e4a10650e5a6D6001C38ff8E64F97016a1645c", + + "0x00a0a24b9f0e5ec7aa4c7389b8302fd0123194de" + ] + } + } + } + }, + "params": { + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2A" + }, + "genesis": { + "seal": { + "authorityRound": { + "step": "0x0", + "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x20000", + "gasLimit": "0x5B8D80" + }, + "accounts": { + "0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0x00521965e7bd230323c423d96c657db5b79d099f": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + }, + "nodes": [ + "enode://c005dd308256c60fab247813d8bf6d6e81f9cd354287837eb1c2fcf294adaa913a3208e88900ef5c55a8cba7042c301d80503edec2ad3f92a72e241ee6743854@192.241.230.87:30303", + "enode://48caeceb2724f2f71406990aa81efe87f8c53f26441d891473da2ae50cc138f238addc0e46b5aee240db55de8c711daac53d7b32a3f13e30edb86a3ca7c2700b@138.68.143.220:30303", + "enode://85705212fd28ebdd56669fb55e958feb9d81f74fe76c82f867564b6c2995e69f596df0f588eba16f1a43b69ce06485d68231a0c83fed8469b41eba0e390c126f@139.59.146.42:30303", + "enode://2aa81bd0a761cd4f02c934dcf3f81c5b65953e51ab5ba03ceb1f125eb06418a1cdffb1c9d01871aa7bd456f3fce35e745608189ad1164f72b2161634b0c3f6ea@188.166.240.190:30303", + "enode://c5900cdd6d20795d58372f42dfbab9d664c27bb97e9c27972741942736e919122f9bac28e74cbc58e4ff195475ea90d9880b71a37af5b5a8cb41d843f765cff8@174.138.79.48:30303" + ] +} diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 5b64b63da15b43d5cbe22a44b7a76891d6825d65..22f253bf85d60ee5620ff7966a56ba58bb8b04dd 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -48,7 +48,10 @@ }, "nodes": [ "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303", - "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303" + "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303", + "enode://fb28713820e718066a2f5df6250ae9d07cff22f672dbf26be6c75d088f821a9ad230138ba492c533a80407d054b1436ef18e951bb65e6901553516c8dffe8ff0@104.155.176.151:30304", + "enode://afdc6076b9bf3e7d3d01442d6841071e84c76c73a7016cb4f35c0437df219db38565766234448f1592a07ba5295a867f0ce87b359bf50311ed0b830a2361392d@104.154.136.117:30403", + "enode://21101a9597b79e933e17bc94ef3506fe99a137808907aa8fefa67eea4b789792ad11fb391f38b00087f8800a2d3dff011572b62a31232133dd1591ac2d1502c8@104.198.71.200:30403" ], "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/instant_seal.json b/ethcore/res/instant_seal.json index b2d1198835ca8ab43c27435860112d1eaf0e4177..c791b9a2d196d08794f01e9b391b1040c6baaedf 100644 --- a/ethcore/res/instant_seal.json +++ b/ethcore/res/instant_seal.json @@ -1,7 +1,11 @@ { "name": "DevelopmentChain", "engine": { - "instantSeal": null + "instantSeal": { + "params": { + "registrar": "0x0000000000000000000000000000000000000005" + } + } }, "params": { "accountStartNonce": "0x0", @@ -25,6 +29,7 @@ "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "balance": "1", "constructor": "0x606060405233600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550670de0b6b3a764000060035534610000575b612904806100666000396000f3006060604052361561013c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306b2ff471461014157806313af40351461018c57806319362a28146101bf5780633f3935d114610248578063432ced04146102b75780634f39ca59146102eb5780636795dbcd1461032457806369fe0e2d146103c857806379ce9fac146103fd5780638da5cb5b1461045557806390b97fc1146104a457806392698814146105245780639890220b1461055d578063ac4e73f914610584578063ac72c12014610612578063c3a358251461064b578063ddca3f43146106c3578063deb931a2146106e6578063df57b74214610747578063e30bd740146107a8578063eadf976014610862578063ef5454d6146108e7578063f25eb5c114610975578063f6d339e414610984575b610000565b3461000057610172600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a1f565b604051808215151515815260200191505060405180910390f35b34610000576101bd600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a81565b005b346100005761022e60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803560001916906020019091905050610ba2565b604051808215151515815260200191505060405180910390f35b346100005761029d600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610dc9565b604051808215151515815260200191505060405180910390f35b6102d1600480803560001916906020019091905050611035565b604051808215151515815260200191505060405180910390f35b346100005761030a60048080356000191690602001909190505061115f565b604051808215151515815260200191505060405180910390f35b346100005761038660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611378565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576103e3600480803590602001909190505061140d565b604051808215151515815260200191505060405180910390f35b346100005761043b60048080356000191690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506114b4565b604051808215151515815260200191505060405180910390f35b34610000576104626115fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b346100005761050660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611621565b60405180826000191660001916815260200191505060405180910390f35b34610000576105436004808035600019169060200190919050506116b2565b604051808215151515815260200191505060405180910390f35b346100005761056a611715565b604051808215151515815260200191505060405180910390f35b34610000576105f8600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611824565b604051808215151515815260200191505060405180910390f35b3461000057610631600480803560001916906020019091905050611d8b565b604051808215151515815260200191505060405180910390f35b34610000576106ad60048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611dee565b6040518082815260200191505060405180910390f35b34610000576106d0611e83565b6040518082815260200191505060405180910390f35b3461000057610705600480803560001916906020019091905050611e89565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610766600480803560001916906020019091905050611ed2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576107d9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611f1b565b6040518080602001828103825283818151815260200191508051906020019080838360008314610828575b80518252602083111561082857602082019150602081019050602083039250610804565b505050905090810190601f1680156108545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576108cd60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001909190505061200c565b604051808215151515815260200191505060405180910390f35b346100005761095b600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612236565b604051808215151515815260200191505060405180910390f35b3461000057610982612425565b005b3461000057610a0560048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612698565b604051808215151515815260200191505060405180910390f35b60006000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290049050141590505b919050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610add57610b9f565b8073ffffffffffffffffffffffffffffffffffffffff16600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236460405180905060405180910390a380600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b50565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610c1d57610dc1565b82600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b60208310610c705780518252602082019150602081019050602083039250610c4d565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b60208310610cdf5780518252602082019150602081019050602083039250610cbc565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314610d82575b805182526020831115610d8257602082019150602081019050602083039250610d5e565b505050905090810190601f168015610dae5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836040518082805190602001908083835b60208310610e1b5780518252602082019150602081019050602083039250610df8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390206000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610ea45761102f565b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2d57805160ff1916838001178555610f5b565b82800160010185558215610f5b579182015b82811115610f5a578251825591602001919060010190610f3f565b5b509050610f8091905b80821115610f7c576000816000905550600101610f64565b5090565b50503373ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b60208310610fcd5780518252602082019150602081019050602083039250610faa565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600191505b5b50919050565b600081600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561109b57611159565b6003543410156110aa57611158565b3360016000856000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff1683600019167f4963513eca575aba66fdcd25f267aae85958fe6fb97e75fa25d783f1a091a22160405180905060405180910390a3600191505b5b5b50919050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156111da57611372565b6002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061127c57506112b3565b601f0160209004906000526020600020908101906112b291905b808211156112ae576000816000905550600101611296565b5090565b5b5060016000846000191660001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550503373ffffffffffffffffffffffffffffffffffffffff1683600019167fef1961b4d2909dc23643b309bfe5c3e5646842d98c3a58517037ef3871185af360405180905060405180910390a3600191505b5b50919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106113cc57805182526020820191506020810190506020830392506113a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561146b576114af565b816003819055507f6bbc57480a46553fa4d156ce702beef5f3ad66303b0ed1a5d4cb44966c6584c3826040518082815260200191505060405180910390a1600190505b5b919050565b6000823373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561152f576115f4565b8260016000866000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1685600019167f7b97c62130aa09acbbcbf7482630e756592496f1759eaf702f469cf64dfb779460405180905060405180910390a4600191505b5b5092915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106116755780518252602082019150602081019050602083039250611652565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390205490505b92915050565b6000600060016000846000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561177357611821565b7fdef931299fe61d176f949118058530c1f3f539dcb6950b4e372c9b835c33ca073073ffffffffffffffffffffffffffffffffffffffff16316040518082815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051809050600060405180830381858888f19350505050151561181b57610000565b600190505b5b90565b60006000836040518082805190602001908083835b6020831061185c5780518252602082019150602081019050602083039250611839565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390203373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561190157611d83565b846040518082805190602001908083835b602083106119355780518252602082019150602081019050602083039250611912565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390209150600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015611ab4575081600019166002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518082805460018160011615610100020316600290048015611aa15780601f10611a7f576101008083540402835291820191611aa1565b820191906000526020600020905b815481529060010190602001808311611a8d575b5050915050604051809103902060001916145b15611c79576002600060016000856000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f10611b5b5750611b92565b601f016020900490600052602060002090810190611b9191905b80821115611b8d576000816000905550600101611b75565b5090565b5b5060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611c1c5780518252602082019150602081019050602083039250611bf9565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a35b8360016000846000191660001916815260200190815260200160002060010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508373ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611d215780518252602082019150602081019050602083039250611cfe565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f728435a0031f6a04538fcdd24922a7e06bc7bc945db03e83d22122d1bc5f28df60405180905060405180910390a3600192505b5b505092915050565b6000600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b60208310611e425780518252602082019150602081019050602083039250611e1f565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b60035481565b600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b600060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b6020604051908101604052806000815250600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611fff5780601f10611fd457610100808354040283529160200191611fff565b820191906000526020600020905b815481529060010190602001808311611fe257829003601f168201915b505050505090505b919050565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156120875761222e565b82600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b602083106120dd57805182526020820191506020810190506020830392506120ba565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b6020831061214c5780518252602082019150602081019050602083039250612129565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea8660405180806020018281038252838181518152602001915080519060200190808383600083146121ef575b8051825260208311156121ef576020820191506020810190506020830392506121cb565b505050905090810190601f16801561221b5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156122945761241f565b82600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061231d57805160ff191683800117855561234b565b8280016001018555821561234b579182015b8281111561234a57825182559160200191906001019061232f565b5b50905061237091905b8082111561236c576000816000905550600101612354565b5090565b50508173ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b602083106123bd578051825260208201915060208101905060208303925061239a565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600190505b5b92915050565b3373ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156124d65780601f106124b45761010080835404028352918201916124d6565b820191906000526020600020905b8154815290600101906020018083116124c2575b505091505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a360016000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156125b05780601f1061258e5761010080835404028352918201916125b0565b820191906000526020600020905b81548152906001019060200180831161259c575b505091505060405180910390206000191660001916815260200190815260200160002060010160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061265d5750612694565b601f01602090049060005260206000209081019061269391905b8082111561268f576000816000905550600101612677565b5090565b5b505b565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515612713576128d0565b8273ffffffffffffffffffffffffffffffffffffffff16600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b6020831061277f578051825260208201915060208101905060208303925061275c565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b602083106127ee57805182526020820191506020810190506020830392506127cb565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314612891575b8051825260208311156128915760208201915060208101905060208303925061286d565b505050905090810190601f1680156128bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5093925050505600a165627a7a7230582066b2da4773a0f1d81efe071c66b51c46868a871661efd18c0f629353ff4c1f9b0029" }, "0x00a329c0648769a73afac7f9381e08fb43dbea72": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 83372fea580ba8cdef43a85ca3dcf45d05d4d847..642f5b385d7a79f7699e29b17699fd8ea1c308a4 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -38,7 +38,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json index 33fdf4c4f657751bf84dce24aab3868fb51a2b72..6c2f87758fa3fea3fe6d56b6c107600754b60569 100644 --- a/ethcore/res/validator_contract.json +++ b/ethcore/res/validator_contract.json @@ -27,7 +27,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 975d2cbc953cdeafdf94bd74dcbe0ab2541fdd39..2f6d49e7588bf22229c5dda2c4918cfadd2b9cee 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -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; @@ -31,6 +31,7 @@ use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath}; pub use ethstore::ethkey::Signature; +pub use ethstore::{Derivation, IndexDerivation}; /// Type of unlock. #[derive(Clone)] @@ -197,6 +198,20 @@ impl AccountProvider { Ok(account.address) } + /// Generates new derived account based on the existing one + /// If password is not provided, account must be unlocked + /// New account will be created with the same password (if save: true) + pub fn derive_account(&self, address: &Address, password: Option, derivation: Derivation, save: bool) + -> Result + { + let account = self.sstore.account_ref(&address)?; + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok( + if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address } + else { self.sstore.generate_derived(&account, &password, derivation)? } + ) + } + /// Import a new presale wallet. pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result { let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?; @@ -226,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>) -> Result<(), Error> { + /// If not `None` or empty it will also override default account. + pub fn set_new_dapps_addresses(&self, accounts: Option>) -> 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>, Error> { + pub fn new_dapps_addresses(&self) -> Result>, 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
, default: Address) -> Vec
{ + 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, 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 { + 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, Error> { Ok(self.dapps_settings.read().recent_dapps()) @@ -257,41 +335,74 @@ impl AccountProvider { Ok(()) } - /// Gets addresses visile for dapp. - pub fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { - let dapps = self.dapps_settings.read(); + /// Gets addresses visible for given dapp. + pub fn dapp_addresses(&self, dapp: DappId) -> Result, 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 { - self.dapps_addresses(dapp)? + pub fn dapp_default_address(&self, dapp: DappId) -> Result { + 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
) -> 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(()) } - /// Removes addresses that are neither accounts nor in address book. - fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { - let valid = self.addresses_info().into_iter() + /// 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>) -> 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, Error> { + Ok(self.addresses_info().into_iter() .map(|(address, _)| address) .chain(self.accounts()?) - .collect::>(); + .collect()) + } + + /// Removes addresses that are neither accounts nor in address book. + fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { + let valid = self.valid_addresses()?; Ok(addresses.into_iter() .filter(|a| valid.contains(&a)) @@ -458,6 +569,15 @@ impl AccountProvider { Ok(self.sstore.sign(&account, &password, &message)?) } + /// Signs message using the derived secret. If password is not provided the account must be unlocked. + pub fn sign_derived(&self, address: &Address, password: Option, derivation: Derivation, message: Message) + -> Result + { + let account = self.sstore.account_ref(address)?; + let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?; + Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?) + } + /// Signs given message with supplied token. Returns a token to use in next signing within this session. pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> { let account = self.sstore.account_ref(&address)?; @@ -593,7 +713,8 @@ mod tests { use super::{AccountProvider, Unlock, DappId}; use std::time::Instant; use ethstore::ethkey::{Generator, Random}; - use ethstore::StoreAccountRef; + use ethstore::{StoreAccountRef, Derivation}; + use util::H256; #[test] fn unlock_account_temp() { @@ -606,6 +727,75 @@ mod tests { assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } + #[test] + fn derived_account_nosave() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(999)), + false, + ).expect("Derivation should not fail"); + + assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(), + "There should be an error because account is not supposed to be saved"); + } + + #[test] + fn derived_account_save() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(999)), + true, + ).expect("Derivation should not fail"); + + assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(), + "There should be an error because password is invalid"); + + assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(), + "Should be ok because account is saved and password is valid"); + } + + #[test] + fn derived_account_sign() { + let kp = Random.generate().unwrap(); + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "base").is_ok()); + assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok()); + + let derived_addr = ap.derive_account( + &kp.address(), + None, + Derivation::SoftHash(H256::from(1999)), + true, + ).expect("Derivation should not fail"); + ap.unlock_account_permanently(derived_addr, "base".into()) + .expect("Should be ok because account is saved and password is valid"); + + let msg = Default::default(); + let signed_msg1 = ap.sign(derived_addr, None, msg) + .expect("Signing with existing unlocked account should not fail"); + let signed_msg2 = ap.sign_derived( + &kp.address(), + None, + Derivation::SoftHash(H256::from(1999)), + msg, + ).expect("Derived signing with existing unlocked account should not fail"); + + assert_eq!(signed_msg1, signed_msg2, + "Signed messages should match"); + } + #[test] fn unlock_account_perm() { let kp = Random.generate().unwrap(); @@ -649,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 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()); - // 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()]); + 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); } } diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index e4bd7e1b9568433d92c541ac277286d64fd7a55a..72bc04da65c6516878ef9691602212404e285920 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -92,13 +92,16 @@ impl AddressBook { #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct DappsSettings { /// A list of visible accounts - pub accounts: Vec
, + pub accounts: Option>, + /// Default account + pub default: Option
, } impl From 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 for DappsSettings { impl From 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 for JsonSettings { /// Dapps user settings #[derive(Debug, Clone, Eq, PartialEq)] pub enum NewDappsPolicy { - AllAccounts, + AllAccounts { + default: Address, + }, Whitelist(Vec
), } impl From 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 for NewDappsPolicy { impl From 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
) { + pub fn set_accounts(&mut self, id: DappId, accounts: Option>) { { 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()])); diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 9e3e86f628bcf776521c35bfcffa001ca0a5b316..3626fdd3a629cce56946c2e8f85d06cf8cc9890f 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -91,7 +91,7 @@ pub struct ExecutedBlock { uncles: Vec
, receipts: Vec, transactions_set: HashSet, - state: State, + state: State, traces: Option>>, } @@ -106,7 +106,7 @@ pub struct BlockRefMut<'a> { /// Transaction receipts. pub receipts: &'a [Receipt], /// State. - pub state: &'a mut State, + pub state: &'a mut State, /// Traces. pub traces: &'a Option>>, } @@ -122,14 +122,14 @@ pub struct BlockRef<'a> { /// Transaction receipts. pub receipts: &'a [Receipt], /// State. - pub state: &'a State, + pub state: &'a State, /// Traces. pub traces: &'a Option>>, } impl ExecutedBlock { /// Create a new block from the given `state`. - fn new(state: State, tracing: bool) -> ExecutedBlock { + fn new(state: State, tracing: bool) -> ExecutedBlock { ExecutedBlock { header: Default::default(), transactions: Default::default(), @@ -184,7 +184,7 @@ pub trait IsBlock { fn header(&self) -> &Header { &self.block().header } /// Get the final state associated with this object's block. - fn state(&self) -> &State { &self.block().state } + fn state(&self) -> &State { &self.block().state } /// Get all information on transactions in this block. fn transactions(&self) -> &[SignedTransaction] { &self.block().transactions } @@ -228,7 +228,7 @@ pub struct ClosedBlock { block: ExecutedBlock, uncle_bytes: Bytes, last_hashes: Arc, - unclosed_state: State, + unclosed_state: State, } /// Just like `ClosedBlock` except that we can't reopen it and it's faster. @@ -540,7 +540,8 @@ pub fn enact( { if ::log::max_log_level() >= ::log::LogLevel::Trace { let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(), factories.clone())?; - trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n", header.number(), s.root(), header.author(), s.balance(&header.author())); + trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n", + header.number(), s.root(), header.author(), s.balance(&header.author())?); } } diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index c4031f954c8fbb475836790740a4e0959e4eec97..8986dc6b881ca63ce2056f8b7eb8d47b8cbb908e 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -191,7 +191,7 @@ pub struct BlockChain { blocks_blooms: RwLock>, block_receipts: RwLock>, - db: Arc, + db: Arc, cache_man: Mutex>, @@ -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) -> BlockChain { + pub fn new(config: Config, genesis: &[u8], db: Arc) -> 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 { + pub fn db(&self) -> &Arc { &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 { - Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap()) + fn new_db() -> Arc { + Arc::new(::util::kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0))) } - fn new_chain(genesis: &[u8], db: Arc) -> BlockChain { + fn new_chain(genesis: &[u8], db: Arc) -> 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, bc: &BlockChain, bytes: &[u8], receipts: Vec) -> ImportRoute { + fn insert_block(db: &Arc, bc: &BlockChain, bytes: &[u8], receipts: Vec) -> 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(); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index d85685f3fa6562a07181aab6cba17266aa6b0d0b..edd585551c76a71ce5027dd6dc32ab2993c98e08 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -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}; @@ -25,7 +24,7 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, MutexGuard, Hashable}; -use util::{journaldb, TrieFactory, Trie}; +use util::{journaldb, DBValue, TrieFactory, Trie}; use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::trie::TrieSpec; use util::kvdb::*; @@ -35,7 +34,7 @@ use io::*; use views::BlockView; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; use header::BlockNumber; -use state::{State, CleanupMode}; +use state::{self, State, CleanupMode}; use spec::Spec; use basic_types::Seal; use engines::Engine; @@ -135,7 +134,7 @@ pub struct Client { engine: Arc, config: ClientConfig, pruning: journaldb::Algorithm, - db: RwLock>, + db: RwLock>, state_db: Mutex, block_queue: BlockQueue, report: RwLock, @@ -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, miner: Arc, message_channel: IoChannel, - db_config: &DatabaseConfig, ) -> Result, 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)?; } @@ -256,7 +253,7 @@ impl Client { if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { trace!(target: "client", "Found registrar at {}", reg_addr); let weak = Arc::downgrade(&client); - let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); + let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d))); *client.registrar.lock() = Some(registrar); } Ok(client) @@ -311,18 +308,24 @@ impl Client { } /// The env info as of the best block. - fn latest_env_info(&self) -> EnvInfo { - let header = self.best_block_header(); - - EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: self.build_last_hashes(header.hash()), - gas_used: U256::default(), - gas_limit: header.gas_limit(), - } + pub fn latest_env_info(&self) -> EnvInfo { + self.env_info(BlockId::Latest).expect("Best block header always stored; qed") + } + + /// The env info as of a given block. + /// returns `None` if the block unknown. + pub fn env_info(&self, id: BlockId) -> Option { + self.block_header(id).map(|header| { + EnvInfo { + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + gas_limit: header.gas_limit(), + } + }) } fn build_last_hashes(&self, parent_hash: H256) -> Arc { @@ -530,7 +533,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 +557,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 +606,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(); @@ -659,7 +662,7 @@ impl Client { /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state or the block /// is unknown. - pub fn state_at(&self, id: BlockId) -> Option { + pub fn state_at(&self, id: BlockId) -> Option> { // fast path for latest state. match id.clone() { BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), @@ -689,7 +692,7 @@ impl Client { /// /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. - pub fn state_at_beginning(&self, id: BlockId) -> Option { + pub fn state_at_beginning(&self, id: BlockId) -> Option> { // fast path for latest state. match id { BlockId::Pending => self.state_at(BlockId::Latest), @@ -701,7 +704,7 @@ impl Client { } /// Get a copy of the best block's state. - pub fn state(&self) -> State { + pub fn state(&self) -> State { let header = self.best_block_header(); State::from_existing( self.state_db.lock().boxed_clone_canon(&header.hash()), @@ -877,54 +880,45 @@ impl snapshot::DatabaseRestore for Client { impl BlockChainClient for Client { fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { - let header = self.block_header(block).ok_or(CallError::StatePruned)?; - let last_hashes = self.build_last_hashes(header.parent_hash()); - let env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::zero(), - gas_limit: U256::max_value(), - }; + let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; + env_info.gas_limit = U256::max_value(); + // that's just a copy of the state. let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; let sender = t.sender(); - let balance = state.balance(&sender); + let balance = state.balance(&sender).map_err(|_| CallError::StateCorrupt)?; let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); + state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty) + .map_err(|_| CallError::StateCorrupt)?; } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)?; // TODO gav move this into Executive. - ret.state_diff = original_state.map(|original| state.diff_from(original)); + if let Some(original) = original_state { + ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?); + } Ok(ret) } fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result { const UPPER_CEILING: u64 = 1_000_000_000_000u64; - let header = self.block_header(block).ok_or(CallError::StatePruned)?; - let last_hashes = self.build_last_hashes(header.parent_hash()); - let env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::zero(), - gas_limit: UPPER_CEILING.into(), + let (mut upper, env_info) = { + let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; + let initial_upper = env_info.gas_limit; + env_info.gas_limit = UPPER_CEILING.into(); + (initial_upper, env_info) }; + // that's just a copy of the state. let original_state = self.state_at(block).ok_or(CallError::StatePruned)?; let sender = t.sender(); - let balance = original_state.balance(&sender); + let balance = original_state.balance(&sender).map_err(ExecutionError::from)?; let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false }; let cond = |gas| { @@ -936,27 +930,28 @@ impl BlockChainClient for Client { let needed_balance = tx.value + tx.gas * tx.gas_price; if balance < needed_balance { // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); + state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty) + .map_err(ExecutionError::from)?; } - Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm) + Ok(Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm) .transact(&tx, options.clone()) .map(|r| r.exception.is_none()) - .unwrap_or(false) + .unwrap_or(false)) }; - let mut upper = header.gas_limit(); - if !cond(upper) { + if !cond(upper)? { // impossible at block gas limit - try `UPPER_CEILING` instead. // TODO: consider raising limit by powers of two. upper = UPPER_CEILING.into(); - if !cond(upper) { + if !cond(upper)? { trace!(target: "estimate_gas", "estimate_gas failed with {}", upper); - return Err(CallError::Execution(ExecutionError::Internal)) + let err = ExecutionError::Internal(format!("Requires higher than upper limit of {}", upper)); + return Err(err.into()) } } let lower = t.gas_required(&self.engine.schedule(&env_info)).into(); - if cond(lower) { + if cond(lower)? { trace!(target: "estimate_gas", "estimate_gas succeeded with {}", lower); return Ok(lower) } @@ -964,28 +959,30 @@ impl BlockChainClient for Client { /// Find transition point between `lower` and `upper` where `cond` changes from `false` to `true`. /// Returns the lowest value between `lower` and `upper` for which `cond` returns true. /// We assert: `cond(lower) = false`, `cond(upper) = true` - fn binary_chop(mut lower: U256, mut upper: U256, mut cond: F) -> U256 where F: FnMut(U256) -> bool { + fn binary_chop(mut lower: U256, mut upper: U256, mut cond: F) -> Result + where F: FnMut(U256) -> Result + { while upper - lower > 1.into() { let mid = (lower + upper) / 2.into(); trace!(target: "estimate_gas", "{} .. {} .. {}", lower, mid, upper); - let c = cond(mid); + let c = cond(mid)?; match c { true => upper = mid, false => lower = mid, }; trace!(target: "estimate_gas", "{} => {} .. {}", c, lower, upper); } - upper + Ok(upper) } // binary chop to non-excepting call with gas somewhere between 21000 and block gas limit trace!(target: "estimate_gas", "estimate_gas chopping {} .. {}", lower, upper); - Ok(binary_chop(lower, upper, cond)) + binary_chop(lower, upper, cond) } fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?; - let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; + let mut env_info = self.env_info(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let body = self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let mut state = self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let mut txs = body.transactions(); @@ -995,31 +992,20 @@ impl BlockChainClient for Client { } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let last_hashes = self.build_last_hashes(header.hash()); - let mut env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::default(), - gas_limit: header.gas_limit(), - }; const PROOF: &'static str = "Transactions fetched from blockchain; blockchain transactions are valid; qed"; let rest = txs.split_off(address.index); for t in txs { let t = SignedTransaction::new(t).expect(PROOF); - match Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&t, Default::default()) { - Ok(x) => { env_info.gas_used = env_info.gas_used + x.gas_used; } - Err(ee) => { return Err(CallError::Execution(ee)) } - } + let x = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&t, Default::default())?; + env_info.gas_used = env_info.gas_used + x.gas_used; } let first = rest.into_iter().next().expect("We split off < `address.index`; Length is checked earlier; qed"); let t = SignedTransaction::new(first).expect(PROOF); let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; let mut ret = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&t, options)?; - ret.state_diff = original_state.map(|original| state.diff_from(original)); - + if let Some(original) = original_state { + ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?) + } Ok(ret) } @@ -1111,11 +1097,11 @@ impl BlockChainClient for Client { } fn nonce(&self, address: &Address, id: BlockId) -> Option { - self.state_at(id).map(|s| s.nonce(address)) + self.state_at(id).and_then(|s| s.nonce(address).ok()) } fn storage_root(&self, address: &Address, id: BlockId) -> Option { - self.state_at(id).and_then(|s| s.storage_root(address)) + self.state_at(id).and_then(|s| s.storage_root(address).ok()).and_then(|x| x) } fn block_hash(&self, id: BlockId) -> Option { @@ -1124,15 +1110,15 @@ impl BlockChainClient for Client { } fn code(&self, address: &Address, id: BlockId) -> Option> { - self.state_at(id).map(|s| s.code(address).map(|c| (*c).clone())) + self.state_at(id).and_then(|s| s.code(address).ok()).map(|c| c.map(|c| (&*c).clone())) } fn balance(&self, address: &Address, id: BlockId) -> Option { - self.state_at(id).map(|s| s.balance(address)) + self.state_at(id).and_then(|s| s.balance(address).ok()) } fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { - self.state_at(id).map(|s| s.storage_at(address, position)) + self.state_at(id).and_then(|s| s.storage_at(address, position).ok()) } fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option> { @@ -1185,7 +1171,7 @@ impl BlockChainClient for Client { }; let root = match state.storage_root(account) { - Some(root) => root, + Ok(Some(root)) => root, _ => return None, }; @@ -1442,7 +1428,7 @@ impl BlockChainClient for Client { } } - fn call_contract(&self, address: Address, data: Bytes) -> Result { + fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result { let from = Address::default(); let transaction = Transaction { nonce: self.latest_nonce(&from), @@ -1453,7 +1439,7 @@ impl BlockChainClient for Client { data: data, }.fake_sign(from); - self.call(&transaction, BlockId::Latest, Default::default()) + self.call(&transaction, block_id, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output @@ -1617,6 +1603,25 @@ impl ::client::ProvingBlockChainClient for Client { .and_then(|x| x) .unwrap_or_else(Vec::new) } + + fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option> { + let (state, env_info) = match (self.state_at(id), self.env_info(id)) { + (Some(s), Some(e)) => (s, e), + _ => return None, + }; + let mut jdb = self.state_db.lock().journal_db().boxed_clone(); + let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); + + let mut state = state.replace_backend(backend); + let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false }; + let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options); + + match res { + Err(ExecutionError::Internal(_)) => return None, + _ => return Some(state.drop().1.extract_proof()), + } + } + } impl Drop for Client { @@ -1691,7 +1696,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); }); diff --git a/ethcore/src/client/registry.rs b/ethcore/src/client/registry.rs index 9e60a1251d051a8acbb49f597116fb5733bdf81d..c8f750576fd5832b05710914eec2bdf4b20de197 100644 --- a/ethcore/src/client/registry.rs +++ b/ethcore/src/client/registry.rs @@ -1,264 +1,338 @@ // Autogenerated from JSON contract definition using Rust contract convertor. - +// Command line: --name=Registry --jsonabi=/Users/gav/registry.abi +#![allow(unused_imports)] use std::string::String; use std::result::Result; use std::fmt; use {util, ethabi}; -use util::FixedHash; -use util::Uint; +use util::{FixedHash, Uint}; pub struct Registry { contract: ethabi::Contract, pub address: util::Address, - do_call: Box) -> Result, String> + Send + 'static>, + do_call: Box) -> Result, String> + Send + Sync + 'static>, } impl Registry { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + pub fn new(address: util::Address, do_call: F) -> Self + where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { Registry { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"confirmReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"bytes32\"}],\"name\":\"set\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"drop\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserved\",\"outputs\":[{\"name\":\"reserved\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"drain\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"proposeReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"reverse\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setUint\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"removeReverse\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Drained\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Reserved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"Transferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Dropped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"key\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"plainKey\",\"type\":\"string\"}],\"name\":\"DataChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"current\",\"type\":\"address\"}],\"name\":\"NewOwner\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"_data\",\"type\":\"address\"}],\"name\":\"canReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"bytes32\"}],\"name\":\"setData\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"confirmReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":true,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"drop\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getData\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserved\",\"outputs\":[{\"name\":\"reserved\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"drain\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"proposeReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"hasReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_data\",\"type\":\"address\"}],\"name\":\"reverse\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setUint\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"confirmReverseAs\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"removeReverse\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), address: address, do_call: Box::new(do_call), } } fn as_string(e: T) -> String { format!("{:?}", e) } + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn can_reverse(&self, _data: &util::Address) -> Result + { + let call = self.contract.function("canReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_data.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + } + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_owner(&self, _new: &util::Address) -> Result<(), String> { + pub fn set_owner(&self, _new: &util::Address) -> Result<(), String> + { let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::Address(_new.clone().0)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - Ok(()) + Ok(()) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn confirm_reverse(&self, _name: &str) -> Result { - let call = self.contract.function("confirmReverse".into()).map_err(Self::as_string)?; + pub fn set_data(&self, _name: &util::H256, _key: &str, _value: &util::H256) -> Result + { + let call = self.contract.function("setData".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::String(_name.to_owned())] + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::FixedBytes(_value.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reserve(&self, _name: &util::H256) -> Result { - let call = self.contract.function("reserve".into()).map_err(Self::as_string)?; + pub fn confirm_reverse(&self, _name: &str) -> Result + { + let call = self.contract.function("confirmReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + vec![ethabi::Token::String(_name.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"}` #[allow(dead_code)] - pub fn set(&self, _name: &util::H256, _key: &str, _value: &util::H256) -> Result { - let call = self.contract.function("set".into()).map_err(Self::as_string)?; + pub fn reserve(&self, _name: &util::H256) -> Result + { + let call = self.contract.function("reserve".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::FixedBytes(_value.as_ref().to_owned())] + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn drop(&self, _name: &util::H256) -> Result { + pub fn drop(&self, _name: &util::H256) -> Result + { let call = self.contract.function("drop".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_address(&self, _name: &util::H256, _key: &str) -> Result { + pub fn get_address(&self, _name: &util::H256, _key: &str) -> Result + { let call = self.contract.function("getAddress".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_fee(&self, _amount: util::U256) -> Result<(), String> { + pub fn set_fee(&self, _amount: util::U256) -> Result + { let call = self.contract.function("setFee".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; _amount.to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; - call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - - Ok(()) + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn transfer(&self, _name: &util::H256, _to: &util::Address) -> Result { + pub fn transfer(&self, _name: &util::H256, _to: &util::Address) -> Result + { let call = self.contract.function("transfer".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn owner(&self) -> Result { + pub fn owner(&self) -> Result + { let call = self.contract.function("owner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_data(&self, _name: &util::H256, _key: &str) -> Result + { + let call = self.contract.function("getData".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reserved(&self, _name: &util::H256) -> Result { + pub fn reserved(&self, _name: &util::H256) -> Result + { let call = self.contract.function("reserved".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn drain(&self) -> Result<(), String> { + pub fn drain(&self) -> Result + { let call = self.contract.function("drain".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; - call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - - Ok(()) + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn propose_reverse(&self, _name: &str, _who: &util::Address) -> Result { + pub fn propose_reverse(&self, _name: &str, _who: &util::Address) -> Result + { let call = self.contract.function("proposeReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::String(_name.to_owned()), ethabi::Token::Address(_who.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_uint(&self, _name: &util::H256, _key: &str) -> Result { - let call = self.contract.function("getUint".into()).map_err(Self::as_string)?; + pub fn has_reverse(&self, _name: &util::H256) -> Result + { + let call = self.contract.function("hasReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get(&self, _name: &util::H256, _key: &str) -> Result { - let call = self.contract.function("get".into()).map_err(Self::as_string)?; + pub fn get_uint(&self, _name: &util::H256, _key: &str) -> Result + { + let call = self.contract.function("getUint".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn fee(&self) -> Result { + pub fn fee(&self) -> Result + { let call = self.contract.function("fee".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_owner(&self, _name: &util::H256) -> Result { + pub fn get_owner(&self, _name: &util::H256) -> Result + { let call = self.contract.function("getOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_reverse(&self, _name: &util::H256) -> Result + { + let call = self.contract.function("getReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reverse(&self, _1: &util::Address) -> Result { + pub fn reverse(&self, _data: &util::Address) -> Result + { let call = self.contract.function("reverse".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Address(_1.clone().0)] + vec![ethabi::Token::Address(_data.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_string().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_string().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_uint(&self, _name: &util::H256, _key: &str, _value: util::U256) -> Result { + pub fn set_uint(&self, _name: &util::H256, _key: &str, _value: util::U256) -> Result + { let call = self.contract.function("setUint".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn confirm_reverse_as(&self, _name: &str, _who: &util::Address) -> Result + { + let call = self.contract.function("confirmReverseAs".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::String(_name.to_owned()), ethabi::Token::Address(_who.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn remove_reverse(&self) -> Result<(), String> { + pub fn remove_reverse(&self) -> Result<(), String> + { let call = self.contract.function("removeReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - Ok(()) + Ok(()) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_address(&self, _name: &util::H256, _key: &str, _value: &util::Address) -> Result { + pub fn set_address(&self, _name: &util::H256, _key: &str, _value: &util::Address) -> Result + { let call = self.contract.function("setAddress".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Address(_value.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } -} \ No newline at end of file +} + diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index dc9cb5944577cf0bea13cb201dd48d8223b1b111..6a613fa7da925f1ffc2d6dafac81156245f1a461 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -731,7 +731,7 @@ impl BlockChainClient for TestBlockChainClient { } } - fn call_contract(&self, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } + fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { @@ -765,6 +765,10 @@ impl ProvingBlockChainClient for TestBlockChainClient { fn code_by_hash(&self, _: H256, _: BlockId) -> Bytes { Vec::new() } + + fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option> { + None + } } impl EngineClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index dce708b3a8d30ffe95dbb51b2e5802f20d45da4e..e31712852146ec17830be487cb57cd40e8d3a05f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -16,7 +16,7 @@ use std::collections::BTreeMap; use util::{U256, Address, H256, H2048, Bytes, Itertools}; -use util::stats::Histogram; +use util::hashdb::DBValue; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; use block::{OpenBlock, SealedBlock}; @@ -212,38 +212,24 @@ pub trait BlockChainClient : Sync + Send { fn ready_transactions(&self) -> Vec; /// Sorted list of transaction gas prices from at least last sample_size blocks. - fn gas_price_corpus(&self, sample_size: usize) -> Vec { + fn gas_price_corpus(&self, sample_size: usize) -> ::stats::Corpus { let mut h = self.chain_info().best_block_hash; let mut corpus = Vec::new(); while corpus.is_empty() { for _ in 0..sample_size { - let block = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); - let header = block.header_view(); - if header.number() == 0 { - corpus.sort(); - return corpus; + let block = match self.block(BlockId::Hash(h)) { + Some(block) => block, + None => return corpus.into(), + }; + + if block.number() == 0 { + return corpus.into(); } block.transaction_views().iter().foreach(|t| corpus.push(t.gas_price())); - h = header.parent_hash().clone(); + h = block.parent_hash().clone(); } } - corpus.sort(); - corpus - } - - /// Calculate median gas price from recent blocks if they have any transactions. - fn gas_price_median(&self, sample_size: usize) -> Option { - let corpus = self.gas_price_corpus(sample_size); - corpus.get(corpus.len() / 2).cloned() - } - - /// Get the gas price distribution based on recent blocks if they have any transactions. - fn gas_price_histogram(&self, sample_size: usize, bucket_number: usize) -> Option { - let raw_corpus = self.gas_price_corpus(sample_size); - let raw_len = raw_corpus.len(); - // Throw out outliers. - let (corpus, _) = raw_corpus.split_at(raw_len - raw_len / 40); - Histogram::new(corpus, bucket_number) + corpus.into() } /// Get the preferred network ID to sign on @@ -269,7 +255,7 @@ pub trait BlockChainClient : Sync + Send { fn pruning_info(&self) -> PruningInfo; /// Like `call`, but with various defaults. Designed to be used for calling contracts. - fn call_contract(&self, address: Address, data: Bytes) -> Result; + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; /// Import a transaction: used for misbehaviour reporting. fn transact_contract(&self, address: Address, data: Bytes) -> Result; @@ -336,4 +322,7 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Get code by address hash. fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes; + + /// Prove execution of a transaction at the given block. + fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option>; } diff --git a/ethcore/src/db.rs b/ethcore/src/db.rs index 8f55f802500aef99884bb8435f1a613728153488..4e8da714d43d246359a8b41de4a535ec2f39935d 100644 --- a/ethcore/src/db.rs +++ b/ethcore/src/db.rs @@ -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 = Some(2); pub const COL_EXTRA: Option = Some(3); /// Column for Traces pub const COL_TRACE: Option = Some(4); -/// Column for Traces +/// Column for the empty accounts bloom filter. pub const COL_ACCOUNT_BLOOM: Option = Some(5); +/// Column for general information from the local node which can persist. +pub const COL_NODE_INFO: Option = Some(6); /// Number of columns in DB -pub const NUM_COLUMNS: Option = Some(6); +pub const NUM_COLUMNS: Option = Some(7); /// Modes for updating caches. #[derive(Clone, Copy)] @@ -212,7 +214,7 @@ impl Writable for DBTransaction { } } -impl Readable for Database { +impl Readable for KVDB { fn read(&self, col: Option, key: &Key) -> Option where T: rlp::Decodable, R: Deref { let result = self.get(col, &key.key()); diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9c69ce6adb37def5429e02769215b776581f3d94..03b5d785fa9cd2ec90b60bd4372ef90bff5d329d 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -47,6 +47,8 @@ pub struct AuthorityRoundParams { pub step_duration: Duration, /// Block reward. pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, /// Starting step, pub start_step: Option, /// Valid validators. @@ -60,6 +62,7 @@ impl From for AuthorityRoundParams { step_duration: Duration::from_secs(p.step_duration.into()), validators: p.validators, block_reward: p.block_reward.map_or_else(U256::zero, Into::into), + registrar: p.registrar.map_or_else(Address::new, Into::into), start_step: p.start_step.map(Into::into), } } @@ -71,6 +74,7 @@ pub struct AuthorityRound { params: CommonParams, gas_limit_bound_divisor: U256, block_reward: U256, + registrar: Address, step_duration: Duration, builtins: BTreeMap, transition_service: IoService<()>, @@ -79,6 +83,8 @@ pub struct AuthorityRound { client: RwLock>>, signer: EngineSigner, validators: Box, + /// Is this Engine just for testing (prevents step calibration). + calibrate_step: bool, } fn header_step(header: &Header) -> Result { @@ -109,6 +115,7 @@ impl AuthorityRound { params: params, gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, block_reward: our_params.block_reward, + registrar: our_params.registrar, step_duration: our_params.step_duration, builtins: builtins, transition_service: IoService::<()>::start()?, @@ -117,6 +124,7 @@ impl AuthorityRound { client: RwLock::new(None), signer: Default::default(), validators: new_validator_set(our_params.validators), + calibrate_step: our_params.start_step.is_none(), }); // Do not initialize timeouts for tests. if should_timeout { @@ -126,6 +134,12 @@ impl AuthorityRound { Ok(engine) } + fn calibrate_step(&self) { + if self.calibrate_step { + self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst); + } + } + fn remaining_step_duration(&self) -> Duration { let now = unix_now(); let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); @@ -136,12 +150,22 @@ impl AuthorityRound { } } - fn step_proposer(&self, step: usize) -> Address { - self.validators.get(step) + fn step_proposer(&self, bh: &H256, step: usize) -> Address { + self.validators.get(bh, step) } - fn is_step_proposer(&self, step: usize, address: &Address) -> bool { - self.step_proposer(step) == *address + fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool { + self.step_proposer(bh, step) == *address + } + + fn is_future_step(&self, step: usize) -> bool { + if step > self.step.load(AtomicOrdering::SeqCst) + 1 { + // Make absolutely sure that the step is correct. + self.calibrate_step(); + step > self.step.load(AtomicOrdering::SeqCst) + 1 + } else { + false + } } } @@ -176,11 +200,16 @@ impl IoHandler<()> for TransitionHandler { impl Engine for AuthorityRound { fn name(&self) -> &str { "AuthorityRound" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Two fields - consensus step and the corresponding proposer signature. fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } + + fn additional_params(&self) -> HashMap { hash_map!["registrar".to_owned() => self.registrar.hex()] } + fn builtins(&self) -> &BTreeMap { &self.builtins } fn step(&self) { @@ -220,8 +249,8 @@ impl Engine for AuthorityRound { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. @@ -232,7 +261,7 @@ impl Engine for AuthorityRound { if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); let step = self.step.load(AtomicOrdering::SeqCst); - if self.is_step_proposer(step, header.author()) { + if self.is_step_proposer(header.parent_hash(), step, header.author()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) { trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); @@ -250,10 +279,12 @@ impl Engine for AuthorityRound { fn on_close_block(&self, block: &mut ExecutedBlock) { let fields = block.fields_mut(); // Bestow block reward - fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty); + let res = fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty) + .map_err(::error::Error::from) + .and_then(|_| fields.state.commit()); // Commit state so that we can actually figure out the state root. - if let Err(e) = fields.state.commit() { - warn!("Encountered error on state commit: {}", e); + if let Err(e) = res { + warn!("Encountered error on closing block: {}", e); } } @@ -269,32 +300,32 @@ impl Engine for AuthorityRound { } } - /// Check if the signature belongs to the correct proposer. - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let header_step = header_step(header)?; - // Give one step slack if step is lagging, double vote is still not possible. - if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 { - let proposer_signature = header_signature(header)?; - let correct_proposer = self.step_proposer(header_step); - if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) - } else { - trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step); - Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? - } - } else { + } + + /// Do the validator and gas limit validation. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let step = header_step(header)?; + // Give one step slack if step is lagging, double vote is still not possible. + if self.is_future_step(step) { trace!(target: "engine", "verify_block_unordered: block from the future"); self.validators.report_benign(header.author()); Err(BlockError::InvalidSeal)? + } else { + let proposer_signature = header_signature(header)?; + let correct_proposer = self.step_proposer(header.parent_hash(), step); + if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { + trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step); + Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? + } } - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } - let step = header_step(header)?; // Check if parent is from a previous step. if step == header_step(parent)? { trace!(target: "engine", "Multiple blocks proposed for step {}.", step); @@ -382,7 +413,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } @@ -420,10 +451,14 @@ mod tests { #[test] fn proposer_switching() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); - + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -432,17 +467,22 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); } #[test] fn rejects_future_block() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -451,8 +491,8 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 5d1abd064f43510a31b2d5ae0775bd84aab54477..34b89b2d65bd3b1d954e0861951090d8e0039682 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -103,15 +103,15 @@ impl Engine for BasicAuthority { }); } - fn is_sealer(&self, author: &Address) -> Option { - Some(self.validators.contains(author)) + fn seals_internally(&self) -> Option { + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { let header = block.header(); let author = header.author(); - if self.validators.contains(author) { + if self.validators.contains(header.parent_hash(), author) { // account should be pernamently unlocked, otherwise sealing will fail if let Ok(signature) = self.signer.sign(header.bare_hash()) { return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]); @@ -133,20 +133,20 @@ impl Engine for BasicAuthority { Ok(()) } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - use rlp::{UntrustedRlp, View}; + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + Ok(()) + } - // check the signature is legit. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + use rlp::{UntrustedRlp, View}; + // Check if the signature belongs to a validator, can depend on parent state. let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); - if !self.validators.contains(&signer) { + if !self.validators.contains(header.parent_hash(), &signer) { return Err(BlockError::InvalidSeal)?; } - Ok(()) - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // we should not calculate difficulty for genesis blocks + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } @@ -239,7 +239,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } @@ -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()); } } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index c709398e37418f7e86c7474be1bfd1a94b9f2610..45bede9f4516b54fbd32909acdae0d082041b614 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use util::Address; +use util::{Address, HashMap}; use builtin::Builtin; use engines::{Engine, Seal}; use env_info::EnvInfo; @@ -26,14 +26,16 @@ use block::ExecutedBlock; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { params: CommonParams, + registrar: Address, builtins: BTreeMap, } impl InstantSeal { /// Returns new instance of InstantSeal with default VM Factory - pub fn new(params: CommonParams, builtins: BTreeMap) -> Self { + pub fn new(params: CommonParams, registrar: Address, builtins: BTreeMap) -> Self { InstantSeal { params: params, + registrar: registrar, builtins: builtins, } } @@ -48,6 +50,10 @@ impl Engine for InstantSeal { &self.params } + fn additional_params(&self) -> HashMap { + hash_map!["registrar".to_owned() => self.registrar.hex()] + } + fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -56,7 +62,7 @@ impl Engine for InstantSeal { Schedule::new_post_eip150(usize::max_value(), true, true, true) } - fn is_sealer(&self, _author: &Address) -> Option { Some(true) } + fn seals_internally(&self) -> Option { Some(true) } fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::Regular(Vec::new()) @@ -76,9 +82,9 @@ mod tests { fn instant_can_seal() { let spec = Spec::new_instant(); let engine = &*spec.engine; - let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); + let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 489521bb8ce1c3c11241fb0495cdd5fc41e65cd7..9db59779ae20afdc62e61e4d9924c6a6f9294953 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -130,11 +130,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 { None } - /// Checks if default address is able to seal. - fn is_default_sealer(&self) -> Option { 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 { None } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. @@ -169,7 +168,9 @@ pub trait Engine : Sync + Send { } /// The network ID that transactions should be signed with. - fn signing_network_id(&self, _env_info: &EnvInfo) -> Option { None } + fn signing_network_id(&self, _env_info: &EnvInfo) -> Option { + Some(self.params().chain_id) + } /// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods /// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bc26dc921b9f9399e698bdec0fa8136812c8532d..d67c57d60470e6e1caf5e1653f33ef3717843fcf 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -78,6 +78,7 @@ pub struct Tendermint { step_service: IoService, client: RwLock>>, block_reward: U256, + registrar: Address, /// Blockchain height. height: AtomicUsize, /// Consensus view. @@ -94,6 +95,8 @@ pub struct Tendermint { last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock>, + /// Hash of the proposal parent block. + proposal_parent: RwLock, /// Set used to determine the current validators. validators: Box, } @@ -109,14 +112,16 @@ impl Tendermint { client: RwLock::new(None), step_service: IoService::::start()?, block_reward: our_params.block_reward, + registrar: our_params.registrar, height: AtomicUsize::new(1), view: AtomicUsize::new(0), step: RwLock::new(Step::Propose), - votes: VoteCollector::default(), + votes: Default::default(), signer: Default::default(), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), proposal: RwLock::new(None), + proposal_parent: Default::default(), validators: new_validator_set(our_params.validators), }); let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak, Box::new(our_params.timeouts)); @@ -151,12 +156,12 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { let h = self.height.load(AtomicOrdering::SeqCst); let r = self.view.load(AtomicOrdering::SeqCst); - let s = self.step.read(); - let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash); + let s = *self.step.read(); + let vote_info = message_info_rlp(&VoteStep::new(h, r, s), block_hash); match self.signer.sign(vote_info.sha3()).map(Into::into) { Ok(signature) => { let message_rlp = message_full_rlp(&signature, &vote_info); - let message = ConsensusMessage::new(signature, h, r, *s, block_hash); + let message = ConsensusMessage::new(signature, h, r, s, block_hash); let validator = self.signer.address(); self.votes.vote(message.clone(), &validator); debug!(target: "engine", "Generated {:?} as {}.", message, validator); @@ -230,7 +235,7 @@ impl Tendermint { let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { // Generate seal and remove old votes. - if self.is_signer_proposer() { + if self.is_signer_proposer(&*self.proposal_parent.read()) { let proposal_step = VoteStep::new(height, view, Step::Propose); let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit); let votes = self.votes.round_signatures(&precommit_step, &block_hash); @@ -254,23 +259,23 @@ impl Tendermint { } fn is_authority(&self, address: &Address) -> bool { - self.validators.contains(address) + self.validators.contains(&*self.proposal_parent.read(), address) } fn is_above_threshold(&self, n: usize) -> bool { - n > self.validators.count() * 2/3 + n > self.validators.count(&*self.proposal_parent.read()) * 2/3 } /// Find the designated for the given view. - fn view_proposer(&self, height: Height, view: View) -> Address { + fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address { let proposer_nonce = height + view; trace!(target: "engine", "Proposer nonce: {}", proposer_nonce); - self.validators.get(proposer_nonce) + self.validators.get(bh, proposer_nonce) } /// Check if address is a proposer for given view. - fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> { - let proposer = self.view_proposer(height, view); + fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> { + let proposer = self.view_proposer(bh, height, view); if proposer == *address { Ok(()) } else { @@ -279,8 +284,8 @@ impl Tendermint { } /// Check if current signer is the current proposer. - fn is_signer_proposer(&self) -> bool { - let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + fn is_signer_proposer(&self, bh: &H256) -> bool { + let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.signer.is_address(&proposer) } @@ -371,14 +376,20 @@ impl Tendermint { impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// (consensus view, proposal signature, authority signatures) fn seal_fields(&self) -> usize { 3 } fn params(&self) -> &CommonParams { &self.params } + + fn additional_params(&self) -> HashMap { hash_map!["registrar".to_owned() => self.registrar.hex()] } + fn builtins(&self) -> &BTreeMap { &self.builtins } fn maximum_uncle_count(&self) -> usize { 0 } + fn maximum_uncle_age(&self) -> usize { 0 } /// Additional engine-specific information for the user/developer concerning `header`. @@ -412,8 +423,8 @@ impl Engine for Tendermint { } /// Should this node participate. - fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_authority(address)) + fn seals_internally(&self) -> Option { + Some(self.signer.address() != Address::default()) } /// Attempt to seal generate a proposal seal. @@ -421,7 +432,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. - if !self.is_signer_proposer() || self.proposal.read().is_some() { + if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() { return Seal::None; } @@ -435,6 +446,7 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author); // Remember proposal for later seal submission. *self.proposal.write() = bh; + *self.proposal_parent.write() = header.parent_hash().clone(); Seal::Proposal(vec![ ::rlp::encode(&view).to_vec(), ::rlp::encode(&signature).to_vec(), @@ -469,10 +481,12 @@ impl Engine for Tendermint { fn on_close_block(&self, block: &mut ExecutedBlock) { let fields = block.fields_mut(); // Bestow block reward - fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty); + let res = fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty) + .map_err(::error::Error::from) + .and_then(|_| fields.state.commit()); // Commit state so that we can actually figure out the state root. - if let Err(e) = fields.state.commit() { - warn!("Encountered error on state commit: {}", e); + if let Err(e) = res { + warn!("Encountered error on closing block: {}", e); } } @@ -497,7 +511,12 @@ impl Engine for Tendermint { } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + Ok(()) + } + + /// Verify validators and gas limit. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = ConsensusMessage::new_proposal(header)?; let proposer = proposal.verify()?; if !self.is_authority(&proposer) { @@ -514,7 +533,7 @@ impl Engine for Tendermint { Some(a) => a, None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), }; - if !self.validators.contains(&address) { + if !self.validators.contains(header.parent_hash(), &address) { Err(EngineError::NotAuthorized(address.to_owned()))? } @@ -537,12 +556,9 @@ impl Engine for Tendermint { found: signatures_len }))?; } - self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?; + self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?; } - Ok(()) - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.number() == 0 { Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; } @@ -587,6 +603,7 @@ impl Engine for Tendermint { debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); if self.is_view(&proposal) { *self.proposal.write() = proposal.block_hash.clone(); + *self.proposal_parent.write() = header.parent_hash().clone(); } self.votes.vote(proposal, &proposer); true @@ -599,7 +616,7 @@ impl Engine for Tendermint { trace!(target: "engine", "Propose timeout."); if self.proposal.read().is_none() { // Report the proposer if no proposal was received. - let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.validators.report_benign(¤t_proposer); } Step::Prevote @@ -757,20 +774,25 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "0"); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); + let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); - let validator = insert_and_unlock(&tap, "1"); + let validator = insert_and_unlock(&tap, "0"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } @@ -780,7 +802,7 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Not authority. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; @@ -792,19 +814,24 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); + header.set_number(2); + header.set_gas_limit(U256::from_str("222222").unwrap()); let proposer = insert_and_unlock(&tap, "1"); header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash())); + let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash())); let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); header.set_seal(seal.clone()); // One good signature is not enough. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, _ => panic!(), } @@ -815,7 +842,7 @@ mod tests { seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); header.set_seal(seal.clone()); - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); @@ -824,7 +851,7 @@ mod tests { header.set_seal(seal); // One good and one bad signature. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index caff8cee87a9c50b342f59d94764d8209ee76933..e380131888adfc563a532f93a98f098fb2c09a31 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use util::{U256, Uint}; +use util::{U256, Uint, Address, FixedHash}; use time::Duration; use super::super::transition::Timeouts; use super::Step; @@ -33,6 +33,8 @@ pub struct TendermintParams { pub timeouts: TendermintTimeouts, /// Block reward. pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, } /// Base timeout of each step in ms. @@ -88,6 +90,7 @@ impl From for TendermintParams { commit: p.timeout_commit.map_or(dt.commit, to_duration), }, block_reward: p.block_reward.map_or_else(U256::zero, Into::into), + registrar: p.registrar.map_or_else(Address::new, Into::into), } } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 81bd6b0894d4bcf0cfa60996ad205d3f425b6eec..91fbf5fab8b5e42444cbc9c3cbf4726977f56885 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract; /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorContract { - validators: Arc, + validators: ValidatorSafeContract, provider: RwLock>, } impl ValidatorContract { pub fn new(contract_address: Address) -> Self { ValidatorContract { - validators: Arc::new(ValidatorSafeContract::new(contract_address)), + validators: ValidatorSafeContract::new(contract_address), provider: RwLock::new(None), } } } -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.contains(address) +impl ValidatorSet for ValidatorContract { + fn contains(&self, bh: &H256, address: &Address) -> bool { + self.validators.contains(bh, address) } - fn get(&self, nonce: usize) -> Address { - self.validators.get(nonce) + fn get(&self, bh: &H256, nonce: usize) -> Address { + self.validators.get(bh, nonce) } - fn count(&self) -> usize { - self.validators.count() + fn count(&self, bh: &H256) -> usize { + self.validators.count(bh) } fn report_malicious(&self, address: &Address) { @@ -144,6 +144,7 @@ mod tests { use header::Header; use account_provider::AccountProvider; use miner::MinerService; + use types::ids::BlockId; use client::BlockChainClient; use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; @@ -154,8 +155,9 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] @@ -171,18 +173,21 @@ mod tests { client.miner().set_engine_signer(v1, "".into()).unwrap(); let mut header = Header::default(); - let seal = encode(&vec!(5u8)).to_vec(); - header.set_seal(vec!(seal)); + let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()]; + header.set_seal(seal); header.set_author(v1); - header.set_number(1); + header.set_number(2); + header.set_parent_hash(client.chain_info().best_block_hash); + // `reportBenign` when the designated proposer releases block from the future (bad clock). - assert!(client.engine().verify_block_unordered(&header, None).is_err()); + assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); assert_eq!(client.chain_info().best_block_number, 1); // Check if the unresponsive validator is `disliked`. - assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); + assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); // Simulate a misbehaving validator by handling a double proposal. + let header = client.best_block_header().decode(); assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 43a6f71d1235818e3b43bce841f4fba819b378db..3e86c357faca2f8f7536d7dee125fbdb884af579 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -21,7 +21,7 @@ mod safe_contract; mod contract; use std::sync::Weak; -use util::{Address, Arc}; +use util::{Address, H256}; use ethjson::spec::ValidatorSet as ValidatorSpec; use client::Client; use self::simple_list::SimpleList; @@ -32,18 +32,18 @@ use self::safe_contract::ValidatorSafeContract; pub fn new_validator_set(spec: ValidatorSpec) -> Box { match spec { ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), - ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))), - ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), + ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), + ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), } } pub trait ValidatorSet { /// Checks if a given address is a validator. - fn contains(&self, address: &Address) -> bool; + fn contains(&self, bh: &H256, address: &Address) -> bool; /// Draws an validator nonce modulo number of validators. - fn get(&self, nonce: usize) -> Address; + fn get(&self, bh: &H256, nonce: usize) -> Address; /// Returns the current number of validators. - fn count(&self) -> usize; + fn count(&self, bh: &H256) -> usize; /// Notifies about malicious behaviour. fn report_malicious(&self, _validator: &Address) {} /// Notifies about benign misbehaviour. diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 1a068d858cc70dc66eeea77d63f4e0463ff7fdb0..0a0eaecfd9efe95f3423075fc0881535bef4d1a5 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -17,17 +17,23 @@ /// Validator set maintained in a contract, updated using `getValidators` method. use std::sync::Weak; +use ethabi; use util::*; +use util::cache::MemoryLruCache; +use types::ids::BlockId; use client::{Client, BlockChainClient}; -use client::chain_notify::ChainNotify; use super::ValidatorSet; use super::simple_list::SimpleList; +const MEMOIZE_CAPACITY: usize = 500; +const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]"; +const GET_VALIDATORS: &'static str = "getValidators"; + /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorSafeContract { pub address: Address, - validators: RwLock, + validators: RwLock>, provider: RwLock>, } @@ -35,102 +41,127 @@ impl ValidatorSafeContract { pub fn new(contract_address: Address) -> Self { ValidatorSafeContract { address: contract_address, - validators: Default::default(), + validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), provider: RwLock::new(None), } } - /// Queries the state and updates the set of validators. - pub fn update(&self) { + /// Queries the state and gets the set of validators. + fn get_list(&self, block_hash: H256) -> Option { if let Some(ref provider) = *self.provider.read() { - match provider.get_validators() { + match provider.get_validators(BlockId::Hash(block_hash)) { Ok(new) => { debug!(target: "engine", "Set of validators obtained: {:?}", new); - *self.validators.write() = SimpleList::new(new); + Some(SimpleList::new(new)) + }, + Err(s) => { + debug!(target: "engine", "Set of validators could not be updated: {}", s); + None }, - Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s), } } else { - warn!(target: "engine", "Set of validators could not be updated: no provider contract.") - } - } -} - -/// Checks validators on every block. -impl ChainNotify for ValidatorSafeContract { - fn new_blocks( - &self, - _: Vec, - _: Vec, - enacted: Vec, - _: Vec, - _: Vec, - _: Vec, - _duration: u64) { - if !enacted.is_empty() { - self.update(); + warn!(target: "engine", "Set of validators could not be updated: no provider contract."); + None } } } -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.read().contains(address) +impl ValidatorSet for ValidatorSafeContract { + fn contains(&self, block_hash: &H256, address: &Address) -> bool { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.contains(block_hash, address)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or(false, |list| { + let contains = list.contains(block_hash, address); + guard.insert(block_hash.clone(), list); + contains + })) } - fn get(&self, nonce: usize) -> Address { - self.validators.read().get(nonce) + fn get(&self, block_hash: &H256, nonce: usize) -> Address { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.get(block_hash, nonce)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(Default::default, |list| { + let address = list.get(block_hash, nonce); + guard.insert(block_hash.clone(), list); + address + })) } - fn count(&self) -> usize { - self.validators.read().count() + fn count(&self, block_hash: &H256) -> usize { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.count(block_hash)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(usize::max_value, |list| { + let address = list.count(block_hash); + guard.insert(block_hash.clone(), list); + address + })) } fn register_contract(&self, client: Weak) { - if let Some(c) = client.upgrade() { - c.add_notify(self.clone()); - } - { - *self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)))); - } - self.update(); + trace!(target: "engine", "Setting up contract caller."); + let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed")); + let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed"); + let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed"); + let contract_address = self.address.clone(); + let do_call = move |id| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.call_contract(id, contract_address.clone(), data.clone())) + .map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed")); + *self.provider.write() = Some(provider::Contract::new(do_call)); } } mod provider { - // Autogenerated from JSON contract definition using Rust contract convertor. - #![allow(unused_imports)] use std::string::String; use std::result::Result; - use std::fmt; use {util, ethabi}; - use util::{FixedHash, Uint}; + use types::ids::BlockId; pub struct Contract { - contract: ethabi::Contract, - address: util::Address, - do_call: Box) -> Result, String> + Send + Sync + 'static>, + do_call: Box Result, String> + Send + Sync + 'static>, } + impl Contract { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { + pub fn new(do_call: F) -> Self where F: Fn(BlockId) -> Result, String> + Send + Sync + 'static { Contract { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), - address: address, do_call: Box::new(do_call), } } - fn as_string(e: T) -> String { format!("{:?}", e) } - - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` - #[allow(dead_code)] - pub fn get_validators(&self) -> Result, String> { - let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::>() })) + + /// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` + pub fn get_validators(&self, id: BlockId) -> Result, String> { + Ok((self.do_call)(id)? + .into_iter() + .rev() + .collect::>() + .pop() + .expect("get_validators returns one argument; qed") + .to_array() + .and_then(|v| v + .into_iter() + .map(|a| a.to_address()) + .collect::>>()) + .expect("get_validators returns a list of addresses; qed") + .into_iter() + .map(util::Address::from) + .collect::>() + ) } } } @@ -138,13 +169,14 @@ mod provider { #[cfg(test)] mod tests { use util::*; + use types::ids::BlockId; use spec::Spec; use account_provider::AccountProvider; use transaction::{Transaction, Action}; use client::{BlockChainClient, EngineClient}; use ethkey::Secret; use miner::MinerService; - use tests::helpers::generate_dummy_client_with_spec_and_accounts; + use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use super::super::ValidatorSet; use super::ValidatorSafeContract; @@ -153,12 +185,13 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] - fn updates_validators() { + fn knows_validators() { let tap = Arc::new(AccountProvider::transient_provider()); let s0 = Secret::from_slice(&"1".sha3()).unwrap(); let v0 = tap.insert_account(s0.clone(), "").unwrap(); @@ -212,5 +245,14 @@ mod tests { client.update_sealing(); // Able to seal again. assert_eq!(client.chain_info().best_block_number, 3); + + // Check syncing. + let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]); + sync_client.engine().register_client(Arc::downgrade(&sync_client)); + for i in 1..4 { + sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); + } + sync_client.flush_queue(); + assert_eq!(sync_client.chain_info().best_block_number, 3); } } diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs index 3ba574b5a52103313e9cc50a4dda44baf1aa9175..2d768797912c9ee92c0090236f5df87c493c2475 100644 --- a/ethcore/src/engines/validator_set/simple_list.rs +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -16,7 +16,7 @@ /// Preconfigured validator list. -use util::Address; +use util::{H256, Address, HeapSizeOf}; use super::ValidatorSet; #[derive(Debug, PartialEq, Eq, Default)] @@ -34,16 +34,22 @@ impl SimpleList { } } +impl HeapSizeOf for SimpleList { + fn heap_size_of_children(&self) -> usize { + self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children() + } +} + impl ValidatorSet for SimpleList { - fn contains(&self, address: &Address) -> bool { + fn contains(&self, _bh: &H256, address: &Address) -> bool { self.validators.contains(address) } - fn get(&self, nonce: usize) -> Address { + fn get(&self, _bh: &H256, nonce: usize) -> Address { self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone() } - fn count(&self) -> usize { + fn count(&self, _bh: &H256) -> usize { self.validator_n } } @@ -60,9 +66,9 @@ mod tests { let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); let list = SimpleList::new(vec![a1.clone(), a2.clone()]); - assert!(list.contains(&a1)); - assert_eq!(list.get(0), a1); - assert_eq!(list.get(1), a2); - assert_eq!(list.get(2), a1); + assert!(list.contains(&Default::default(), &a1)); + assert_eq!(list.get(&Default::default(), 0), a1); + assert_eq!(list.get(&Default::default(), 1), a2); + assert_eq!(list.get(&Default::default(), 2), a1); } } diff --git a/ethcore/src/env_info.rs b/ethcore/src/env_info.rs index 9e1bb6a407ecef921d2d5f5fef8f53c529012827..cc42008d5102e346d0a2dd3dccc7ba3330d30e72 100644 --- a/ethcore/src/env_info.rs +++ b/ethcore/src/env_info.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Environment information for transaction execution. + use std::cmp; use std::sync::Arc; use util::{U256, Address, H256, Hashable}; @@ -25,7 +27,7 @@ use ethjson; pub type LastHashes = Vec; /// Information concerning the execution environment for a message-call/contract-creation. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EnvInfo { /// The block number. pub number: BlockNumber, diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 6c7b91d399abc15b4f25c01730724090d98c4351..fbb9c8a5ca4e6e50054f6760377b0ac53c3ae2b7 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -79,6 +79,14 @@ pub struct EthashParams { pub ecip1010_continue_transition: u64, /// Maximum amount of code that can be deploying into a contract. pub max_code_size: u64, + /// Number of first block where the max gas limit becomes effective. + pub max_gas_limit_transition: u64, + /// Maximum valid block gas limit, + pub max_gas_limit: U256, + /// Number of first block where the minimum gas price becomes effective. + pub min_gas_price_transition: u64, + /// Do not alow transactions with lower gas price. + pub min_gas_price: U256, } impl From for EthashParams { @@ -106,6 +114,10 @@ impl From for EthashParams { ecip1010_pause_transition: p.ecip1010_pause_transition.map_or(u64::max_value(), Into::into), ecip1010_continue_transition: p.ecip1010_continue_transition.map_or(u64::max_value(), Into::into), max_code_size: p.max_code_size.map_or(u64::max_value(), Into::into), + max_gas_limit_transition: p.max_gas_limit_transition.map_or(u64::max_value(), Into::into), + max_gas_limit: p.max_gas_limit.map_or(U256::max_value(), Into::into), + min_gas_price_transition: p.min_gas_price_transition.map_or(u64::max_value(), Into::into), + min_gas_price: p.min_gas_price.map_or(U256::zero(), Into::into), } } } @@ -174,8 +186,12 @@ impl Engine for Ethash { } } - fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) { + fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, mut gas_ceil_target: U256) { let difficulty = self.calculate_difficulty(header, parent); + if header.number() >= self.ethash_params.max_gas_limit_transition && gas_ceil_target > self.ethash_params.max_gas_limit { + warn!("Gas limit target is limited to {}", self.ethash_params.max_gas_limit); + gas_ceil_target = self.ethash_params.max_gas_limit; + } let gas_limit = { let gas_limit = parent.gas_limit().clone(); let bound_divisor = self.ethash_params.gas_limit_bound_divisor; @@ -213,10 +229,16 @@ impl Engine for Ethash { if block.fields().header.number() == self.ethash_params.dao_hardfork_transition { // TODO: enable trigger function maybe? // if block.fields().header.gas_limit() <= 4_000_000.into() { - let mut state = block.fields_mut().state; + let state = block.fields_mut().state; for child in &self.ethash_params.dao_hardfork_accounts { - let b = state.balance(child); - state.transfer_balance(child, &self.ethash_params.dao_hardfork_beneficiary, &b, CleanupMode::NoEmpty); + let beneficiary = &self.ethash_params.dao_hardfork_beneficiary; + let res = state.balance(child) + .and_then(|b| state.transfer_balance(child, beneficiary, &b, CleanupMode::NoEmpty)); + + if let Err(_) = res { + warn!("Unable to apply DAO hardfork due to database corruption."); + warn!("Your node is now likely out of consensus."); + } } // } } @@ -229,12 +251,28 @@ impl Engine for Ethash { let fields = block.fields_mut(); // Bestow block reward - fields.state.add_balance(fields.header.author(), &(reward + reward / U256::from(32) * U256::from(fields.uncles.len())), CleanupMode::NoEmpty); + let res = fields.state.add_balance( + fields.header.author(), + &(reward + reward / U256::from(32) * U256::from(fields.uncles.len())), + CleanupMode::NoEmpty + ); + + if let Err(e) = res { + warn!("Failed to give block reward: {}", e); + } // Bestow uncle rewards let current_number = fields.header.number(); for u in fields.uncles.iter() { - fields.state.add_balance(u.author(), &(reward * U256::from(8 + u.number() - current_number) / U256::from(8)), CleanupMode::NoEmpty); + let res = fields.state.add_balance( + u.author(), + &(reward * U256::from(8 + u.number() - current_number) / U256::from(8)), + CleanupMode::NoEmpty + ); + + if let Err(e) = res { + warn!("Failed to give uncle reward: {}", e); + } } // Commit state so that we can actually figure out the state root. @@ -312,11 +350,15 @@ impl Engine for Ethash { return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: expected_difficulty, found: header.difficulty().clone() }))) } let gas_limit_divisor = self.ethash_params.gas_limit_bound_divisor; - let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; - let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; + let parent_gas_limit = *parent.gas_limit(); + let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor; + let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor; if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); } + if header.number() >= self.ethash_params.max_gas_limit_transition && header.gas_limit() > &self.ethash_params.max_gas_limit && header.gas_limit() > &parent_gas_limit { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(self.ethash_params.max_gas_limit), found: header.gas_limit().clone() }))); + } Ok(()) } @@ -331,6 +373,10 @@ impl Engine for Ethash { } } + if header.number() >= self.ethash_params.min_gas_price_transition && t.gas_price < self.ethash_params.min_gas_price { + return Err(TransactionError::InsufficientGasPrice { minimal: self.ethash_params.min_gas_price, got: t.gas_price }.into()); + } + Ok(()) } } @@ -467,7 +513,7 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close(); - assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244f40000").unwrap()); + assert_eq!(b.state().balance(&Address::zero()).unwrap(), U256::from_str("4563918244f40000").unwrap()); } #[test] @@ -485,8 +531,8 @@ mod tests { b.push_uncle(uncle).unwrap(); let b = b.close(); - assert_eq!(b.state().balance(&Address::zero()), "478eae0e571ba000".into()); - assert_eq!(b.state().balance(&uncle_author), "3cb71f51fc558000".into()); + assert_eq!(b.state().balance(&Address::zero()).unwrap(), "478eae0e571ba000".into()); + assert_eq!(b.state().balance(&uncle_author).unwrap(), "3cb71f51fc558000".into()); } #[test] @@ -835,4 +881,100 @@ mod tests { ethash.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002)); assert_eq!(*header.gas_limit(), U256::from(150_002)); } + + #[test] + fn difficulty_max_timestamp() { + let spec = new_homestead_test(); + let ethparams = get_default_ethash_params(); + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + + let mut parent_header = Header::default(); + parent_header.set_number(1000000); + parent_header.set_difficulty(U256::from_str("b69de81a22b").unwrap()); + parent_header.set_timestamp(1455404053); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + header.set_timestamp(u64::max_value()); + + let difficulty = ethash.calculate_difficulty(&header, &parent_header); + assert_eq!(U256::from(12543204905719u64), difficulty); + } + + #[test] + fn rejects_blocks_over_max_gas_limit() { + let spec = new_homestead_test(); + let mut ethparams = get_default_ethash_params(); + ethparams.max_gas_limit_transition = 10; + ethparams.max_gas_limit = 100_000.into(); + + let mut parent_header = Header::default(); + parent_header.set_number(1); + parent_header.set_gas_limit(100_000.into()); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + header.set_gas_limit(100_001.into()); + header.set_difficulty(ethparams.minimum_difficulty); + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + assert!(ethash.verify_block_family(&header, &parent_header, None).is_ok()); + + parent_header.set_number(9); + header.set_number(parent_header.number() + 1); + + parent_header.set_gas_limit(99_999.into()); + header.set_gas_limit(100_000.into()); + assert!(ethash.verify_block_family(&header, &parent_header, None).is_ok()); + + parent_header.set_gas_limit(200_000.into()); + header.set_gas_limit(200_000.into()); + assert!(ethash.verify_block_family(&header, &parent_header, None).is_ok()); + + parent_header.set_gas_limit(100_000.into()); + header.set_gas_limit(100_001.into()); + assert!(ethash.verify_block_family(&header, &parent_header, None).is_err()); + + parent_header.set_gas_limit(200_000.into()); + header.set_gas_limit(200_001.into()); + assert!(ethash.verify_block_family(&header, &parent_header, None).is_err()); + } + + #[test] + fn rejects_transactions_below_min_gas_price() { + use ethkey::{Generator, Random}; + use types::transaction::{Transaction, Action}; + + let spec = new_homestead_test(); + let mut ethparams = get_default_ethash_params(); + ethparams.min_gas_price_transition = 10; + ethparams.min_gas_price = 100000.into(); + + let mut header = Header::default(); + header.set_number(1); + + let keypair = Random.generate().unwrap(); + let tx1 = Transaction { + action: Action::Create, + value: U256::zero(), + data: Vec::new(), + gas: 100_000.into(), + gas_price: 100_000.into(), + nonce: U256::zero(), + }.sign(keypair.secret(), None).into(); + + let tx2 = Transaction { + action: Action::Create, + value: U256::zero(), + data: Vec::new(), + gas: 100_000.into(), + gas_price: 99_999.into(), + nonce: U256::zero(), + }.sign(keypair.secret(), None).into(); + + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + assert!(ethash.verify_transaction_basic(&tx1, &header).is_ok()); + assert!(ethash.verify_transaction_basic(&tx2, &header).is_ok()); + + header.set_number(10); + assert!(ethash.verify_transaction_basic(&tx1, &header).is_ok()); + assert!(ethash.verify_transaction_basic(&tx2, &header).is_err()); + } } diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index c8eb44911994f57d2dec8eecbb6d1470ff1b727f..ce8b84b3192e709eba3f4cffaba9d7f263bdbb6f 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -30,46 +30,52 @@ pub use self::denominations::*; use super::spec::*; /// Most recent fork block that we support on Mainnet. -pub const FORK_SUPPORTED_FRONTIER: u64 = 2675000; +pub const FORK_SUPPORTED_FOUNDATION: u64 = 2675000; /// Most recent fork block that we support on Ropsten. pub const FORK_SUPPORTED_ROPSTEN: u64 = 10; +/// Most recent fork block that we support on Kovan. +pub const FORK_SUPPORTED_KOVAN: u64 = 0; + fn load(b: &[u8]) -> Spec { Spec::load(b).expect("chain spec is invalid") } -/// Create a new Olympic chain spec. +/// Create a new Foundation Olympic chain spec. pub fn new_olympic() -> Spec { load(include_bytes!("../../res/ethereum/olympic.json")) } -/// Create a new Frontier mainnet chain spec. -pub fn new_frontier() -> Spec { load(include_bytes!("../../res/ethereum/frontier.json")) } +/// Create a new Foundation Mainnet chain spec. +pub fn new_foundation() -> Spec { load(include_bytes!("../../res/ethereum/foundation.json")) } -/// Create a new Frontier mainnet chain spec without the DAO hardfork. +/// Create a new Classic Mainnet chain spec without the DAO hardfork. pub fn new_classic() -> Spec { load(include_bytes!("../../res/ethereum/classic.json")) } -/// Create a new Frontier mainnet chain spec without the DAO hardfork. +/// Create a new Expanse mainnet chain spec. pub fn new_expanse() -> Spec { load(include_bytes!("../../res/ethereum/expanse.json")) } -/// Create a new Frontier chain spec as though it never changes to Homestead. +/// Create a new Kovan testnet chain spec. +pub fn new_kovan() -> Spec { load(include_bytes!("../../res/ethereum/kovan.json")) } + +/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead. pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/frontier_test.json")) } -/// Create a new Homestead chain spec as though it never changed from Frontier. +/// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier. pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) } -/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. +/// Create a new Foundation Homestead-EIP150-era chain spec as though it never changed from Homestead/Frontier. pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) } -/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. +/// Create a new Foundation Homestead-EIP161-era chain spec as though it never changed from Homestead/Frontier. pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) } -/// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8. +/// Create a new Foundation Frontier/Homestead/DAO chain spec with transition points at #5 and #8. pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) } -/// Create a new Frontier main net chain spec without genesis accounts. +/// Create a new Foundation Mainnet chain spec without genesis accounts. pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } -/// Create a new Ropsten chain spec. +/// Create a new Foundation Ropsten chain spec. pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } /// Create a new Morden chain spec. @@ -91,12 +97,12 @@ mod tests { let mut db_result = get_temp_state_db(); let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap(); - assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()), 1u64.into()); - assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()), 1u64.into()); - assert_eq!(s.balance(&"0000000000000000000000000000000000000003".into()), 1u64.into()); - assert_eq!(s.balance(&"0000000000000000000000000000000000000004".into()), 1u64.into()); - assert_eq!(s.balance(&"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c".into()), U256::from(1u64) << 200); - assert_eq!(s.balance(&"0000000000000000000000000000000000000000".into()), 0u64.into()); + assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()).unwrap(), 1u64.into()); + assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()).unwrap(), 1u64.into()); + assert_eq!(s.balance(&"0000000000000000000000000000000000000003".into()).unwrap(), 1u64.into()); + assert_eq!(s.balance(&"0000000000000000000000000000000000000004".into()).unwrap(), 1u64.into()); + assert_eq!(s.balance(&"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c".into()).unwrap(), U256::from(1u64) << 200); + assert_eq!(s.balance(&"0000000000000000000000000000000000000000".into()).unwrap(), 0u64.into()); } #[test] @@ -112,7 +118,7 @@ mod tests { #[test] fn frontier() { - let frontier = new_frontier(); + let frontier = new_foundation(); assert_eq!(frontier.state_root(), "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".into()); let genesis = frontier.genesis_block(); diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 420ebb6a00e9679c33d1c9be36a4f9a127d0acd3..09a93f087d1aa2a2959700de3b003719118984b2 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -17,12 +17,12 @@ //! Evm interface. use std::{ops, cmp, fmt}; -use util::{U128, U256, U512, Uint}; +use util::{U128, U256, U512, Uint, trie}; use action_params::ActionParams; use evm::Ext; /// Evm errors. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Error { /// `OutOfGas` is returned when transaction execution runs out of gas. /// The state should be reverted to the state from before the @@ -61,8 +61,13 @@ pub enum Error { }, /// Returned on evm internal error. Should never be ignored during development. /// Likely to cause consensus issues. - #[allow(dead_code)] // created only by jit - Internal, + Internal(String), +} + +impl From> for Error { + fn from(err: Box) -> Self { + Error::Internal(format!("Internal error: {}", err)) + } } impl fmt::Display for Error { @@ -74,7 +79,7 @@ impl fmt::Display for Error { BadInstruction { .. } => "Bad instruction", StackUnderflow { .. } => "Stack underflow", OutOfStack { .. } => "Out of stack", - Internal => "Internal error", + Internal(ref msg) => msg, }; message.fmt(f) } diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index e2578cc6835d6575221bf68aeb172ae092d667a5..352ffb7d9b828e4fff6625f35336622d12023199 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -42,24 +42,25 @@ pub enum MessageCallResult { } /// Externalities interface for EVMs +// TODO: [rob] associated error type instead of `trie::Result`. Not all EVMs are trie powered. pub trait Ext { /// Returns a value for given key. - fn storage_at(&self, key: &H256) -> H256; + fn storage_at(&self, key: &H256) -> trie::Result; /// Stores a value for given key. - fn set_storage(&mut self, key: H256, value: H256); + fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()>; /// Determine whether an account exists. - fn exists(&self, address: &Address) -> bool; + fn exists(&self, address: &Address) -> trie::Result; /// Determine whether an account exists and is not null (zero balance/nonce, no code). - fn exists_and_not_null(&self, address: &Address) -> bool; + fn exists_and_not_null(&self, address: &Address) -> trie::Result; /// Balance of the origin account. - fn origin_balance(&self) -> U256; + fn origin_balance(&self) -> trie::Result; /// Returns address balance. - fn balance(&self, address: &Address) -> U256; + fn balance(&self, address: &Address) -> trie::Result; /// Returns the hash of one of the 256 most recent complete blocks. fn blockhash(&self, number: &U256) -> H256; @@ -87,10 +88,10 @@ pub trait Ext { ) -> MessageCallResult; /// Returns code at given address - fn extcode(&self, address: &Address) -> Arc; + fn extcode(&self, address: &Address) -> trie::Result>; /// Returns code size at given address - fn extcodesize(&self, address: &Address) -> usize; + fn extcodesize(&self, address: &Address) -> trie::Result; /// Creates log entry with given topics and data fn log(&mut self, topics: Vec, data: &[u8]); @@ -101,7 +102,7 @@ pub trait Ext { /// Should be called when contract commits suicide. /// Address to which funds should be refunded. - fn suicide(&mut self, refund_address: &Address); + fn suicide(&mut self, refund_address: &Address) -> trie::Result<()> ; /// Returns schedule. fn schedule(&self) -> &Schedule; diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index 5c96c3c05c52a4b59a2b6590b0c67ee7db1d0c01..9086200fafba5b6b47f200edc29c56047f3ae479 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -123,7 +123,7 @@ impl Gasometer { instructions::SSTORE => { let address = H256::from(stack.peek(0)); let newval = stack.peek(1); - let val = U256::from(&*ext.storage_at(&address)); + let val = U256::from(&*ext.storage_at(&address)?); let gas = if val.is_zero() && !newval.is_zero() { schedule.sstore_set_gas @@ -146,12 +146,12 @@ impl Gasometer { instructions::SUICIDE => { let mut gas = Gas::from(schedule.suicide_gas); - let is_value_transfer = !ext.origin_balance().is_zero(); + let is_value_transfer = !ext.origin_balance()?.is_zero(); let address = u256_to_address(stack.peek(0)); if ( - !schedule.no_empty && !ext.exists(&address) + !schedule.no_empty && !ext.exists(&address)? ) || ( - schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address) + schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)? ) { gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into())); } @@ -198,9 +198,9 @@ impl Gasometer { let is_value_transfer = !stack.peek(2).is_zero(); if instruction == instructions::CALL && ( - (!schedule.no_empty && !ext.exists(&address)) + (!schedule.no_empty && !ext.exists(&address)?) || - (schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)) + (schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)?) ) { gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into())); } diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index bc3caa08432f698ee42473ec791b79e4ff08943a..79304793eae89e1c8244eabe0fe5688d670ab62a 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -273,7 +273,7 @@ impl Interpreter { let create_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is `CREATE`; qed"); let contract_code = self.mem.read_slice(init_off, init_size); - let can_create = ext.balance(¶ms.address) >= endowment && ext.depth() < ext.schedule().max_depth; + let can_create = ext.balance(¶ms.address)? >= endowment && ext.depth() < ext.schedule().max_depth; if !can_create { stack.push(U256::zero()); @@ -319,11 +319,11 @@ impl Interpreter { // Get sender & receive addresses, check if we have balance let (sender_address, receive_address, has_balance, call_type) = match instruction { instructions::CALL => { - let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed"); + let has_balance = ext.balance(¶ms.address)? >= value.expect("value set for all but delegate call; qed"); (¶ms.address, &code_address, has_balance, CallType::Call) }, instructions::CALLCODE => { - let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed"); + let has_balance = ext.balance(¶ms.address)? >= value.expect("value set for all but delegate call; qed"); (¶ms.address, ¶ms.address, has_balance, CallType::CallCode) }, instructions::DELEGATECALL => (¶ms.sender, ¶ms.address, true, CallType::DelegateCall), @@ -366,7 +366,7 @@ impl Interpreter { }, instructions::SUICIDE => { let address = stack.pop_back(); - ext.suicide(&u256_to_address(&address)); + ext.suicide(&u256_to_address(&address))?; return Ok(InstructionResult::StopExecution); }, instructions::LOG0...instructions::LOG4 => { @@ -410,19 +410,19 @@ impl Interpreter { }, instructions::SLOAD => { let key = H256::from(&stack.pop_back()); - let word = U256::from(&*ext.storage_at(&key)); + let word = U256::from(&*ext.storage_at(&key)?); stack.push(word); }, instructions::SSTORE => { let address = H256::from(&stack.pop_back()); let val = stack.pop_back(); - let current_val = U256::from(&*ext.storage_at(&address)); + let current_val = U256::from(&*ext.storage_at(&address)?); // Increase refund for clear if !self.is_zero(¤t_val) && self.is_zero(&val) { ext.inc_sstore_clears(); } - ext.set_storage(address, H256::from(&val)); + ext.set_storage(address, H256::from(&val))?; }, instructions::PC => { stack.push(U256::from(code.position - 1)); @@ -438,7 +438,7 @@ impl Interpreter { }, instructions::BALANCE => { let address = u256_to_address(&stack.pop_back()); - let balance = ext.balance(&address); + let balance = ext.balance(&address)?; stack.push(balance); }, instructions::CALLER => { @@ -474,7 +474,7 @@ impl Interpreter { }, instructions::EXTCODESIZE => { let address = u256_to_address(&stack.pop_back()); - let len = ext.extcodesize(&address); + let len = ext.extcodesize(&address)?; stack.push(U256::from(len)); }, instructions::CALLDATACOPY => { @@ -485,7 +485,7 @@ impl Interpreter { }, instructions::EXTCODECOPY => { let address = u256_to_address(&stack.pop_back()); - let code = ext.extcode(&address); + let code = ext.extcode(&address)?; self.copy_data_to_memory(stack, &code); }, instructions::GASPRICE => { diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index ccf711a408ebf61f123b48b9c614d713c7421704..3002c170c4f66b973a1044f50612754165b057b9 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -82,28 +82,29 @@ impl Default for Schedule { } impl Ext for FakeExt { - fn storage_at(&self, key: &H256) -> H256 { - self.store.get(key).unwrap_or(&H256::new()).clone() + fn storage_at(&self, key: &H256) -> trie::Result { + Ok(self.store.get(key).unwrap_or(&H256::new()).clone()) } - fn set_storage(&mut self, key: H256, value: H256) { + fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()> { self.store.insert(key, value); + Ok(()) } - fn exists(&self, address: &Address) -> bool { - self.balances.contains_key(address) + fn exists(&self, address: &Address) -> trie::Result { + Ok(self.balances.contains_key(address)) } - fn exists_and_not_null(&self, address: &Address) -> bool { - self.balances.get(address).map_or(false, |b| !b.is_zero()) + fn exists_and_not_null(&self, address: &Address) -> trie::Result { + Ok(self.balances.get(address).map_or(false, |b| !b.is_zero())) } - fn origin_balance(&self) -> U256 { + fn origin_balance(&self) -> trie::Result { unimplemented!() } - fn balance(&self, address: &Address) -> U256 { - self.balances[address] + fn balance(&self, address: &Address) -> trie::Result { + Ok(self.balances[address]) } fn blockhash(&self, number: &U256) -> H256 { @@ -146,12 +147,12 @@ impl Ext for FakeExt { MessageCallResult::Success(*gas) } - fn extcode(&self, address: &Address) -> Arc { - self.codes.get(address).unwrap_or(&Arc::new(Bytes::new())).clone() + fn extcode(&self, address: &Address) -> trie::Result> { + Ok(self.codes.get(address).unwrap_or(&Arc::new(Bytes::new())).clone()) } - fn extcodesize(&self, address: &Address) -> usize { - self.codes.get(address).map_or(0, |c| c.len()) + fn extcodesize(&self, address: &Address) -> trie::Result { + Ok(self.codes.get(address).map_or(0, |c| c.len())) } fn log(&mut self, topics: Vec, data: &[u8]) { @@ -165,7 +166,7 @@ impl Ext for FakeExt { unimplemented!(); } - fn suicide(&mut self, _refund_address: &Address) { + fn suicide(&mut self, _refund_address: &Address) -> trie::Result<()> { unimplemented!(); } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 3dffc66fa803e29b1bedfef448b79c6baa0ac2bb..d9f1b74132efa3878796382bfcb973213e3a5aa7 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -17,7 +17,7 @@ //! Transaction Execution environment. use util::*; use action_params::{ActionParams, ActionValue}; -use state::{State, Substate, CleanupMode}; +use state::{Backend as StateBackend, State, Substate, CleanupMode}; use engines::Engine; use types::executed::CallType; use env_info::EnvInfo; @@ -56,17 +56,17 @@ pub struct TransactOptions { } /// Transaction executor. -pub struct Executive<'a> { - state: &'a mut State, +pub struct Executive<'a, B: 'a + StateBackend> { + state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, depth: usize, } -impl<'a> Executive<'a> { +impl<'a, B: 'a + StateBackend> Executive<'a, B> { /// Basic constructor. - pub fn new(state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory) -> Self { + pub fn new(state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory) -> Self { Executive { state: state, info: info, @@ -77,7 +77,7 @@ impl<'a> Executive<'a> { } /// Populates executive from parent properties. Increments executive depth. - pub fn from_parent(state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, parent_depth: usize) -> Self { + pub fn from_parent(state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, parent_depth: usize) -> Self { Executive { state: state, info: info, @@ -95,7 +95,7 @@ impl<'a> Executive<'a> { output: OutputPolicy<'any, 'any>, tracer: &'any mut T, vm_tracer: &'any mut V - ) -> Externalities<'any, T, V> where T: Tracer, V: VMTracer { + ) -> Externalities<'any, T, V, B> where T: Tracer, V: VMTracer { Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer, vm_tracer) } @@ -123,7 +123,7 @@ impl<'a> Executive<'a> { mut vm_tracer: V ) -> Result where T: Tracer, V: VMTracer { let sender = t.sender(); - let nonce = self.state.nonce(&sender); + let nonce = self.state.nonce(&sender)?; let schedule = self.engine.schedule(self.info); let base_gas_required = U256::from(t.gas_required(&schedule)); @@ -149,7 +149,7 @@ impl<'a> Executive<'a> { } // TODO: we might need bigints here, or at least check overflows. - let balance = self.state.balance(&sender); + let balance = self.state.balance(&sender)?; let gas_cost = t.gas.full_mul(t.gas_price); let total_cost = U512::from(t.value) + gas_cost; @@ -160,8 +160,8 @@ impl<'a> Executive<'a> { } // NOTE: there can be no invalid transactions from this point. - self.state.inc_nonce(&sender); - self.state.sub_balance(&sender, &U256::from(gas_cost)); + self.state.inc_nonce(&sender)?; + self.state.sub_balance(&sender, &U256::from(gas_cost))?; let mut substate = Substate::new(); @@ -192,8 +192,8 @@ impl<'a> Executive<'a> { gas: init_gas, gas_price: t.gas_price, value: ActionValue::Transfer(t.value), - code: self.state.code(address), - code_hash: self.state.code_hash(address), + code: self.state.code(address)?, + code_hash: self.state.code_hash(address)?, data: Some(t.data.clone()), call_type: CallType::Call, }; @@ -257,7 +257,7 @@ impl<'a> Executive<'a> { // at first, transfer value to destination if let ActionValue::Transfer(val) = params.value { - self.state.transfer_balance(¶ms.sender, ¶ms.address, &val, substate.to_cleanup_mode(&schedule)); + self.state.transfer_balance(¶ms.sender, ¶ms.address, &val, substate.to_cleanup_mode(&schedule))?; } trace!("Executive::call(params={:?}) self.env_info={:?}", params, self.info); @@ -322,13 +322,13 @@ impl<'a> Executive<'a> { let traces = subtracer.traces(); match res { - Ok(gas_left) => tracer.trace_call( + Ok(ref gas_left) => tracer.trace_call( trace_info, - gas - gas_left, + gas - *gas_left, trace_output, traces ), - Err(e) => tracer.trace_failed_call(trace_info, traces, e.into()), + Err(ref e) => tracer.trace_failed_call(trace_info, traces, e.into()), }; trace!(target: "executive", "substate={:?}; unconfirmed_substate={:?}\n", substate, unconfirmed_substate); @@ -365,9 +365,9 @@ impl<'a> Executive<'a> { // create contract and transfer value to it if necessary let schedule = self.engine.schedule(self.info); let nonce_offset = if schedule.no_empty {1} else {0}.into(); - let prev_bal = self.state.balance(¶ms.address); + let prev_bal = self.state.balance(¶ms.address)?; if let ActionValue::Transfer(val) = params.value { - self.state.sub_balance(¶ms.sender, &val); + self.state.sub_balance(¶ms.sender, &val)?; self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset); } else { self.state.new_contract(¶ms.address, prev_bal, nonce_offset); @@ -388,14 +388,14 @@ impl<'a> Executive<'a> { vm_tracer.done_subtrace(subvmtracer); match res { - Ok(gas_left) => tracer.trace_create( + Ok(ref gas_left) => tracer.trace_create( trace_info, - gas - gas_left, + gas - *gas_left, trace_output, created, subtracer.traces() ), - Err(e) => tracer.trace_failed_create(trace_info, subtracer.traces(), e.into()) + Err(ref e) => tracer.trace_failed_create(trace_info, subtracer.traces(), e.into()) }; self.enact_result(&res, substate, unconfirmed_substate); @@ -435,9 +435,9 @@ impl<'a> Executive<'a> { let sender = t.sender(); trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, sender); // Below: NoEmpty is safe since the sender must already be non-null to have sent this transaction - self.state.add_balance(&sender, &refund_value, CleanupMode::NoEmpty); + self.state.add_balance(&sender, &refund_value, CleanupMode::NoEmpty)?; trace!("exec::finalize: Compensating author: fees_value={}, author={}\n", fees_value, &self.info.author); - self.state.add_balance(&self.info.author, &fees_value, substate.to_cleanup_mode(&schedule)); + self.state.add_balance(&self.info.author, &fees_value, substate.to_cleanup_mode(&schedule))?; // perform suicides for address in &substate.suicides { @@ -446,13 +446,13 @@ impl<'a> Executive<'a> { // perform garbage-collection for address in &substate.garbage { - if self.state.exists(address) && !self.state.exists_and_not_null(address) { + if self.state.exists(address)? && !self.state.exists_and_not_null(address)? { self.state.kill_account(address); } } match result { - Err(evm::Error::Internal) => Err(ExecutionError::Internal), + Err(evm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)), Err(exception) => { Ok(Executed { exception: Some(exception), @@ -495,7 +495,7 @@ impl<'a> Executive<'a> { | Err(evm::Error::OutOfStack {..}) => { self.state.revert_to_checkpoint(); }, - Ok(_) | Err(evm::Error::Internal) => { + Ok(_) | Err(evm::Error::Internal(_)) => { self.state.discard_checkpoint(); substate.accrue(un_substate); } @@ -544,7 +544,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(0x7)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(0x100u64), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(0x100u64), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -555,9 +555,9 @@ mod tests { }; assert_eq!(gas_left, U256::from(79_975)); - assert_eq!(state.storage_at(&address, &H256::new()), H256::from(&U256::from(0xf9u64))); - assert_eq!(state.balance(&sender), U256::from(0xf9)); - assert_eq!(state.balance(&address), U256::from(0x7)); + assert_eq!(state.storage_at(&address, &H256::new()).unwrap(), H256::from(&U256::from(0xf9u64))); + assert_eq!(state.balance(&sender).unwrap(), U256::from(0xf9)); + assert_eq!(state.balance(&address).unwrap(), U256::from(0x7)); // 0 cause contract hasn't returned assert_eq!(substate.contracts_created.len(), 0); @@ -603,7 +603,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -662,7 +662,7 @@ mod tests { params.call_type = CallType::Call; let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(5); let mut substate = Substate::new(); @@ -773,7 +773,7 @@ mod tests { params.value = ActionValue::Transfer(100.into()); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(5); let mut substate = Substate::new(); @@ -861,7 +861,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -913,7 +913,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(1024); let mut substate = Substate::new(); @@ -971,9 +971,9 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.init_code(&address_a, code_a.clone()); - state.init_code(&address_b, code_b.clone()); - state.add_balance(&sender, &U256::from(100_000), CleanupMode::NoEmpty); + state.init_code(&address_a, code_a.clone()).unwrap(); + state.init_code(&address_b, code_b.clone()).unwrap(); + state.add_balance(&sender, &U256::from(100_000), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); @@ -985,7 +985,7 @@ mod tests { }; assert_eq!(gas_left, U256::from(73_237)); - assert_eq!(state.storage_at(&address_a, &H256::from(&U256::from(0x23))), H256::from(&U256::from(1))); + assert_eq!(state.storage_at(&address_a, &H256::from(&U256::from(0x23))).unwrap(), H256::from(&U256::from(1))); } // test is incorrect, mk @@ -1019,7 +1019,7 @@ mod tests { params.code = Some(Arc::new(code.clone())); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.init_code(&address, code); + state.init_code(&address, code).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -1030,8 +1030,8 @@ mod tests { }; assert_eq!(gas_left, U256::from(59_870)); - assert_eq!(state.storage_at(&address, &H256::from(&U256::zero())), H256::from(&U256::from(1))); - assert_eq!(state.storage_at(&address, &H256::from(&U256::one())), H256::from(&U256::from(1))); + assert_eq!(state.storage_at(&address, &H256::from(&U256::zero())).unwrap(), H256::from(&U256::from(1))); + assert_eq!(state.storage_at(&address, &H256::from(&U256::one())).unwrap(), H256::from(&U256::from(1))); } // test is incorrect, mk @@ -1052,7 +1052,7 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(18), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(18), CleanupMode::NoEmpty).unwrap(); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1069,10 +1069,10 @@ mod tests { assert_eq!(executed.cumulative_gas_used, U256::from(41_301)); assert_eq!(executed.logs.len(), 0); assert_eq!(executed.contracts_created.len(), 0); - assert_eq!(state.balance(&sender), U256::from(1)); - assert_eq!(state.balance(&contract), U256::from(17)); - assert_eq!(state.nonce(&sender), U256::from(1)); - assert_eq!(state.storage_at(&contract, &H256::new()), H256::from(&U256::from(1))); + assert_eq!(state.balance(&sender).unwrap(), U256::from(1)); + assert_eq!(state.balance(&contract).unwrap(), U256::from(17)); + assert_eq!(state.nonce(&sender).unwrap(), U256::from(1)); + assert_eq!(state.storage_at(&contract, &H256::new()).unwrap(), H256::from(&U256::from(1))); } evm_test!{test_transact_invalid_nonce: test_transact_invalid_nonce_jit, test_transact_invalid_nonce_int} @@ -1090,7 +1090,7 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty).unwrap(); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1123,7 +1123,7 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty).unwrap(); let mut info = EnvInfo::default(); info.gas_used = U256::from(20_000); info.gas_limit = U256::from(100_000); @@ -1158,7 +1158,7 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100_017), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from(100_017), CleanupMode::NoEmpty).unwrap(); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1193,7 +1193,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from_str("0de0b6b3a7640000").unwrap()); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from_str("152d02c7e14af6800000").unwrap(), CleanupMode::NoEmpty); + state.add_balance(&sender, &U256::from_str("152d02c7e14af6800000").unwrap(), CleanupMode::NoEmpty).unwrap(); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index faac16821fdf1e95bcbec3c38ee2c911c1c5e6ec..893ba03bece63ee04e8d32f7ea9920796015249b 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -17,7 +17,7 @@ //! Transaction Execution environment. use util::*; use action_params::{ActionParams, ActionValue}; -use state::{State, Substate}; +use state::{Backend as StateBackend, State, Substate}; use engines::Engine; use env_info::EnvInfo; use executive::*; @@ -57,8 +57,10 @@ impl OriginInfo { } /// Implementation of evm Externalities. -pub struct Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { - state: &'a mut State, +pub struct Externalities<'a, T: 'a, V: 'a, B: 'a> + where T: Tracer, V: VMTracer, B: StateBackend +{ + state: &'a mut State, env_info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, @@ -71,10 +73,12 @@ pub struct Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { vm_tracer: &'a mut V, } -impl<'a, T, V> Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { - #[cfg_attr(feature="dev", allow(too_many_arguments))] +impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B> + where T: Tracer, V: VMTracer, B: StateBackend +{ /// Basic `Externalities` constructor. - pub fn new(state: &'a mut State, + #[cfg_attr(feature="dev", allow(too_many_arguments))] + pub fn new(state: &'a mut State, env_info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, @@ -101,26 +105,28 @@ impl<'a, T, V> Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { } } -impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { - fn storage_at(&self, key: &H256) -> H256 { +impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B> + where T: Tracer, V: VMTracer, B: StateBackend +{ + fn storage_at(&self, key: &H256) -> trie::Result { self.state.storage_at(&self.origin_info.address, key) } - fn set_storage(&mut self, key: H256, value: H256) { + fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()> { self.state.set_storage(&self.origin_info.address, key, value) } - fn exists(&self, address: &Address) -> bool { + fn exists(&self, address: &Address) -> trie::Result { self.state.exists(address) } - fn exists_and_not_null(&self, address: &Address) -> bool { + fn exists_and_not_null(&self, address: &Address) -> trie::Result { self.state.exists_and_not_null(address) } - fn origin_balance(&self) -> U256 { self.balance(&self.origin_info.address) } + fn origin_balance(&self) -> trie::Result { self.balance(&self.origin_info.address) } - fn balance(&self, address: &Address) -> U256 { + fn balance(&self, address: &Address) -> trie::Result { self.state.balance(address) } @@ -143,7 +149,13 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT fn create(&mut self, gas: &U256, value: &U256, code: &[u8]) -> ContractCreateResult { // create new contract address - let address = contract_address(&self.origin_info.address, &self.state.nonce(&self.origin_info.address)); + let address = match self.state.nonce(&self.origin_info.address) { + Ok(nonce) => contract_address(&self.origin_info.address, &nonce), + Err(e) => { + debug!(target: "ext", "Database corruption encountered: {:?}", e); + return ContractCreateResult::Failed + } + }; // prepare the params let params = ActionParams { @@ -160,7 +172,10 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT call_type: CallType::None, }; - self.state.inc_nonce(&self.origin_info.address); + if let Err(e) = self.state.inc_nonce(&self.origin_info.address) { + debug!(target: "ext", "Database corruption encountered: {:?}", e); + return ContractCreateResult::Failed + } let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth); // TODO: handle internal error separately @@ -185,6 +200,14 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT ) -> MessageCallResult { trace!(target: "externalities", "call"); + let code_res = self.state.code(code_address) + .and_then(|code| self.state.code_hash(code_address).map(|hash| (code, hash))); + + let (code, code_hash) = match code_res { + Ok((code, hash)) => (code, hash), + Err(_) => return MessageCallResult::Failed, + }; + let mut params = ActionParams { sender: sender_address.clone(), address: receive_address.clone(), @@ -193,8 +216,8 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT origin: self.origin_info.origin.clone(), gas: *gas, gas_price: self.origin_info.gas_price, - code: self.state.code(code_address), - code_hash: self.state.code_hash(code_address), + code: code, + code_hash: code_hash, data: Some(data.to_vec()), call_type: call_type, }; @@ -211,12 +234,12 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT } } - fn extcode(&self, address: &Address) -> Arc { - self.state.code(address).unwrap_or_else(|| Arc::new(vec![])) + fn extcode(&self, address: &Address) -> trie::Result> { + Ok(self.state.code(address)?.unwrap_or_else(|| Arc::new(vec![]))) } - fn extcodesize(&self, address: &Address) -> usize { - self.state.code_size(address).unwrap_or(0) + fn extcodesize(&self, address: &Address) -> trie::Result { + Ok(self.state.code_size(address)?.unwrap_or(0)) } #[cfg_attr(feature="dev", allow(match_ref_pats))] @@ -251,10 +274,7 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT handle_copy(copy); - let mut code = vec![]; - code.extend_from_slice(data); - - self.state.init_code(&self.origin_info.address, code); + self.state.init_code(&self.origin_info.address, data.to_vec())?; Ok(*gas - return_cost) } } @@ -271,19 +291,26 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT }); } - fn suicide(&mut self, refund_address: &Address) { + fn suicide(&mut self, refund_address: &Address) -> trie::Result<()> { let address = self.origin_info.address.clone(); - let balance = self.balance(&address); + let balance = self.balance(&address)?; if &address == refund_address { // TODO [todr] To be consistent with CPP client we set balance to 0 in that case. - self.state.sub_balance(&address, &balance); + self.state.sub_balance(&address, &balance)?; } else { trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance); - self.state.transfer_balance(&address, refund_address, &balance, self.substate.to_cleanup_mode(&self.schedule)); + self.state.transfer_balance( + &address, + refund_address, + &balance, + self.substate.to_cleanup_mode(&self.schedule) + )?; } self.tracer.trace_suicide(address, balance, refund_address.clone()); self.substate.suicides.insert(address); + + Ok(()) } fn schedule(&self) -> &Schedule { @@ -346,7 +373,7 @@ mod tests { } struct TestSetup { - state: GuardedTempResult, + state: GuardedTempResult>, engine: Arc, sub_state: Substate, env_info: EnvInfo @@ -479,7 +506,7 @@ mod tests { { let vm_factory = Default::default(); let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer); - ext.suicide(refund_account); + ext.suicide(refund_account).unwrap(); } assert_eq!(setup.sub_state.suicides.len(), 1); diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 1ec98708e317d5cab33ff04ab96771e7797d0eae..60b9dcb4678c19a12d685d4d0154462238d443fe 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -276,7 +276,7 @@ impl Decodable for Header { number: r.val_at(8)?, gas_limit: r.val_at(9)?, gas_used: r.val_at(10)?, - timestamp: r.val_at(11)?, + timestamp: min(r.val_at::(11)?, u64::max_value().into()).as_u64(), extra_data: r.val_at(12)?, seal: vec![], hash: RefCell::new(Some(r.as_raw().sha3())), diff --git a/ethcore/src/json_tests/chain.rs b/ethcore/src/json_tests/chain.rs index f4b888060b1927af4faa29153a0d43feb870b889..221fa1aba0fc04745d7a9da648c9018cd96354d8 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -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 { 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) { diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 6716fcc2a69df9a670aca610395602b89da6ed28..844fa08f59786a8a5bdc703fab95e70d81136c75 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -16,7 +16,7 @@ use super::test_common::*; use action_params::ActionParams; -use state::{State, Substate}; +use state::{Backend as StateBackend, State, Substate}; use executive::*; use engines::Engine; use env_info::EnvInfo; @@ -51,15 +51,19 @@ impl From for CallCreate { /// Tiny wrapper around executive externalities. /// Stores callcreates. -struct TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { - ext: Externalities<'a, T, V>, +struct TestExt<'a, T: 'a, V: 'a, B: 'a> + where T: Tracer, V: VMTracer, B: StateBackend +{ + ext: Externalities<'a, T, V, B>, callcreates: Vec, contract_address: Address } -impl<'a, T, V> TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { +impl<'a, T: 'a, V: 'a, B: 'a> TestExt<'a, T, V, B> + where T: Tracer, V: VMTracer, B: StateBackend +{ fn new( - state: &'a mut State, + state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, vm_factory: &'a Factory, @@ -70,37 +74,39 @@ impl<'a, T, V> TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer { address: Address, tracer: &'a mut T, vm_tracer: &'a mut V, - ) -> Self { - TestExt { - contract_address: contract_address(&address, &state.nonce(&address)), + ) -> trie::Result { + Ok(TestExt { + contract_address: contract_address(&address, &state.nonce(&address)?), ext: Externalities::new(state, info, engine, vm_factory, depth, origin_info, substate, output, tracer, vm_tracer), callcreates: vec![] - } + }) } } -impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer { - fn storage_at(&self, key: &H256) -> H256 { +impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B> + where T: Tracer, V: VMTracer, B: StateBackend +{ + fn storage_at(&self, key: &H256) -> trie::Result { self.ext.storage_at(key) } - fn set_storage(&mut self, key: H256, value: H256) { + fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()> { self.ext.set_storage(key, value) } - fn exists(&self, address: &Address) -> bool { + fn exists(&self, address: &Address) -> trie::Result { self.ext.exists(address) } - fn exists_and_not_null(&self, address: &Address) -> bool { + fn exists_and_not_null(&self, address: &Address) -> trie::Result { self.ext.exists_and_not_null(address) } - fn balance(&self, address: &Address) -> U256 { + fn balance(&self, address: &Address) -> trie::Result { self.ext.balance(address) } - fn origin_balance(&self) -> U256 { + fn origin_balance(&self) -> trie::Result { self.ext.origin_balance() } @@ -137,11 +143,11 @@ impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer { MessageCallResult::Success(*gas) } - fn extcode(&self, address: &Address) -> Arc { + fn extcode(&self, address: &Address) -> trie::Result> { self.ext.extcode(address) } - fn extcodesize(&self, address: &Address) -> usize { + fn extcodesize(&self, address: &Address) -> trie::Result { self.ext.extcodesize(address) } @@ -153,7 +159,7 @@ impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer { self.ext.ret(gas, data) } - fn suicide(&mut self, refund_address: &Address) { + fn suicide(&mut self, refund_address: &Address) -> trie::Result<()> { self.ext.suicide(refund_address) } @@ -195,6 +201,19 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { fail = true }; + macro_rules! try_fail { + ($e: expr) => { + match $e { + Ok(x) => x, + Err(e) => { + let msg = format!("Internal error: {}", e); + fail_unless(false, &msg); + continue + } + } + } + } + let out_of_gas = vm.out_of_gas(); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); @@ -211,7 +230,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { // execute let (res, callcreates) = { - let mut ex = TestExt::new( + let mut ex = try_fail!(TestExt::new( &mut state, &info, &engine, @@ -223,7 +242,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { params.address.clone(), &mut tracer, &mut vm_tracer, - ); + )); let mut evm = vm_factory.create(params.gas); let res = evm.exec(params, &mut ex); // a return in finalize will not alter callcreates @@ -242,14 +261,19 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { for (address, account) in vm.post_state.unwrap().into_iter() { let address = address.into(); let code: Vec = account.code.into(); - fail_unless(state.code(&address).as_ref().map_or_else(|| code.is_empty(), |c| &**c == &code), "code is incorrect"); - fail_unless(state.balance(&address) == account.balance.into(), "balance is incorrect"); - fail_unless(state.nonce(&address) == account.nonce.into(), "nonce is incorrect"); - account.storage.into_iter().foreach(|(k, v)| { + let found_code = try_fail!(state.code(&address)); + let found_balance = try_fail!(state.balance(&address)); + let found_nonce = try_fail!(state.nonce(&address)); + + fail_unless(found_code.as_ref().map_or_else(|| code.is_empty(), |c| &**c == &code), "code is incorrect"); + fail_unless(found_balance == account.balance.into(), "balance is incorrect"); + fail_unless(found_nonce == account.nonce.into(), "nonce is incorrect"); + for (k, v) in account.storage { let key: U256 = k.into(); let value: U256 = v.into(); - fail_unless(state.storage_at(&address, &From::from(key)) == From::from(value), "storage is incorrect"); - }); + let found_storage = try_fail!(state.storage_at(&address, &From::from(key))); + fail_unless(found_storage == From::from(value), "storage is incorrect"); + } } let calls: Option> = vm.calls.map(|c| c.into_iter().map(From::from).collect()); diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index be5247340c5d6d2abd378c25a9ca887b0b8dc23e..a78e2120f0b28fbccb469c37a9b1401a5db5aa64 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -79,7 +79,6 @@ //! cargo build --release //! ``` - extern crate ethcore_io as io; extern crate rustc_serialize; extern crate crypto; @@ -106,6 +105,7 @@ extern crate lru_cache; extern crate ethcore_stratum; extern crate ethabi; extern crate hardware_wallet; +extern crate stats; #[macro_use] extern crate log; @@ -139,14 +139,14 @@ pub mod snapshot; pub mod action_params; pub mod db; pub mod verification; +pub mod state; +pub mod env_info; #[macro_use] pub mod evm; mod cache_manager; mod blooms; mod basic_types; -mod env_info; mod pod_account; -mod state; mod state_db; mod account_db; mod builtin; diff --git a/ethcore/src/migrations/mod.rs b/ethcore/src/migrations/mod.rs index 7b71cc76923fafd577bf069d93a50ad27b5000a4..6cc4a13a8aa625f24a65c58102217e3982acc335 100644 --- a/ethcore/src/migrations/mod.rs +++ b/ethcore/src/migrations/mod.rs @@ -26,3 +26,6 @@ pub use self::v9::Extract; mod v10; pub use self::v10::ToV10; + +mod v11; +pub use self::v11::TO_V11; diff --git a/ethcore/src/migrations/v10.rs b/ethcore/src/migrations/v10.rs index 1da6673bbd0186118b0ab56e5c019db5723d91a5..a152657780a44b3e58b10ecc30539b54292f43d2 100644 --- a/ethcore/src/migrations/v10.rs +++ b/ethcore/src/migrations/v10.rs @@ -70,7 +70,7 @@ pub fn generate_bloom(source: Arc, 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)?; diff --git a/ethcore/src/migrations/v11.rs b/ethcore/src/migrations/v11.rs new file mode 100644 index 0000000000000000000000000000000000000000..e33de6170c023eff0c81f5ee1e44e23138d649af --- /dev/null +++ b/ethcore/src/migrations/v11.rs @@ -0,0 +1,26 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Adds a seventh column for node information. + +use util::migration::ChangeColumns; + +/// The migration from v10 to v11. +pub const TO_V11: ChangeColumns = ChangeColumns { + pre_columns: Some(6), + post_columns: Some(7), + version: 11, +}; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 06949f4bd7864bf5155a2022f6ea0983705d0c98..99c22f88e36ea059989459ee8e04a29fc00ea911 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -215,8 +215,6 @@ pub struct Miner { sealing_block_last_request: Mutex, // for sealing... options: MinerOptions, - /// Does the node perform internal (without work) sealing. - pub seals_internally: bool, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -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()), @@ -310,7 +307,7 @@ impl Miner { } /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. - pub fn pending_state(&self) -> Option { + pub fn pending_state(&self) -> Option> { self.sealing_work.lock().queue.peek_last_ref().map(|b| b.block().fields().state.clone()) } @@ -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; @@ -714,17 +711,20 @@ impl MinerService for Miner { let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; let sender = t.sender(); - let balance = state.balance(&sender); + let balance = state.balance(&sender).map_err(ExecutionError::from)?; let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); + state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty) + .map_err(ExecutionError::from)?; } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, &*self.engine, client.vm_factory()).transact(t, options)?; // TODO gav move this into Executive. - ret.state_diff = original_state.map(|original| state.diff_from(original)); + if let Some(original) = original_state { + ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?); + } Ok(ret) }, @@ -732,54 +732,56 @@ impl MinerService for Miner { } } - fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { + // TODO: The `chain.latest_x` actually aren't infallible, they just panic on corruption. + // TODO: return trie::Result here, or other. + fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { self.from_pending_block( chain.chain_info().best_block_number, - || chain.latest_balance(address), - |b| b.block().fields().state.balance(address) + || Some(chain.latest_balance(address)), + |b| b.block().fields().state.balance(address).ok(), ) } - fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 { + fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option { self.from_pending_block( chain.chain_info().best_block_number, - || chain.latest_storage_at(address, position), - |b| b.block().fields().state.storage_at(address, position) + || Some(chain.latest_storage_at(address, position)), + |b| b.block().fields().state.storage_at(address, position).ok(), ) } - fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { + fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { self.from_pending_block( chain.chain_info().best_block_number, - || chain.latest_nonce(address), - |b| b.block().fields().state.nonce(address) + || Some(chain.latest_nonce(address)), + |b| b.block().fields().state.nonce(address).ok(), ) } - fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { + fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option> { self.from_pending_block( chain.chain_info().best_block_number, - || chain.latest_code(address), - |b| b.block().fields().state.code(address).map(|c| (*c).clone()) + || Some(chain.latest_code(address)), + |b| b.block().fields().state.code(address).ok().map(|c| c.map(|c| (&*c).clone())) ) } 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 +916,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 +986,7 @@ impl MinerService for Miner { } } - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { let queue = self.transaction_queue.lock(); match self.options.pending_set { PendingSet::AlwaysQueue => queue.find(hash), @@ -992,14 +994,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 +1073,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") } } } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index d88a261f3685901801ab0e0818216805c49a9ee2..403aca760a3798331c13c8e7d914152bf76f399e 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -32,7 +32,7 @@ //! use ethcore::miner::{Miner, MinerService}; //! //! fn main() { -//! let miner: Miner = Miner::with_spec(ðereum::new_frontier()); +//! let miner: Miner = Miner::with_spec(ðereum::new_foundation()); //! // get status //! assert_eq!(miner.status().transactions_in_pending_queue, 0); //! @@ -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; + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Get a list of all pending transactions in the queue. fn pending_transactions(&self) -> Vec; @@ -181,19 +181,19 @@ pub trait MinerService : Send + Sync { fn sensible_gas_limit(&self) -> U256 { 21000.into() } /// Latest account balance in pending state. - fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; + fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; /// Call into contract code using pending state. fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result; /// Get storage value in pending state. - fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256; + fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> Option; /// Get account nonce in pending state. - fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; + fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; /// Get contract code in pending state. - fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option; + fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option>; } /// Mining status diff --git a/ethcore/src/miner/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs index f3281dadea2c0c4b8a000dcc4a7dbb40030a7dc1..5a7ab04e920fe8dd29a37f59b5e1f6edb463ddfd 100644 --- a/ethcore/src/miner/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use types::ids::BlockId; use client::MiningBlockChainClient; use transaction::SignedTransaction; use util::{U256, Uint, Mutex}; @@ -45,7 +46,7 @@ impl ServiceTransactionChecker { debug_assert_eq!(tx.gas_price, U256::zero()); if let Some(ref contract) = *self.contract.lock() { - let do_call = |a, d| client.call_contract(a, d); + let do_call = |a, d| client.call_contract(BlockId::Latest, a, d); contract.certified(&do_call, &tx.sender()) } else { Err("contract is not configured".to_owned()) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index ef7094a90c806d30d08c2c269c998d0dc0cc4a92..d686a3ff54e7de4e0ea8e79afdac016c85ac15e5 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1109,7 +1109,7 @@ impl TransactionQueue { r } - /// Return all ready transactions. + /// Return all future transactions. pub fn future_transactions(&self) -> Vec { 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 { - self.by_hash.get(hash).map(|tx| tx.transaction.clone()) + pub fn find(&self, hash: &H256) -> Option { + 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 diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index c45eae4114897fbbd701e75aca3b7bc37cddce84..b48fb94454bd9ca71ef185c65122ad2f5527a5eb 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -57,6 +57,7 @@ pub struct ClientService { client: Arc, snapshot: Arc, panic_handler: Arc, + database: Arc, _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 + Send>) -> Result<(), IoError> { self.io_service.register_handler(handler) @@ -152,6 +155,9 @@ impl ClientService { pub fn add_notify(&self, notify: Arc) { self.client.add_notify(notify); } + + /// Get a handle to the database. + pub fn db(&self) -> Arc { self.database.clone() } } impl MayPanic for ClientService { diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index 1c46985af73aa0ce0d48890cb4a13eea14ea9b5c..555ee665bbdacbd14cdf17156649cf1d692dbd3e 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -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()); -} \ No newline at end of file +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 77fd552513b76fb0523d62c372750796a1a0414a..b50d0563cf9f77686d4931b0c2c53f8e1fd1a1cd 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -24,7 +24,7 @@ use executive::Executive; use trace::{NoopTracer, NoopVMTracer}; use action_params::{ActionValue, ActionParams}; use types::executed::CallType; -use state::{State, Substate}; +use state::{Backend, State, Substate}; use env_info::EnvInfo; use pod_state::*; use account_db::*; @@ -160,7 +160,7 @@ impl Spec { fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap) -> Arc { match engine_spec { ethjson::spec::Engine::Null => Arc::new(NullEngine::new(params, builtins)), - ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), + ethjson::spec::Engine::InstantSeal(instant) => Arc::new(InstantSeal::new(params, instant.params.registrar.map_or_else(Address::new, Into::into), builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."), @@ -394,6 +394,6 @@ mod tests { let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(), Default::default()).unwrap(); let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); - assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()), expected); + assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()).unwrap(), expected); } } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 6e5297bc43c56b31278261cdf54443c7c4f347ad..ebdf36d89637cd3a2d2c68b5e1c755c8c38fd993 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -169,22 +169,16 @@ impl Account { /// Get (and cache) the contents of the trie's storage at `key`. /// Takes modifed storage into account. - pub fn storage_at(&self, db: &HashDB, key: &H256) -> H256 { + pub fn storage_at(&self, db: &HashDB, key: &H256) -> trie::Result { if let Some(value) = self.cached_storage_at(key) { - return value; + return Ok(value); } - let db = SecTrieDB::new(db, &self.storage_root) - .expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \ - SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \ - using it will not fail."); - - let item: U256 = match db.get_with(key, ::rlp::decode) { - Ok(x) => x.unwrap_or_else(U256::zero), - Err(e) => panic!("Encountered potential DB corruption: {}", e), - }; + let db = SecTrieDB::new(db, &self.storage_root)?; + + let item: U256 = db.get_with(key, ::rlp::decode)?.unwrap_or_else(U256::zero); let value: H256 = item.into(); self.storage_cache.borrow_mut().insert(key.clone(), value.clone()); - value + Ok(value) } /// Get cached storage value if any. Returns `None` if the @@ -345,24 +339,19 @@ impl Account { } /// Commit the `storage_changes` to the backing DB and update `storage_root`. - pub fn commit_storage(&mut self, trie_factory: &TrieFactory, db: &mut HashDB) { - let mut t = trie_factory.from_existing(db, &mut self.storage_root) - .expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \ - SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \ - using it will not fail."); + pub fn commit_storage(&mut self, trie_factory: &TrieFactory, db: &mut HashDB) -> trie::Result<()> { + let mut t = trie_factory.from_existing(db, &mut self.storage_root)?; for (k, v) in self.storage_changes.drain() { // cast key and value to trait type, // so we can call overloaded `to_bytes` method - let res = match v.is_zero() { - true => t.remove(&k), - false => t.insert(&k, &encode(&U256::from(&*v))), + match v.is_zero() { + true => t.remove(&k)?, + false => t.insert(&k, &encode(&U256::from(&*v)))?, }; - if let Err(e) = res { - warn!("Encountered potential DB corruption: {}", e); - } self.storage_cache.borrow_mut().insert(k, v); } + Ok(()) } /// Commit any unsaved code. `code_hash` will always return the hash of the `code_cache` after this. @@ -494,7 +483,7 @@ mod tests { let rlp = { let mut a = Account::new_contract(69.into(), 0.into()); a.set_storage(H256::from(&U256::from(0x00u64)), H256::from(&U256::from(0x1234u64))); - a.commit_storage(&Default::default(), &mut db); + a.commit_storage(&Default::default(), &mut db).unwrap(); a.init_code(vec![]); a.commit_code(&mut db); a.rlp() @@ -502,8 +491,8 @@ mod tests { let a = Account::from_rlp(&rlp); assert_eq!(a.storage_root().unwrap().hex(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2"); - assert_eq!(a.storage_at(&db.immutable(), &H256::from(&U256::from(0x00u64))), H256::from(&U256::from(0x1234u64))); - assert_eq!(a.storage_at(&db.immutable(), &H256::from(&U256::from(0x01u64))), H256::new()); + assert_eq!(a.storage_at(&db.immutable(), &H256::from(&U256::from(0x00u64))).unwrap(), H256::from(&U256::from(0x1234u64))); + assert_eq!(a.storage_at(&db.immutable(), &H256::from(&U256::from(0x01u64))).unwrap(), H256::new()); } #[test] @@ -532,7 +521,7 @@ mod tests { let mut db = AccountDBMut::new(&mut db, &Address::new()); a.set_storage(0.into(), 0x1234.into()); assert_eq!(a.storage_root(), None); - a.commit_storage(&Default::default(), &mut db); + a.commit_storage(&Default::default(), &mut db).unwrap(); assert_eq!(a.storage_root().unwrap().hex(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2"); } @@ -542,11 +531,11 @@ mod tests { let mut db = MemoryDB::new(); let mut db = AccountDBMut::new(&mut db, &Address::new()); a.set_storage(0.into(), 0x1234.into()); - a.commit_storage(&Default::default(), &mut db); + a.commit_storage(&Default::default(), &mut db).unwrap(); a.set_storage(1.into(), 0x1234.into()); - a.commit_storage(&Default::default(), &mut db); + a.commit_storage(&Default::default(), &mut db).unwrap(); a.set_storage(1.into(), 0.into()); - a.commit_storage(&Default::default(), &mut db); + a.commit_storage(&Default::default(), &mut db).unwrap(); assert_eq!(a.storage_root().unwrap().hex(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2"); } diff --git a/ethcore/src/state/backend.rs b/ethcore/src/state/backend.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ab620b0e9de82ee10112c642109b1a13c920931 --- /dev/null +++ b/ethcore/src/state/backend.rs @@ -0,0 +1,223 @@ +// 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 . + +//! A minimal "state backend" trait: an abstraction over the sources of data +//! a blockchain state may draw upon. +//! +//! Currently assumes a very specific DB + cache structure, but +//! should become general over time to the point where not even a +//! merkle trie is strictly necessary. + +use std::collections::{HashSet, HashMap}; +use std::sync::Arc; + +use state::Account; +use util::{Address, MemoryDB, Mutex, H256}; +use util::hashdb::{AsHashDB, HashDB, DBValue}; + +/// State backend. See module docs for more details. +pub trait Backend: Send { + /// Treat the backend as a read-only hashdb. + fn as_hashdb(&self) -> &HashDB; + + /// Treat the backend as a writeable hashdb. + fn as_hashdb_mut(&mut self) -> &mut HashDB; + + /// Add an account entry to the cache. + fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool); + + /// Add a global code cache entry. This doesn't need to worry about canonicality because + /// it simply maps hashes to raw code and will always be correct in the absence of + /// hash collisions. + fn cache_code(&self, hash: H256, code: Arc>); + + /// Get basic copy of the cached account. Not required to include storage. + /// Returns 'None' if cache is disabled or if the account is not cached. + fn get_cached_account(&self, addr: &Address) -> Option>; + + /// Get value from a cached account. + /// `None` is passed to the closure if the account entry cached + /// is known not to exist. + /// `None` is returned if the entry is not cached. + fn get_cached(&self, a: &Address, f: F) -> Option + where F: FnOnce(Option<&mut Account>) -> U; + + /// Get cached code based on hash. + fn get_cached_code(&self, hash: &H256) -> Option>>; + + /// Note that an account with the given address is non-null. + fn note_non_null_account(&self, address: &Address); + + /// Check whether an account is known to be empty. Returns true if known to be + /// empty, false otherwise. + fn is_known_null(&self, address: &Address) -> bool; +} + +/// A raw backend used to check proofs of execution. +/// +/// This doesn't delete anything since execution proofs won't have mangled keys +/// and we want to avoid collisions. +// TODO: when account lookup moved into backends, this won't rely as tenuously on intended +// usage. +#[derive(Clone, PartialEq)] +pub struct ProofCheck(MemoryDB); + +impl ProofCheck { + /// Create a new `ProofCheck` backend from the given state items. + pub fn new(proof: &[DBValue]) -> Self { + let mut db = MemoryDB::new(); + for item in proof { db.insert(item); } + ProofCheck(db) + } +} + +impl HashDB for ProofCheck { + fn keys(&self) -> HashMap { self.0.keys() } + fn get(&self, key: &H256) -> Option { + self.0.get(key) + } + + fn contains(&self, key: &H256) -> bool { + self.0.contains(key) + } + + fn insert(&mut self, value: &[u8]) -> H256 { + self.0.insert(value) + } + + fn emplace(&mut self, key: H256, value: DBValue) { + self.0.emplace(key, value) + } + + fn remove(&mut self, _key: &H256) { } +} + +impl Backend for ProofCheck { + fn as_hashdb(&self) -> &HashDB { self } + fn as_hashdb_mut(&mut self) -> &mut HashDB { self } + fn add_to_account_cache(&mut self, _addr: Address, _data: Option, _modified: bool) {} + fn cache_code(&self, _hash: H256, _code: Arc>) {} + fn get_cached_account(&self, _addr: &Address) -> Option> { None } + fn get_cached(&self, _a: &Address, _f: F) -> Option + where F: FnOnce(Option<&mut Account>) -> U + { + None + } + fn get_cached_code(&self, _hash: &H256) -> Option>> { None } + fn note_non_null_account(&self, _address: &Address) {} + fn is_known_null(&self, _address: &Address) -> bool { false } +} + +/// Proving state backend. +/// This keeps track of all state values loaded during usage of this backend. +/// The proof-of-execution can be extracted with `extract_proof`. +/// +/// This doesn't cache anything or rely on the canonical state caches. +pub struct Proving { + base: H, // state we're proving values from. + changed: MemoryDB, // changed state via insertions. + proof: Mutex>, +} + +impl HashDB for Proving { + fn keys(&self) -> HashMap { + let mut keys = self.base.as_hashdb().keys(); + keys.extend(self.changed.keys()); + keys + } + + fn get(&self, key: &H256) -> Option { + match self.base.as_hashdb().get(key) { + Some(val) => { + self.proof.lock().insert(val.clone()); + Some(val) + } + None => self.changed.get(key) + } + } + + fn contains(&self, key: &H256) -> bool { + self.get(key).is_some() + } + + fn insert(&mut self, value: &[u8]) -> H256 { + self.changed.insert(value) + } + + fn emplace(&mut self, key: H256, value: DBValue) { + self.changed.emplace(key, value) + } + + fn remove(&mut self, key: &H256) { + // only remove from `changed` + if self.changed.contains(key) { + self.changed.remove(key) + } + } +} + +impl Backend for Proving { + fn as_hashdb(&self) -> &HashDB { + self + } + + fn as_hashdb_mut(&mut self) -> &mut HashDB { + self + } + + fn add_to_account_cache(&mut self, _: Address, _: Option, _: bool) { } + + fn cache_code(&self, _: H256, _: Arc>) { } + + fn get_cached_account(&self, _: &Address) -> Option> { None } + + fn get_cached(&self, _: &Address, _: F) -> Option + where F: FnOnce(Option<&mut Account>) -> U + { + None + } + + fn get_cached_code(&self, _: &H256) -> Option>> { None } + fn note_non_null_account(&self, _: &Address) { } + fn is_known_null(&self, _: &Address) -> bool { false } +} + +impl Proving { + /// Create a new `Proving` over a base database. + /// This will store all values ever fetched from that base. + pub fn new(base: H) -> Self { + Proving { + base: base, + changed: MemoryDB::new(), + proof: Mutex::new(HashSet::new()), + } + } + + /// Consume the backend, extracting the gathered proof. + pub fn extract_proof(self) -> Vec { + self.proof.into_inner().into_iter().collect() + } +} + +impl Clone for Proving { + fn clone(&self) -> Self { + Proving { + base: self.base.clone(), + changed: self.changed.clone(), + proof: Mutex::new(self.proof.lock().clone()), + } + } +} diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 1b899d935e923ebd21d5df68cf873ee5a5febd0f..3c5a3bc09a888af14e617bb979efb0b908dd2ff8 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -14,6 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! A mutable state representation suitable to execute transactions. +//! Generic over a `Backend`. Deals with `Account`s. +//! Unconfirmed sub-states are managed with `checkpoint`s which may be canonicalized +//! or rolled back. + use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry; @@ -26,18 +31,23 @@ use factory::Factories; use trace::FlatTrace; use pod_account::*; use pod_state::{self, PodState}; +use types::executed::{Executed, ExecutionError}; use types::state_diff::StateDiff; use transaction::SignedTransaction; use state_db::StateDB; use util::*; +use util::trie; use util::trie::recorder::Recorder; mod account; mod substate; +pub mod backend; + pub use self::account::Account; +pub use self::backend::Backend; pub use self::substate::Substate; /// Used to return information about an `State::apply` operation. @@ -51,6 +61,17 @@ pub struct ApplyOutcome { /// Result type for the execution ("application") of a transaction. pub type ApplyResult = Result; +/// Return type of proof validity check. +#[derive(Debug, Clone)] +pub enum ProvedExecution { + /// Proof wasn't enough to complete execution. + BadProof, + /// The transaction failed, but not due to a bad proof. + Failed(ExecutionError), + /// The transaction successfully completd with the given proof. + Complete(Executed), +} + #[derive(Eq, PartialEq, Clone, Copy, Debug)] /// Account modification state. Used to check if the account was /// Modified in between commits and overall. @@ -141,6 +162,39 @@ impl AccountEntry { } } +/// Check the given proof of execution. +/// `Err(ExecutionError::Internal)` indicates failure, everything else indicates +/// a successful proof (as the transaction itself may be poorly chosen). +pub fn check_proof( + proof: &[::util::DBValue], + root: H256, + transaction: &SignedTransaction, + engine: &Engine, + env_info: &EnvInfo, +) -> ProvedExecution { + let backend = self::backend::ProofCheck::new(proof); + let mut factories = Factories::default(); + factories.accountdb = ::account_db::Factory::Plain; + + let res = State::from_existing( + backend, + root, + engine.account_start_nonce(), + factories + ); + + let mut state = match res { + Ok(state) => state, + Err(_) => return ProvedExecution::BadProof, + }; + + match state.execute(env_info, engine, transaction, false) { + Ok(executed) => ProvedExecution::Complete(executed), + Err(ExecutionError::Internal(_)) => ProvedExecution::BadProof, + Err(e) => ProvedExecution::Failed(e), + } +} + /// Representation of the entire state of all accounts in the system. /// /// `State` can work together with `StateDB` to share account cache. @@ -186,8 +240,8 @@ impl AccountEntry { /// checkpoint can be discateded with `discard_checkpoint`. All of the orignal /// backed-up values are moved into a parent checkpoint (if any). /// -pub struct State { - db: StateDB, +pub struct State { + db: B, root: H256, cache: RefCell>, // The original account is preserved in @@ -203,20 +257,24 @@ enum RequireCache { Code, } +/// Mode of dealing with null accounts. #[derive(PartialEq)] pub enum CleanupMode<'a> { + /// Create accounts which would be null. ForceCreate, + /// Don't delete null accounts upon touching, but also don't create them. NoEmpty, + /// Add encountered null accounts to the provided kill-set, to be deleted later. KillEmpty(&'a mut HashSet
), } const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \ Therefore creating a SecTrieDB with this state's root will not fail."; -impl State { +impl State { /// Creates new state with empty state root #[cfg(test)] - pub fn new(mut db: StateDB, account_start_nonce: U256, factories: Factories) -> State { + pub fn new(mut db: B, account_start_nonce: U256, factories: Factories) -> State { let mut root = H256::new(); { // init trie and reset root too null @@ -234,7 +292,7 @@ impl State { } /// Creates new state with existing state root - pub fn from_existing(db: StateDB, root: H256, account_start_nonce: U256, factories: Factories) -> Result { + pub fn from_existing(db: B, root: H256, account_start_nonce: U256, factories: Factories) -> Result, TrieError> { if !db.as_hashdb().contains(&root) { return Err(TrieError::InvalidStateRoot(root)); } @@ -251,6 +309,19 @@ impl State { Ok(state) } + /// Swap the current backend for another. + // TODO: [rob] find a less hacky way to avoid duplication of `Client::state_at`. + pub fn replace_backend(self, backend: T) -> State { + State { + db: backend, + root: self.root, + cache: self.cache, + checkpoints: self.checkpoints, + account_start_nonce: self.account_start_nonce, + factories: self.factories, + } + } + /// Create a recoverable checkpoint of this state. pub fn checkpoint(&mut self) { self.checkpoints.get_mut().push(HashMap::new()); @@ -328,7 +399,7 @@ impl State { } /// Destroy the current object and return root and database. - pub fn drop(mut self) -> (H256, StateDB) { + pub fn drop(mut self) -> (H256, B) { self.propagate_to_global_cache(); (self.root, self.db) } @@ -350,37 +421,37 @@ impl State { } /// Determine whether an account exists. - pub fn exists(&self, a: &Address) -> bool { + pub fn exists(&self, a: &Address) -> trie::Result { // Bloom filter does not contain empty accounts, so it is important here to // check if account exists in the database directly before EIP-161 is in effect. self.ensure_cached(a, RequireCache::None, false, |a| a.is_some()) } /// Determine whether an account exists and if not empty. - pub fn exists_and_not_null(&self, a: &Address) -> bool { + pub fn exists_and_not_null(&self, a: &Address) -> trie::Result { self.ensure_cached(a, RequireCache::None, false, |a| a.map_or(false, |a| !a.is_null())) } /// Get the balance of account `a`. - pub fn balance(&self, a: &Address) -> U256 { + pub fn balance(&self, a: &Address) -> trie::Result { self.ensure_cached(a, RequireCache::None, true, |a| a.as_ref().map_or(U256::zero(), |account| *account.balance())) } /// Get the nonce of account `a`. - pub fn nonce(&self, a: &Address) -> U256 { + pub fn nonce(&self, a: &Address) -> trie::Result { self.ensure_cached(a, RequireCache::None, true, |a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce())) } /// Get the storage root of account `a`. - pub fn storage_root(&self, a: &Address) -> Option { + pub fn storage_root(&self, a: &Address) -> trie::Result> { self.ensure_cached(a, RequireCache::None, true, |a| a.as_ref().and_then(|account| account.storage_root().cloned())) } /// Mutate storage of account `address` so that it is `value` for `key`. - pub fn storage_at(&self, address: &Address, key: &H256) -> H256 { + pub fn storage_at(&self, address: &Address, key: &H256) -> trie::Result { // Storage key search and update works like this: // 1. If there's an entry for the account in the local cache check for the key and return it if found. // 2. If there's an entry for the account in the global cache check for the key or load it into that account. @@ -394,42 +465,46 @@ impl State { match maybe_acc.account { Some(ref account) => { if let Some(value) = account.cached_storage_at(key) { - return value; + return Ok(value); } else { local_account = Some(maybe_acc); } }, - _ => return H256::new(), + _ => return Ok(H256::new()), } } // check the global cache and and cache storage key there if found, - // otherwise cache the account localy and cache storage key there. - if let Some(result) = self.db.get_cached(address, |acc| acc.map_or(H256::new(), |a| { + let trie_res = self.db.get_cached(address, |acc| match acc { + None => Ok(H256::new()), + Some(a) => { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address)); a.storage_at(account_db.as_hashdb(), key) - })) { - return result; + } + }); + + match trie_res { + None => {} + Some(res) => return res, } + + // otherwise cache the account localy and cache storage key there. if let Some(ref mut acc) = local_account { if let Some(ref account) = acc.account { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(address)); return account.storage_at(account_db.as_hashdb(), key) } else { - return H256::new() + return Ok(H256::new()) } } } - // check bloom before any requests to trie - if !self.db.check_non_null_bloom(address) { return H256::zero() } + // check if the account could exist before any requests to trie + if self.db.is_known_null(address) { return Ok(H256::zero()) } // account is not found in the global cache, get from the DB and insert into local let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); - let maybe_acc = match db.get_with(address, Account::from_rlp) { - Ok(acc) => acc, - Err(e) => panic!("Potential DB corruption encountered: {}", e), - }; - let r = maybe_acc.as_ref().map_or(H256::new(), |a| { + let maybe_acc = db.get_with(address, Account::from_rlp)?; + let r = maybe_acc.as_ref().map_or(Ok(H256::new()), |a| { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address)); a.storage_at(account_db.as_hashdb(), key) }); @@ -438,86 +513,92 @@ impl State { } /// Get accounts' code. - pub fn code(&self, a: &Address) -> Option> { + pub fn code(&self, a: &Address) -> trie::Result>> { self.ensure_cached(a, RequireCache::Code, true, |a| a.as_ref().map_or(None, |a| a.code().clone())) } - pub fn code_hash(&self, a: &Address) -> H256 { + /// Get an account's code hash. + pub fn code_hash(&self, a: &Address) -> trie::Result { self.ensure_cached(a, RequireCache::None, true, |a| a.as_ref().map_or(SHA3_EMPTY, |a| a.code_hash())) } /// Get accounts' code size. - pub fn code_size(&self, a: &Address) -> Option { + pub fn code_size(&self, a: &Address) -> trie::Result> { self.ensure_cached(a, RequireCache::CodeSize, true, |a| a.as_ref().and_then(|a| a.code_size())) } /// Add `incr` to the balance of account `a`. #[cfg_attr(feature="dev", allow(single_match))] - pub fn add_balance(&mut self, a: &Address, incr: &U256, cleanup_mode: CleanupMode) { - trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)); + pub fn add_balance(&mut self, a: &Address, incr: &U256, cleanup_mode: CleanupMode) -> trie::Result<()> { + trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)?); let is_value_transfer = !incr.is_zero(); - if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)) { - self.require(a, false).add_balance(incr); + if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)?) { + self.require(a, false)?.add_balance(incr); } else { match cleanup_mode { - CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a) && !self.exists_and_not_null(a) { + CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a)? && !self.exists_and_not_null(a)? { set.insert(a.clone()); }, _ => {} } } + + Ok(()) } /// Subtract `decr` from the balance of account `a`. - pub fn sub_balance(&mut self, a: &Address, decr: &U256) { - trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)); - if !decr.is_zero() || !self.exists(a) { - self.require(a, false).sub_balance(decr); + pub fn sub_balance(&mut self, a: &Address, decr: &U256) -> trie::Result<()> { + trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)?); + if !decr.is_zero() || !self.exists(a)? { + self.require(a, false)?.sub_balance(decr); } + + Ok(()) } /// Subtracts `by` from the balance of `from` and adds it to that of `to`. - pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, cleanup_mode: CleanupMode) { - self.sub_balance(from, by); - self.add_balance(to, by, cleanup_mode); + pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, cleanup_mode: CleanupMode) -> trie::Result<()> { + self.sub_balance(from, by)?; + self.add_balance(to, by, cleanup_mode)?; + Ok(()) } /// Increment the nonce of account `a` by 1. - pub fn inc_nonce(&mut self, a: &Address) { - self.require(a, false).inc_nonce() + pub fn inc_nonce(&mut self, a: &Address) -> trie::Result<()> { + self.require(a, false).map(|mut x| x.inc_nonce()) } /// Mutate storage of account `a` so that it is `value` for `key`. - pub fn set_storage(&mut self, a: &Address, key: H256, value: H256) { - if self.storage_at(a, &key) != value { - self.require(a, false).set_storage(key, value) + pub fn set_storage(&mut self, a: &Address, key: H256, value: H256) -> trie::Result<()> { + if self.storage_at(a, &key)? != value { + self.require(a, false)?.set_storage(key, value) } + + Ok(()) } /// Initialise the code of account `a` so that it is `code`. /// NOTE: Account should have been created with `new_contract`. - pub fn init_code(&mut self, a: &Address, code: Bytes) { - self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).init_code(code); + pub fn init_code(&mut self, a: &Address, code: Bytes) -> trie::Result<()> { + self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{})?.init_code(code); + Ok(()) } /// Reset the code of account `a` so that it is `code`. - pub fn reset_code(&mut self, a: &Address, code: Bytes) { - self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).reset_code(code); + pub fn reset_code(&mut self, a: &Address, code: Bytes) -> trie::Result<()> { + self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{})?.reset_code(code); + Ok(()) } - /// Execute a given transaction. + /// Execute a given transaction, producing a receipt and an optional trace. /// This will change the state accordingly. pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, tracing: bool) -> ApplyResult { // let old = self.to_pod(); - let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true }; - let vm_factory = self.factories.vm.clone(); - let e = Executive::new(self, env_info, engine, &vm_factory).transact(t, options)?; - - // TODO uncomment once to_pod() works correctly. + let e = self.execute(env_info, engine, t, tracing)?; // trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod())); let state_root = if env_info.number < engine.params().eip98_transition { self.commit()?; @@ -530,13 +611,22 @@ impl State { Ok(ApplyOutcome{receipt: receipt, trace: e.trace}) } + // Execute a given transaction. + fn execute(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, tracing: bool) -> Result { + let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true }; + let vm_factory = self.factories.vm.clone(); + + Executive::new(self, env_info, engine, &vm_factory).transact(t, options) + } + + /// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit. /// `accounts` is mutable because we may need to commit the code or storage and record that. #[cfg_attr(feature="dev", allow(match_ref_pats))] #[cfg_attr(feature="dev", allow(needless_borrow))] fn commit_into( factories: &Factories, - db: &mut StateDB, + db: &mut B, root: &mut H256, accounts: &mut HashMap ) -> Result<(), Error> { @@ -546,7 +636,7 @@ impl State { let addr_hash = account.address_hash(address); { let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); - account.commit_storage(&factories.trie, account_db.as_hashdb_mut()); + account.commit_storage(&factories.trie, account_db.as_hashdb_mut())?; account.commit_code(account_db.as_hashdb_mut()); } if !account.is_empty() { @@ -616,29 +706,33 @@ impl State { })) } - fn query_pod(&mut self, query: &PodState) { - for (address, pod_account) in query.get().into_iter() - .filter(|&(a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some())) - { + fn query_pod(&mut self, query: &PodState) -> trie::Result<()> { + for (address, pod_account) in query.get() { + if !self.ensure_cached(address, RequireCache::Code, true, |a| a.is_some())? { + continue + } + // needs to be split into two parts for the refcell code here // to work. for key in pod_account.storage.keys() { - self.storage_at(address, key); + self.storage_at(address, key)?; } } + + Ok(()) } /// Returns a `StateDiff` describing the difference from `orig` to `self`. /// Consumes self. - pub fn diff_from(&self, orig: State) -> StateDiff { + pub fn diff_from(&self, orig: State) -> trie::Result { let pod_state_post = self.to_pod(); let mut state_pre = orig; - state_pre.query_pod(&pod_state_post); - pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post) + state_pre.query_pod(&pod_state_post)?; + Ok(pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post)) } // load required account data from the databases. - fn update_account_cache(require: RequireCache, account: &mut Account, state_db: &StateDB, db: &HashDB) { + fn update_account_cache(require: RequireCache, account: &mut Account, state_db: &B, db: &HashDB) { match (account.is_cached(), require) { (true, _) | (false, RequireCache::None) => {} (false, require) => { @@ -668,16 +762,16 @@ impl State { /// Check caches for required data /// First searches for account in the local, then the shared cache. /// Populates local cache if nothing found. - fn ensure_cached(&self, a: &Address, require: RequireCache, check_bloom: bool, f: F) -> U + fn ensure_cached(&self, a: &Address, require: RequireCache, check_null: bool, f: F) -> trie::Result where F: Fn(Option<&Account>) -> U { // check local cache first if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { if let Some(ref mut account) = maybe_acc.account { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); - return f(Some(account)); + return Ok(f(Some(account))); } - return f(None); + return Ok(f(None)); } // check global cache let result = self.db.get_cached(a, |mut acc| { @@ -688,49 +782,43 @@ impl State { f(acc.map(|a| &*a)) }); match result { - Some(r) => r, + Some(r) => Ok(r), None => { - // first check bloom if it is not in database for sure - if check_bloom && !self.db.check_non_null_bloom(a) { return f(None); } + // first check if it is not in database for sure + if check_null && self.db.is_known_null(a) { return Ok(f(None)); } // not found in the global cache, get from the DB and insert into local - let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); - let mut maybe_acc = match db.get_with(a, Account::from_rlp) { - Ok(acc) => acc, - Err(e) => panic!("Potential DB corruption encountered: {}", e), - }; + let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root)?; + let mut maybe_acc = db.get_with(a, Account::from_rlp)?; if let Some(ref mut account) = maybe_acc.as_mut() { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); } let r = f(maybe_acc.as_ref()); self.insert_cache(a, AccountEntry::new_clean(maybe_acc)); - r + Ok(r) } } } /// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too. - fn require<'a>(&'a self, a: &Address, require_code: bool) -> RefMut<'a, Account> { + fn require<'a>(&'a self, a: &Address, require_code: bool) -> trie::Result> { self.require_or_from(a, require_code, || Account::new_basic(U256::from(0u8), self.account_start_nonce), |_|{}) } /// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too. /// If it doesn't exist, make account equal the evaluation of `default`. - fn require_or_from<'a, F: FnOnce() -> Account, G: FnOnce(&mut Account)>(&'a self, a: &Address, require_code: bool, default: F, not_default: G) - -> RefMut<'a, Account> + fn require_or_from<'a, F, G>(&'a self, a: &Address, require_code: bool, default: F, not_default: G) -> trie::Result> + where F: FnOnce() -> Account, G: FnOnce(&mut Account), { let contains_key = self.cache.borrow().contains_key(a); if !contains_key { match self.db.get_cached_account(a) { Some(acc) => self.insert_cache(a, AccountEntry::new_clean_cached(acc)), None => { - let maybe_acc = if self.db.check_non_null_bloom(a) { - let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); - match db.get_with(a, Account::from_rlp) { - Ok(acc) => AccountEntry::new_clean(acc), - Err(e) => panic!("Potential DB corruption encountered: {}", e), - } + let maybe_acc = if !self.db.is_known_null(a) { + let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root)?; + AccountEntry::new_clean(db.get_with(a, Account::from_rlp)?) } else { AccountEntry::new_clean(None) }; @@ -741,7 +829,7 @@ impl State { self.note_cache(a); // at this point the entry is guaranteed to be in the cache. - RefMut::map(self.cache.borrow_mut(), |c| { + Ok(RefMut::map(self.cache.borrow_mut(), |c| { let mut entry = c.get_mut(a).expect("entry known to exist in the cache; qed"); match &mut entry.account { @@ -762,18 +850,18 @@ impl State { }, _ => panic!("Required account must always exist; qed"), } - }) + })) } } -// LES state proof implementations. -impl State { +// State proof implementations; useful for light client protocols. +impl State { /// Prove an account's existence or nonexistence in the state trie. /// Returns a merkle proof of the account's trie node with all nodes before `from_level` /// omitted or an encountered trie error. /// Requires a secure trie to be used for accurate results. /// `account_key` == sha3(address) - pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result, Box> { + pub fn prove_account(&self, account_key: H256, from_level: u32) -> trie::Result> { let mut recorder = Recorder::with_depth(from_level); let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; trie.get_with(&account_key, &mut recorder)?; @@ -786,7 +874,7 @@ impl State { /// `from_level` omitted. Requires a secure trie to be used for correctness. /// `account_key` == sha3(address) /// `storage_key` == sha3(key) - pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result, Box> { + pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> trie::Result> { // TODO: probably could look into cache somehow but it's keyed by // address, not sha3(address). let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; @@ -801,7 +889,7 @@ impl State { /// Get code by address hash. /// Only works when backed by a secure trie. - pub fn code_by_address_hash(&self, account_key: H256) -> Result, Box> { + pub fn code_by_address_hash(&self, account_key: H256) -> trie::Result> { let trie = TrieDB::new(self.db.as_hashdb(), &self.root)?; let mut acc = match trie.get_with(&account_key, Account::from_rlp)? { Some(acc) => acc, @@ -813,14 +901,16 @@ impl State { } } -impl fmt::Debug for State { +impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.cache.borrow()) } } -impl Clone for State { - fn clone(&self) -> State { +// TODO: cloning for `State` shouldn't be possible in general; Remove this and use +// checkpoints where possible. +impl Clone for State { + fn clone(&self) -> State { let cache = { let mut cache: HashMap = HashMap::new(); for (key, val) in self.cache.borrow().iter() { @@ -884,7 +974,7 @@ mod tests { data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(), }.sign(&secret(), None); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -914,13 +1004,13 @@ mod tests { let temp = RandomTempPath::new(); let mut state = { let mut state = get_temp_state_in(temp.as_path()); - assert_eq!(state.exists(&a), false); - state.inc_nonce(&a); + assert_eq!(state.exists(&a).unwrap(), false); + state.inc_nonce(&a).unwrap(); state.commit().unwrap(); state.clone() }; - state.inc_nonce(&a); + state.inc_nonce(&a).unwrap(); state.commit().unwrap(); } @@ -944,7 +1034,7 @@ mod tests { data: FromHex::from_hex("5b600056").unwrap(), }.sign(&secret(), None); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -981,8 +1071,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1024,7 +1114,7 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1108,7 +1198,7 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap()); + state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap()).unwrap(); let result = state.apply(&info, engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1151,8 +1241,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()).unwrap(); let result = state.apply(&info, engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1213,8 +1303,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); + state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()).unwrap(); let result = state.apply(&info, engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1272,8 +1362,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1312,9 +1402,9 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1372,8 +1462,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1427,8 +1517,8 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds. - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()).unwrap(); // not enough funds. + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1470,9 +1560,9 @@ mod tests { data: vec![],//600480600b6000396000f35b600056 }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1526,10 +1616,10 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap()); - state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap()).unwrap(); + state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1601,10 +1691,10 @@ mod tests { data: vec![],//600480600b6000396000f35b600056 }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap()); - state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()).unwrap(); + state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap()).unwrap(); + state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()).unwrap(); + state.add_balance(&t.sender(), &(100.into()), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1674,9 +1764,9 @@ mod tests { data: vec![], }.sign(&secret(), None); - state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap()); - state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty); - state.add_balance(&t.sender(), &100.into(), CleanupMode::NoEmpty); + state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap()).unwrap(); + state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty).unwrap(); + state.add_balance(&t.sender(), &100.into(), CleanupMode::NoEmpty).unwrap(); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1713,16 +1803,16 @@ mod tests { let temp = RandomTempPath::new(); let (root, db) = { let mut state = get_temp_state_in(temp.as_path()); - state.require_or_from(&a, false, ||Account::new_contract(42.into(), 0.into()), |_|{}); - state.init_code(&a, vec![1, 2, 3]); - assert_eq!(state.code(&a), Some(Arc::new([1u8, 2, 3].to_vec()))); + state.require_or_from(&a, false, ||Account::new_contract(42.into(), 0.into()), |_|{}).unwrap(); + state.init_code(&a, vec![1, 2, 3]).unwrap(); + assert_eq!(state.code(&a).unwrap(), Some(Arc::new([1u8, 2, 3].to_vec()))); state.commit().unwrap(); - assert_eq!(state.code(&a), Some(Arc::new([1u8, 2, 3].to_vec()))); + assert_eq!(state.code(&a).unwrap(), Some(Arc::new([1u8, 2, 3].to_vec()))); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert_eq!(state.code(&a), Some(Arc::new([1u8, 2, 3].to_vec()))); + assert_eq!(state.code(&a).unwrap(), Some(Arc::new([1u8, 2, 3].to_vec()))); } #[test] @@ -1731,13 +1821,13 @@ mod tests { let temp = RandomTempPath::new(); let (root, db) = { let mut state = get_temp_state_in(temp.as_path()); - state.set_storage(&a, H256::from(&U256::from(1u64)), H256::from(&U256::from(69u64))); + state.set_storage(&a, H256::from(&U256::from(1u64)), H256::from(&U256::from(69u64))).unwrap(); state.commit().unwrap(); state.drop() }; let s = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert_eq!(s.storage_at(&a, &H256::from(&U256::from(1u64))), H256::from(&U256::from(69u64))); + assert_eq!(s.storage_at(&a, &H256::from(&U256::from(1u64))).unwrap(), H256::from(&U256::from(69u64))); } #[test] @@ -1746,16 +1836,16 @@ mod tests { let temp = RandomTempPath::new(); let (root, db) = { let mut state = get_temp_state_in(temp.as_path()); - state.inc_nonce(&a); - state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); + state.inc_nonce(&a).unwrap(); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty).unwrap(); state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(69u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert_eq!(state.balance(&a), U256::from(69u64)); - assert_eq!(state.nonce(&a), U256::from(1u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); + assert_eq!(state.nonce(&a).unwrap(), U256::from(1u64)); } #[test] @@ -1763,16 +1853,16 @@ mod tests { let a = Address::zero(); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - assert_eq!(state.exists(&a), false); - assert_eq!(state.exists_and_not_null(&a), false); - state.inc_nonce(&a); - assert_eq!(state.exists(&a), true); - assert_eq!(state.exists_and_not_null(&a), true); - assert_eq!(state.nonce(&a), U256::from(1u64)); + assert_eq!(state.exists(&a).unwrap(), false); + assert_eq!(state.exists_and_not_null(&a).unwrap(), false); + state.inc_nonce(&a).unwrap(); + assert_eq!(state.exists(&a).unwrap(), true); + assert_eq!(state.exists_and_not_null(&a).unwrap(), true); + assert_eq!(state.nonce(&a).unwrap(), U256::from(1u64)); state.kill_account(&a); - assert_eq!(state.exists(&a), false); - assert_eq!(state.exists_and_not_null(&a), false); - assert_eq!(state.nonce(&a), U256::from(0u64)); + assert_eq!(state.exists(&a).unwrap(), false); + assert_eq!(state.exists_and_not_null(&a).unwrap(), false); + assert_eq!(state.nonce(&a).unwrap(), U256::from(0u64)); } #[test] @@ -1782,13 +1872,13 @@ mod tests { let db = get_temp_state_db_in(path.as_path()); let (root, db) = { let mut state = State::new(db, U256::from(0), Default::default()); - state.add_balance(&a, &U256::default(), CleanupMode::NoEmpty); // create an empty account + state.add_balance(&a, &U256::default(), CleanupMode::NoEmpty).unwrap(); // create an empty account state.commit().unwrap(); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert!(!state.exists(&a)); - assert!(!state.exists_and_not_null(&a)); + assert!(!state.exists(&a).unwrap()); + assert!(!state.exists_and_not_null(&a).unwrap()); } #[test] @@ -1798,13 +1888,13 @@ mod tests { let db = get_temp_state_db_in(path.as_path()); let (root, db) = { let mut state = State::new(db, U256::from(0), Default::default()); - state.add_balance(&a, &U256::default(), CleanupMode::ForceCreate); // create an empty account + state.add_balance(&a, &U256::default(), CleanupMode::ForceCreate).unwrap(); // create an empty account state.commit().unwrap(); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert!(state.exists(&a)); - assert!(!state.exists_and_not_null(&a)); + assert!(state.exists(&a).unwrap()); + assert!(!state.exists_and_not_null(&a).unwrap()); } #[test] @@ -1813,27 +1903,27 @@ mod tests { let temp = RandomTempPath::new(); let (root, db) = { let mut state = get_temp_state_in(temp.as_path()); - state.inc_nonce(&a); + state.inc_nonce(&a).unwrap(); state.commit().unwrap(); - assert_eq!(state.exists(&a), true); - assert_eq!(state.nonce(&a), U256::from(1u64)); + assert_eq!(state.exists(&a).unwrap(), true); + assert_eq!(state.nonce(&a).unwrap(), U256::from(1u64)); state.drop() }; let (root, db) = { let mut state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert_eq!(state.exists(&a), true); - assert_eq!(state.nonce(&a), U256::from(1u64)); + assert_eq!(state.exists(&a).unwrap(), true); + assert_eq!(state.nonce(&a).unwrap(), U256::from(1u64)); state.kill_account(&a); state.commit().unwrap(); - assert_eq!(state.exists(&a), false); - assert_eq!(state.nonce(&a), U256::from(0u64)); + assert_eq!(state.exists(&a).unwrap(), false); + assert_eq!(state.nonce(&a).unwrap(), U256::from(0u64)); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); - assert_eq!(state.exists(&a), false); - assert_eq!(state.nonce(&a), U256::from(0u64)); + assert_eq!(state.exists(&a).unwrap(), false); + assert_eq!(state.nonce(&a).unwrap(), U256::from(0u64)); } #[test] @@ -1842,20 +1932,20 @@ mod tests { let mut state = state_result.reference_mut(); let a = Address::zero(); let b = 1u64.into(); - state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(69u64)); - state.sub_balance(&a, &U256::from(42u64)); - assert_eq!(state.balance(&a), U256::from(27u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); + state.sub_balance(&a, &U256::from(42u64)).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(27u64)); state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(27u64)); - state.transfer_balance(&a, &b, &U256::from(18u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(9u64)); - assert_eq!(state.balance(&b), U256::from(18u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(27u64)); + state.transfer_balance(&a, &b, &U256::from(18u64), CleanupMode::NoEmpty).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(9u64)); + assert_eq!(state.balance(&b).unwrap(), U256::from(18u64)); state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(9u64)); - assert_eq!(state.balance(&b), U256::from(18u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(9u64)); + assert_eq!(state.balance(&b).unwrap(), U256::from(18u64)); } #[test] @@ -1863,16 +1953,16 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); let a = Address::zero(); - state.inc_nonce(&a); - assert_eq!(state.nonce(&a), U256::from(1u64)); - state.inc_nonce(&a); - assert_eq!(state.nonce(&a), U256::from(2u64)); + state.inc_nonce(&a).unwrap(); + assert_eq!(state.nonce(&a).unwrap(), U256::from(1u64)); + state.inc_nonce(&a).unwrap(); + assert_eq!(state.nonce(&a).unwrap(), U256::from(2u64)); state.commit().unwrap(); - assert_eq!(state.nonce(&a), U256::from(2u64)); - state.inc_nonce(&a); - assert_eq!(state.nonce(&a), U256::from(3u64)); + assert_eq!(state.nonce(&a).unwrap(), U256::from(2u64)); + state.inc_nonce(&a).unwrap(); + assert_eq!(state.nonce(&a).unwrap(), U256::from(3u64)); state.commit().unwrap(); - assert_eq!(state.nonce(&a), U256::from(3u64)); + assert_eq!(state.nonce(&a).unwrap(), U256::from(3u64)); } #[test] @@ -1880,11 +1970,11 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); let a = Address::zero(); - assert_eq!(state.balance(&a), U256::from(0u64)); - assert_eq!(state.nonce(&a), U256::from(0u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(0u64)); + assert_eq!(state.nonce(&a).unwrap(), U256::from(0u64)); state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(0u64)); - assert_eq!(state.nonce(&a), U256::from(0u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(0u64)); + assert_eq!(state.nonce(&a).unwrap(), U256::from(0u64)); } #[test] @@ -1892,7 +1982,7 @@ mod tests { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); let a = Address::zero(); - state.require(&a, false); + state.require(&a, false).unwrap(); state.commit().unwrap(); assert_eq!(state.root().hex(), "0ce23f3c809de377b008a4a3ee94a0834aac8bec1f86e28ffe4fdb5a15b0c785"); } @@ -1903,15 +1993,15 @@ mod tests { let mut state = state_result.reference_mut(); let a = Address::zero(); state.checkpoint(); - state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.discard_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.checkpoint(); - state.add_balance(&a, &U256::from(1u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(70u64)); + state.add_balance(&a, &U256::from(1u64), CleanupMode::NoEmpty).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(70u64)); state.revert_to_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); } #[test] @@ -1921,12 +2011,12 @@ mod tests { let a = Address::zero(); state.checkpoint(); state.checkpoint(); - state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty).unwrap(); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.discard_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); + assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); state.revert_to_checkpoint(); - assert_eq!(state.balance(&a), U256::from(0)); + assert_eq!(state.balance(&a).unwrap(), U256::from(0)); } #[test] @@ -1943,14 +2033,14 @@ mod tests { let mut state = state.reference().clone(); let a: Address = 0xa.into(); - state.init_code(&a, b"abcdefg".to_vec()); - state.add_balance(&a, &256.into(), CleanupMode::NoEmpty); - state.set_storage(&a, 0xb.into(), 0xc.into()); + state.init_code(&a, b"abcdefg".to_vec()).unwrap();; + state.add_balance(&a, &256.into(), CleanupMode::NoEmpty).unwrap(); + state.set_storage(&a, 0xb.into(), 0xc.into()).unwrap(); let mut new_state = state.clone(); - new_state.set_storage(&a, 0xb.into(), 0xd.into()); + new_state.set_storage(&a, 0xb.into(), 0xd.into()).unwrap(); - new_state.diff_from(state); + new_state.diff_from(state).unwrap(); } } diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index dbf9e2f55ebcf98738b2da9bb4088ced12ada374..85d40109903729bcef8a26f1c311d0d047702825 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -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 state::{self, 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, 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::>(); @@ -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"); @@ -165,18 +166,6 @@ impl StateDB { bloom } - pub fn check_non_null_bloom(&self, address: &Address) -> bool { - trace!(target: "account_bloom", "Check account bloom: {:?}", address); - let bloom = self.account_bloom.lock(); - bloom.check(&*address.sha3()) - } - - pub fn note_non_null_account(&self, address: &Address) { - trace!(target: "account_bloom", "Note account bloom: {:?}", address); - let mut bloom = self.account_bloom.lock(); - bloom.set(&*address.sha3()); - } - pub fn commit_bloom(batch: &mut DBTransaction, journal: BloomJournal) -> Result<(), UtilError> { assert!(journal.hash_functions <= 255); batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &[journal.hash_functions as u8]); @@ -305,12 +294,10 @@ impl StateDB { } } - /// Returns an interface to HashDB. pub fn as_hashdb(&self) -> &HashDB { self.db.as_hashdb() } - /// Returns an interface to mutable HashDB. pub fn as_hashdb_mut(&mut self) -> &mut HashDB { self.db.as_hashdb_mut() } @@ -365,56 +352,6 @@ impl StateDB { &*self.db } - /// Add a local cache entry. - /// The entry will be propagated to the global cache in `sync_cache`. - /// `modified` indicates that the entry was changed since being read from disk or global cache. - /// `data` can be set to an existing (`Some`), or non-existing account (`None`). - pub fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool) { - self.local_cache.push(CacheQueueItem { - address: addr, - account: data, - modified: modified, - }) - } - - /// Add a global code cache entry. This doesn't need to worry about canonicality because - /// it simply maps hashes to raw code and will always be correct in the absence of - /// hash collisions. - pub fn cache_code(&self, hash: H256, code: Arc>) { - let mut cache = self.code_cache.lock(); - - cache.insert(hash, code); - } - - /// Get basic copy of the cached account. Does not include storage. - /// Returns 'None' if cache is disabled or if the account is not cached. - pub fn get_cached_account(&self, addr: &Address) -> Option> { - let mut cache = self.account_cache.lock(); - if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { - return None; - } - cache.accounts.get_mut(addr).map(|a| a.as_ref().map(|a| a.clone_basic())) - } - - /// Get cached code based on hash. - #[cfg_attr(feature="dev", allow(map_clone))] - pub fn get_cached_code(&self, hash: &H256) -> Option>> { - let mut cache = self.code_cache.lock(); - - cache.get_mut(hash).map(|code| code.clone()) - } - - /// Get value from a cached account. - /// Returns 'None' if cache is disabled or if the account is not cached. - pub fn get_cached(&self, a: &Address, f: F) -> Option - where F: FnOnce(Option<&mut Account>) -> U { - let mut cache = self.account_cache.lock(); - if !Self::is_allowed(a, &self.parent_hash, &cache.modifications) { - return None; - } - cache.accounts.get_mut(a).map(|c| f(c.as_mut())) - } - /// Query how much memory is set aside for the accounts cache (in bytes). pub fn cache_size(&self) -> usize { self.cache_size @@ -455,11 +392,71 @@ impl StateDB { } } +impl state::Backend for StateDB { + fn as_hashdb(&self) -> &HashDB { + self.db.as_hashdb() + } + + fn as_hashdb_mut(&mut self) -> &mut HashDB { + self.db.as_hashdb_mut() + } + + fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool) { + self.local_cache.push(CacheQueueItem { + address: addr, + account: data, + modified: modified, + }) + } + + fn cache_code(&self, hash: H256, code: Arc>) { + let mut cache = self.code_cache.lock(); + + cache.insert(hash, code); + } + + fn get_cached_account(&self, addr: &Address) -> Option> { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { + return None; + } + cache.accounts.get_mut(addr).map(|a| a.as_ref().map(|a| a.clone_basic())) + } + + #[cfg_attr(feature="dev", allow(map_clone))] + fn get_cached_code(&self, hash: &H256) -> Option>> { + let mut cache = self.code_cache.lock(); + + cache.get_mut(hash).map(|code| code.clone()) + } + + fn get_cached(&self, a: &Address, f: F) -> Option + where F: FnOnce(Option<&mut Account>) -> U { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(a, &self.parent_hash, &cache.modifications) { + return None; + } + cache.accounts.get_mut(a).map(|c| f(c.as_mut())) + } + + fn note_non_null_account(&self, address: &Address) { + trace!(target: "account_bloom", "Note account bloom: {:?}", address); + let mut bloom = self.account_bloom.lock(); + bloom.set(&*address.sha3()); + } + + fn is_known_null(&self, address: &Address) -> bool { + trace!(target: "account_bloom", "Check account bloom: {:?}", address); + let bloom = self.account_bloom.lock(); + !bloom.check(&*address.sha3()) + } +} + #[cfg(test)] mod tests { use util::{U256, H256, FixedHash, Address, DBTransaction}; use tests::helpers::*; - use state::Account; + use state::{Account, Backend}; use util::log::init_log; #[test] @@ -477,7 +474,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 ] diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 6c2c02c2d8c9aad73e93b8abb5988717ca57af6d..3734c5520d1783807572cdf32bec2b38e1e59f05 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -16,7 +16,8 @@ use io::IoChannel; use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId}; -use state::CleanupMode; +use state::{self, State, CleanupMode}; +use executive::Executive; use ethereum; use block::IsBlock; use tests::helpers::*; @@ -27,7 +28,6 @@ use miner::Miner; use rlp::View; use spec::Spec; use views::BlockView; -use util::stats::Histogram; use ethkey::{KeyPair, Secret}; use transaction::{PendingTransaction, Transaction, Action, Condition}; use miner::MinerService; @@ -37,14 +37,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(); @@ -55,14 +55,14 @@ fn should_return_registrar() { let dir = RandomTempPath::new(); let spec = ethereum::new_morden(); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap()); let client = Client::new( ClientConfig::default(), &spec, - dir.as_path(), + client_db, Arc::new(Miner::with_spec(&spec)), IoChannel::disconnected(), - &db_config ).unwrap(); let params = client.additional_params(); let address = ¶ms["registrar"]; @@ -86,14 +86,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() { @@ -111,14 +111,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()); @@ -208,11 +208,11 @@ fn can_collect_garbage() { fn can_generate_gas_price_median() { let client_result = generate_dummy_client_with_data(3, 1, slice_into![1, 2, 3]); let client = client_result.reference(); - assert_eq!(Some(U256::from(2)), client.gas_price_median(3)); + assert_eq!(Some(&U256::from(2)), client.gas_price_corpus(3).median()); let client_result = generate_dummy_client_with_data(4, 1, slice_into![1, 4, 3, 2]); let client = client_result.reference(); - assert_eq!(Some(U256::from(3)), client.gas_price_median(4)); + assert_eq!(Some(&U256::from(3)), client.gas_price_corpus(3).median()); } #[test] @@ -220,8 +220,8 @@ fn can_generate_gas_price_histogram() { let client_result = generate_dummy_client_with_data(20, 1, slice_into![6354,8593,6065,4842,7845,7002,689,4958,4250,6098,5804,4320,643,8895,2296,8589,7145,2000,2512,1408]); let client = client_result.reference(); - let hist = client.gas_price_histogram(20, 5).unwrap(); - let correct_hist = Histogram { bucket_bounds: vec_into![643, 2294, 3945, 5596, 7247, 8898], counts: vec![4,2,4,6,4] }; + let hist = client.gas_price_corpus(20).histogram(5).unwrap(); + let correct_hist = ::stats::Histogram { bucket_bounds: vec_into![643, 2294, 3945, 5596, 7247, 8898], counts: vec![4,2,4,6,4] }; assert_eq!(hist, correct_hist); } @@ -230,7 +230,7 @@ fn empty_gas_price_histogram() { let client_result = generate_dummy_client_with_data(20, 0, slice_into![]); let client = client_result.reference(); - assert!(client.gas_price_histogram(20, 5).is_none()); + assert!(client.gas_price_corpus(20).histogram(5).is_none()); } #[test] @@ -277,13 +277,22 @@ 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); + b.block_mut().fields_mut().state.add_balance(&address, &5.into(), CleanupMode::NoEmpty).unwrap(); b.block_mut().fields_mut().state.commit().unwrap(); let b = b.close_and_lock().seal(&*test_spec.engine, vec![]).unwrap(); client.import_sealed_block(b).unwrap(); // account change is in the journal overlay @@ -291,8 +300,14 @@ 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(); - assert_eq!(client.state().balance(&address), 100.into()); + 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).unwrap(), 100.into()); } #[test] @@ -327,3 +342,43 @@ fn does_not_propagate_delayed_transactions() { assert_eq!(2, client.ready_transactions().len()); assert_eq!(2, client.miner().pending_transactions().len()); } + +#[test] +fn transaction_proof() { + use ::client::ProvingBlockChainClient; + + let client_result = generate_dummy_client(0); + let client = client_result.reference(); + let address = Address::random(); + let test_spec = Spec::new_test(); + 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).unwrap(); + b.block_mut().fields_mut().state.commit().unwrap(); + let b = b.close_and_lock().seal(&*test_spec.engine, vec![]).unwrap(); + client.import_sealed_block(b).unwrap(); // account change is in the journal overlay + } + + let transaction = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 5.into(), + data: Vec::new(), + }.fake_sign(address); + + let proof = client.prove_transaction(transaction.clone(), BlockId::Latest).unwrap(); + let backend = state::backend::ProofCheck::new(&proof); + + let mut factories = ::factory::Factories::default(); + factories.accountdb = ::account_db::Factory::Plain; // raw state values, no mangled keys. + let root = client.best_block_header().state_root(); + + let mut state = State::from_existing(backend, root, 0.into(), factories.clone()).unwrap(); + Executive::new(&mut state, &client.latest_env_info(), &*test_spec.engine, &factories.vm) + .transact(&transaction, Default::default()).unwrap(); + + assert_eq!(state.balance(&Address::default()).unwrap(), 5.into()); + assert_eq!(state.balance(&address).unwrap(), 95.into()); +} diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 8f008bc39c90890e5e111a96d49e4180703f080b..7de46b0b335093f5b74072f14067de3efbdda9d3 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -154,14 +154,14 @@ pub fn generate_dummy_client_with_spec_accounts_and_data(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) -> GuardedTempResult GuardedTempResult { } } -pub fn get_temp_state() -> GuardedTempResult { +pub fn get_temp_state() -> GuardedTempResult> { let temp = RandomTempPath::new(); let journal_db = get_temp_state_db_in(temp.as_path()); @@ -365,7 +365,7 @@ pub fn get_temp_state_db_in(path: &Path) -> StateDB { StateDB::new(journal_db, 5 * 1024 * 1024) } -pub fn get_temp_state_in(path: &Path) -> State { +pub fn get_temp_state_in(path: &Path) -> State<::state_db::StateDB> { let journal_db = get_temp_state_db_in(path); State::new(journal_db, U256::from(0), Default::default()) } @@ -456,5 +456,9 @@ pub fn get_default_ethash_params() -> EthashParams{ ecip1010_pause_transition: u64::max_value(), ecip1010_continue_transition: u64::max_value(), max_code_size: u64::max_value(), + max_gas_limit_transition: u64::max_value(), + max_gas_limit: U256::max_value(), + min_gas_price_transition: u64::max_value(), + min_gas_price: U256::zero(), } } diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index 206f1cb7ef557aa65db8fdfeea4bbdc575e12f55..68c8465197e6edda4d236f868487cc07b3778277 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -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 where T: DatabaseExtras { blooms: RwLock>, cache_manager: RwLock>, // db - tracesdb: Arc, + tracesdb: Arc, // config, bloom_config: BloomConfig, // tracing enabled @@ -126,8 +126,8 @@ impl BloomGroupDatabase for TraceDB where T: DatabaseExtras { impl TraceDB where T: DatabaseExtras { /// Creates new instance of `TraceDB`. - pub fn new(config: Config, tracesdb: Arc, extras: Arc) -> Self { - let mut batch = DBTransaction::new(&tracesdb); + pub fn new(config: Config, tracesdb: Arc, extras: Arc) -> 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()); @@ -305,7 +305,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { } fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec) -> Option { - let trace_position_deq = trace_position.into_iter().collect::>(); + let trace_position_deq = VecDeque::from(trace_position); self.extras.block_hash(block_number) .and_then(|block_hash| self.transactions_traces(&block_hash) .and_then(|traces| traces.into_iter().nth(tx_position)) @@ -404,8 +404,7 @@ impl TraceDatabase for TraceDB 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 { - 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); diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index 21858c1946297029be58d4ac858b350f16d49880..4301044ce0ff96e8a533ade328c181049a22ce1a 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -16,7 +16,7 @@ //! Transaction execution format module. -use util::{Bytes, U256, Address, U512}; +use util::{Bytes, U256, Address, U512, trie}; use rlp::*; use evm; use trace::{VMTrace, FlatTrace}; @@ -146,27 +146,33 @@ pub enum ExecutionError { got: U512 }, /// Returned when internal evm error occurs. - Internal, + Internal(String), /// Returned when generic transaction occurs TransactionMalformed(String), } +impl From> for ExecutionError { + fn from(err: Box) -> Self { + ExecutionError::Internal(format!("{}", err)) + } +} + impl fmt::Display for ExecutionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ExecutionError::*; let msg = match *self { - NotEnoughBaseGas { required, got } => + NotEnoughBaseGas { ref required, ref got } => format!("Not enough base gas. {} is required, but only {} paid", required, got), - BlockGasLimitReached { gas_limit, gas_used, gas } => + BlockGasLimitReached { ref gas_limit, ref gas_used, ref gas } => format!("Block gas limit reached. The limit is {}, {} has \ already been used, and {} more is required", gas_limit, gas_used, gas), - InvalidNonce { expected, got } => + InvalidNonce { ref expected, ref got } => format!("Invalid transaction nonce: expected {}, found {}", expected, got), - NotEnoughCash { required, got } => + NotEnoughCash { ref required, ref got } => format!("Cost of transaction exceeds sender balance. {} is required \ but the sender only has {}", required, got), - Internal => "Internal evm error".into(), + Internal(ref msg) => msg.clone(), TransactionMalformed(ref err) => format!("Malformed transaction: {}", err), }; @@ -184,6 +190,8 @@ pub enum CallError { StatePruned, /// Couldn't find an amount of gas that didn't result in an exception. Exceptional, + /// Corrupt state. + StateCorrupt, /// Error executing. Execution(ExecutionError), } @@ -202,6 +210,7 @@ impl fmt::Display for CallError { TransactionNotFound => "Transaction couldn't be found in the chain".into(), StatePruned => "Couldn't find the transaction block's state in the chain".into(), Exceptional => "An exception happened in the execution".into(), + StateCorrupt => "Stored state found to be corrupted.".into(), Execution(ref e) => format!("{}", e), }; diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 7eb16570cf4aa157c6358dbb459b165b8536de0e..ea3d326799a3e07d1a2d4efb5ff0fff35a8c8979 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -40,19 +40,25 @@ pub enum Error { Internal, } -impl From for Error { - fn from(e: EvmError) -> Self { - match e { +impl<'a> From<&'a EvmError> for Error { + fn from(e: &'a EvmError) -> Self { + match *e { EvmError::OutOfGas => Error::OutOfGas, EvmError::BadJumpDestination { .. } => Error::BadJumpDestination, EvmError::BadInstruction { .. } => Error::BadInstruction, EvmError::StackUnderflow { .. } => Error::StackUnderflow, EvmError::OutOfStack { .. } => Error::OutOfStack, - EvmError::Internal => Error::Internal, + EvmError::Internal(_) => Error::Internal, } } } +impl From for Error { + fn from(e: EvmError) -> Self { + Error::from(&e) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; diff --git a/ethcore/src/verification/queue/kind.rs b/ethcore/src/verification/queue/kind.rs index c10fce26d3a6feae0099ef2098d303bdbff30cb3..6ab6d692e302eaff6fc0ea9d04a4612b58026595 100644 --- a/ethcore/src/verification/queue/kind.rs +++ b/ethcore/src/verification/queue/kind.rs @@ -185,7 +185,7 @@ pub mod headers { type Verified = Header; fn create(input: Self::Input, engine: &Engine) -> Result { - verify_header_params(&input, engine).map(|_| input) + verify_header_params(&input, engine, true).map(|_| input) } fn verify(unverified: Self::Unverified, engine: &Engine, check_seal: bool) -> Result { diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 548a4e71be4e241bfeab9a741a1184f07fa21545..34a4ccbd00a10caf15fb41aa2f51fa99c4847317 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -51,12 +51,12 @@ impl HeapSizeOf for PreverifiedBlock { /// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> { - verify_header_params(&header, engine)?; + verify_header_params(&header, engine, true)?; verify_block_integrity(bytes, &header.transactions_root(), &header.uncles_hash())?; engine.verify_block_basic(&header, Some(bytes))?; for u in UntrustedRlp::new(bytes).at(2)?.iter().map(|rlp| rlp.as_val::
()) { let u = u?; - verify_header_params(&u, engine)?; + verify_header_params(&u, engine, false)?; engine.verify_block_basic(&u, None)?; } // Verify transactions. @@ -195,7 +195,7 @@ pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error> } /// Check basic header parameters. -pub fn verify_header_params(header: &Header, engine: &Engine) -> Result<(), Error> { +pub fn verify_header_params(header: &Header, engine: &Engine, is_full: bool) -> Result<(), Error> { if header.number() >= From::from(BlockNumber::max_value()) { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { max: Some(From::from(BlockNumber::max_value())), min: None, found: header.number() }))) } @@ -210,9 +210,11 @@ pub fn verify_header_params(header: &Header, engine: &Engine) -> Result<(), Erro if header.number() != 0 && header.extra_data().len() > maximum_extra_data_size { return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: None, max: Some(maximum_extra_data_size), found: header.extra_data().len() }))); } - let max_time = get_time().sec as u64 + 30; - if header.timestamp() > max_time { - return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: Some(max_time), min: None, found: header.timestamp() }))) + if is_full { + let max_time = get_time().sec as u64 + 30; + if header.timestamp() > max_time { + return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: Some(max_time), min: None, found: header.timestamp() }))) + } } Ok(()) } diff --git a/ethkey/src/extended.rs b/ethkey/src/extended.rs index 2d2e27b5b06cedb41c588a52a6a7765859516dba..c8f057a4ba6319de4fc531c0e0e8d81d3789e6c0 100644 --- a/ethkey/src/extended.rs +++ b/ethkey/src/extended.rs @@ -106,7 +106,7 @@ impl ExtendedSecret { } /// Private key component of the extended key. - pub fn secret(&self) -> &Secret { + pub fn as_raw(&self) -> &Secret { &self.secret } } @@ -127,7 +127,7 @@ impl ExtendedPublic { pub fn from_secret(secret: &ExtendedSecret) -> Result { Ok( ExtendedPublic::new( - derivation::point(**secret.secret())?, + derivation::point(**secret.as_raw())?, secret.chain_code.clone(), ) ) @@ -410,7 +410,7 @@ mod tests { let (private_seed, chain_code) = master_chain_basic(); let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code); let derived = f(extended_secret); - assert_eq!(**derived.secret(), test_private); + assert_eq!(**derived.as_raw(), test_private); } #[test] @@ -419,14 +419,14 @@ mod tests { let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into()); // hardened - assert_eq!(&**extended_secret.secret(), &*secret); - assert_eq!(&**extended_secret.derive(2147483648.into()).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); - assert_eq!(&**extended_secret.derive(2147483649.into()).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); + assert_eq!(&**extended_secret.as_raw(), &*secret); + assert_eq!(&**extended_secret.derive(2147483648.into()).as_raw(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); + assert_eq!(&**extended_secret.derive(2147483649.into()).as_raw(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); // normal - assert_eq!(&**extended_secret.derive(0.into()).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); - assert_eq!(&**extended_secret.derive(1.into()).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); - assert_eq!(&**extended_secret.derive(2.into()).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); + assert_eq!(&**extended_secret.derive(0.into()).as_raw(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); + assert_eq!(&**extended_secret.derive(1.into()).as_raw(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); + assert_eq!(&**extended_secret.derive(2.into()).as_raw(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed"); @@ -436,7 +436,7 @@ mod tests { Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(), 064.into(), ); - assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); + assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().as_raw(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); } #[test] @@ -461,7 +461,7 @@ mod tests { let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap(); let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into()); - assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).secret(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()); + assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).as_raw(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()); } #[test] diff --git a/ethkey/src/lib.rs b/ethkey/src/lib.rs index 3882b3559cf7d4d045db27a798682355f2aa2db5..60039b671f1d9e6f952b45219874aa81229c7927 100644 --- a/ethkey/src/lib.rs +++ b/ethkey/src/lib.rs @@ -44,6 +44,8 @@ pub trait Generator { fn generate(self) -> Result; } +pub mod math; + pub use self::brain::Brain; pub use self::error::Error; pub use self::keypair::{KeyPair, public_to_address}; @@ -51,7 +53,7 @@ pub use self::prefix::Prefix; pub use self::random::Random; pub use self::signature::{sign, verify_public, verify_address, recover, Signature}; pub use self::secret::Secret; -pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError}; +pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError, Derivation}; use bigint::hash::{H160, H256, H512}; diff --git a/ethkey/src/math.rs b/ethkey/src/math.rs new file mode 100644 index 0000000000000000000000000000000000000000..45e5d04e6f2a8321b3601f05186430467842fa63 --- /dev/null +++ b/ethkey/src/math.rs @@ -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 . + +use super::{SECP256K1, Public, Secret, Error}; +use secp256k1::key; +use secp256k1::constants::{GENERATOR_X, GENERATOR_Y}; + +/// Inplace multiply public key by secret key (EC point * scalar) +pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> { + let key_secret = secret.to_secp256k1_secret()?; + let mut key_public = to_secp256k1_public(public)?; + key_public.mul_assign(&SECP256K1, &key_secret)?; + set_public(public, &key_public); + Ok(()) +} + +/// Inplace add one public key to another (EC point + EC point) +pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> { + let mut key_public = to_secp256k1_public(public)?; + let other_public = to_secp256k1_public(other)?; + key_public.add_assign(&SECP256K1, &other_public)?; + set_public(public, &key_public); + Ok(()) +} + +/// Return base point of secp256k1 +pub fn generation_point() -> Public { + let mut public_sec_raw = [0u8; 65]; + public_sec_raw[0] = 4; + public_sec_raw[1..33].copy_from_slice(&GENERATOR_X); + public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y); + + let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw) + .expect("constructing using predefined constants; qed"); + let mut public = Public::default(); + set_public(&mut public, &public_key); + public +} + +fn to_secp256k1_public(public: &Public) -> Result { + let public_data = { + let mut temp = [4u8; 65]; + (&mut temp[1..65]).copy_from_slice(&public[0..64]); + temp + }; + + Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?) +} + +fn set_public(public: &mut Public, key_public: &key::PublicKey) { + let key_public_serialized = key_public.serialize_vec(&SECP256K1, false); + public.copy_from_slice(&key_public_serialized[1..65]); +} diff --git a/ethkey/src/secret.rs b/ethkey/src/secret.rs index 61f4cd1842cea1c41360b0145d187102f811fcbd..d8696ef735617c0871cb6bcba1acf69849a4c39e 100644 --- a/ethkey/src/secret.rs +++ b/ethkey/src/secret.rs @@ -19,7 +19,7 @@ use std::ops::Deref; use std::str::FromStr; use secp256k1::key; use bigint::hash::H256; -use {Error}; +use {Error, SECP256K1}; #[derive(Clone, PartialEq, Eq)] pub struct Secret { @@ -45,6 +45,68 @@ impl Secret { let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?; Ok(secret.into()) } + + /// Inplace add one secret key to another (scalar + scalar) + pub fn add(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.add_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace subtract one secret key from another (scalar - scalar) + pub fn sub(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let mut other_secret = other.to_secp256k1_secret()?; + other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; + key_secret.add_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace multiply one secret key to another (scalar * scalar) + pub fn mul(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.mul_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace inverse secret key (1 / scalar) + pub fn inv(&mut self) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + key_secret.inv_assign(&SECP256K1)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Compute power of secret key inplace (secret ^ pow). + /// This function is not intended to be used with large powers. + pub fn pow(&mut self, pow: usize) -> Result<(), Error> { + match pow { + 0 => *self = key::ONE_KEY.into(), + 1 => (), + _ => { + let c = self.clone(); + for _ in 1..pow { + self.mul(&c)?; + } + }, + } + + Ok(()) + } + + /// Create `secp256k1::key::SecretKey` based on this secret + pub fn to_secp256k1_secret(&self) -> Result { + Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?) + } } impl FromStr for Secret { diff --git a/ethstore/README.md b/ethstore/README.md index 121515943e2764c2b8e677fb038a9be5c4067e06..1986da72bcfce7bf1a3764971221f7891d7680c1 100644 --- a/ethstore/README.md +++ b/ethstore/README.md @@ -16,23 +16,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-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 [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] + ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--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 [--dir DIR]` +#### `insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Encrypt secret with a password and save it in secret store.* - `` - ethereum secret, 32 bytes long - `` - 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
[--dir DIR]` +#### `change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Change account password.* - `
` - ethereum address, 20 bytes long - `` - old account password, file path - `` - 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 [--dir DIR]` +#### `import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Import account from presale wallet.* - `` - presale wallet path - `` - 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
[--dir DIR]` +#### `remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Remove account from secret store.* - `
` - ethereum address, 20 bytes long - `` - 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
[--dir DIR]` +#### `sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` *Sign message with account's secret.* - `
` - ethereum address, 20 bytes long - `` - account password, file path - `` - 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
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Displays public key for an address.* + +- `
` - ethereum address, 20 bytes long +- `` - 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 [--dir DIR]` +*Create new vault.* + +- `` - name of new vault. This can only contain letters, digits, whitespaces, dashes and underscores +- `` - 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 [--dir DIR]` +*Change vault password.* + +- `` - name of existing vault +- `` - old vault password, file path +- `` - 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
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]` +*Move account to vault from another vault/root directory.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` 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 `
` argument, if set +- `[--vault-pwd VAULTPWD]` - password for the current vault of the `
` 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
[--dir DIR]` +*Move account to root directory from given vault.* + +- `
` - ethereum address, 20 bytes long +- `` - name of existing vault to move account to +- `` - password of existing `` 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* diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 06a0b40a8699772d265f262e59481123236f0736..20411a629d945c7339ccb01175a0db82b5dab070 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -31,24 +31,33 @@ Ethereum key management. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - ethstore insert [--dir DIR] - ethstore change-pwd
[--dir DIR] - ethstore list [--dir DIR] + ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore change-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 [--dir DIR] - ethstore remove
[--dir DIR] - ethstore sign
[--dir DIR] - ethstore public
+ ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore sign
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore public
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore list-vaults [--dir DIR] + ethstore create-vault [--dir DIR] + ethstore change-vault-pwd [--dir DIR] + ethstore move-to-vault
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] + ethstore move-from-vault
[--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, Error> { Ok(dir) } +fn open_args_vault(store: &EthStore, args: &Args) -> Result { + 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 { + 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 { - 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(command: I) -> Result where I: IntoIterator = 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(command: I) -> Result where I: IntoIterator DiskDirectory where T: KeyFileManager { } } - /// all accounts found in keys directory - fn files(&self) -> Result, 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, Error> { + Ok(fs::read_dir(&self.path)? .flat_map(Result::ok) .filter(|entry| { let metadata = entry.metadata().ok(); @@ -102,14 +99,34 @@ impl DiskDirectory 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::>(); + .collect::>() + ) + } + + pub fn files_hash(&self) -> Result { + 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, 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 DiskDirectory where T: KeyFileManager { impl KeyDirectory for DiskDirectory where T: KeyFileManager { fn load(&self) -> Result, Error> { - let accounts = self.files()? + let accounts = self.files_content()? .into_iter() .map(|(_, account)| account) .collect(); @@ -191,7 +208,7 @@ impl KeyDirectory for DiskDirectory 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 KeyDirectory for DiskDirectory where T: KeyFileManager { fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> { Some(self) } + + fn unique_repr(&self) -> Result { + self.files_hash() + } } impl VaultKeyDirectoryProvider for DiskDirectory 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"); + } } diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index 1058e433f79b7d04debbd3deca22a923a8e41027..0dfa2e6b23a8ad8620790a61c6f0cb60716e61a8 100755 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -95,4 +95,8 @@ impl KeyDirectory for GethDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs index 941795efc2e392300a3f42ef969aa22dfdd88143..87d12794eb97787e43a4be06da352e2bdd8fcc55 100644 --- a/ethstore/src/dir/memory.rs +++ b/ethstore/src/dir/memory.rs @@ -63,5 +63,12 @@ impl KeyDirectory for MemoryDirectory { } Ok(()) } + + fn unique_repr(&self) -> Result { + let mut val = 0u64; + let accounts = self.accounts.read(); + for acc in accounts.keys() { val = val ^ ::util::FixedHash::low_u64(acc) } + Ok(val) + } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 6e432696840f0f947d98657c1322a0fa1e73de41..83e978707011dbcef2d938471178ab51616db55a 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -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; } /// Vaults provider diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 198e501650f9fd34b4e895b4135ffbc4d168fed7..df03260d3b0461af0689360afd57f894cf6fb976 100755 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -74,4 +74,8 @@ impl KeyDirectory for ParityDirectory { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/ethstore/src/dir/vault.rs b/ethstore/src/dir/vault.rs index 2e777360ac3f184e0300fc87c903781fda78522b..f321e3fbb024c9856535117848630f901c84ed40 100755 --- a/ethstore/src/dir/vault.rs +++ b/ethstore/src/dir/vault.rs @@ -135,6 +135,10 @@ impl VaultKeyDirectory for VaultDiskDirectory { let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone()).map_err(|err| SetKeyError::NonFatalOld(err))?; let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone(); + + // preserve meta + temp_vault.set_meta(&self.meta()).map_err(SetKeyError::NonFatalOld)?; + // jump to next fs level source_path.push("next"); target_path.push("next"); diff --git a/ethstore/src/error.rs b/ethstore/src/error.rs index ff6b4e4db530869d4b7654169f51d7cc50cce143..8a2eb5e8b1d53aed873e94484dfa7f810a45d603 100755 --- a/ethstore/src/error.rs +++ b/ethstore/src/error.rs @@ -18,6 +18,7 @@ use std::fmt; use std::io::Error as IoError; use ethkey::Error as EthKeyError; use crypto::Error as EthCryptoError; +use ethkey::DerivationError; #[derive(Debug)] pub enum Error { @@ -35,6 +36,7 @@ pub enum Error { CreationFailed, EthKey(EthKeyError), EthCrypto(EthCryptoError), + Derivation(DerivationError), Custom(String), } @@ -55,6 +57,7 @@ impl fmt::Display for Error { Error::CreationFailed => "Account creation failed".into(), Error::EthKey(ref err) => err.to_string(), Error::EthCrypto(ref err) => err.to_string(), + Error::Derivation(ref err) => format!("Derivation error: {:?}", err), Error::Custom(ref s) => s.clone(), }; @@ -79,3 +82,9 @@ impl From for Error { Error::EthCrypto(err) } } + +impl From for Error { + fn from(err: DerivationError) -> Self { + Error::Derivation(err) + } +} diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index ff1a4fa67648b6b0ccc04a6bf001c11d61ba96cb..cacb6054f9094f56120ede679305505ab835b9d5 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -21,12 +21,12 @@ use parking_lot::{Mutex, RwLock}; use crypto::KEY_ITERATIONS; use random::Random; -use ethkey::{Signature, Address, Message, Secret, Public, KeyPair}; +use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, ExtendedKeyPair}; use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid}; -use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; +use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; pub struct EthStore { store: EthMultiStore, @@ -54,6 +54,16 @@ impl SimpleSecretStore for EthStore { self.store.insert_account(vault, secret, password) } + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + self.store.insert_derived(vault, account_ref, password, derivation) + } + + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result { + self.store.generate_derived(account_ref, password, derivation) + } + fn account_ref(&self, address: &Address) -> Result { self.store.account_ref(address) } @@ -71,8 +81,13 @@ impl SimpleSecretStore for EthStore { } fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result { - let account = self.get(account)?; - account.sign(password, message) + self.get(account)?.sign(password, message) + } + + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) + -> Result + { + self.store.sign_derived(account_ref, password, derivation, message) } fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { @@ -215,6 +230,7 @@ pub struct EthMultiStore { // order lock: cache, then vaults cache: RwLock>>, vaults: Mutex>>, + dir_hash: Mutex>, } impl EthMultiStore { @@ -229,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(); @@ -269,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() { @@ -340,6 +368,23 @@ impl EthMultiStore { return Ok(()); } + + fn generate(&self, secret: Secret, derivation: Derivation) -> Result { + let mut extended = ExtendedKeyPair::new(secret); + match derivation { + Derivation::Hierarchical(path) => { + for path_item in path { + extended = extended.derive( + if path_item.soft { ethkey::Derivation::Soft(path_item.index) } + else { ethkey::Derivation::Hard(path_item.index) } + )?; + } + }, + Derivation::SoftHash(h256) => { extended = extended.derive(ethkey::Derivation::Soft(h256))?; } + Derivation::HardHash(h256) => { extended = extended.derive(ethkey::Derivation::Hard(h256))?; } + } + Ok(extended) + } } impl SimpleSecretStore for EthMultiStore { @@ -350,8 +395,56 @@ impl SimpleSecretStore for EthMultiStore { self.import(vault, account) } + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + let accounts = self.get(account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + return self.insert_account(vault, extended.secret().as_raw().clone(), password); + } + Err(Error::InvalidPassword) + } + + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) + -> Result + { + let accounts = self.get(&account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + + return Ok(ethkey::public_to_address(extended.public().public())); + } + Err(Error::InvalidPassword) + } + + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) + -> Result + { + let accounts = self.get(&account_ref)?; + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + let extended = self.generate(account.crypto.secret(password)?, derivation)?; + let secret = extended.secret().as_raw(); + return Ok(ethkey::sign(&secret, message)?) + } + Err(Error::InvalidPassword) + + } + fn account_ref(&self, address: &Address) -> Result { - self.reload_accounts()?; + self.reload_if_changed()?; self.cache.read().keys() .find(|r| &r.address == address) .cloned() @@ -359,7 +452,7 @@ impl SimpleSecretStore for EthMultiStore { } fn accounts(&self) -> Result, Error> { - self.reload_accounts()?; + self.reload_if_changed()?; Ok(self.cache.read().keys().cloned().collect()) } @@ -511,7 +604,7 @@ impl SimpleSecretStore for EthMultiStore { let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; vault_provider.vault_meta(name) }) - + } fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> { @@ -527,9 +620,10 @@ mod tests { use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory}; use ethkey::{Random, Generator, KeyPair}; - use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef}; + use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; use super::{EthStore, EthMultiStore}; use devtools::RandomTempPath; + use util::H256; fn keypair() -> KeyPair { Random.generate().unwrap() @@ -898,4 +992,44 @@ mod tests { assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned()); assert!(store.get_vault_meta("vault2").is_err()); } + + #[test] + fn should_store_derived_keys() { + // given we have one account in the store + let store = store(); + let keypair = keypair(); + let address = store.insert_account(SecretVaultRef::Root, keypair.secret().clone(), "test").unwrap(); + + // when we deriving from that account + let derived = store.insert_derived( + SecretVaultRef::Root, + &address, + "test", + Derivation::HardHash(H256::from(0)), + ).unwrap(); + + // there should be 2 accounts in the store + let accounts = store.accounts().unwrap(); + assert_eq!(accounts.len(), 2); + + // and we can sign with the derived contract + assert!(store.sign(&derived, "test", &Default::default()).is_ok(), "Second password should work for second store."); + } + + #[test] + fn should_save_meta_when_setting_before_password() { + // given + let mut dir = RootDiskDirectoryGuard::new(); + let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); + let name = "vault"; let password = "password1"; + let new_password = "password2"; + + // when + store.create_vault(name, password).unwrap(); + store.set_vault_meta(name, "OldMeta").unwrap(); + store.change_vault_password(name, new_password).unwrap(); + + // then + assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned()); + } } diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 51ac0afbab026e0731cb6d66b47ace3c632e2861..f092c3fe62b3fb13d24f6e88e5a1dfa71911676c 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -57,5 +57,8 @@ pub use self::error::Error; pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::import::{import_accounts, read_geth_accounts}; pub use self::presale::PresaleWallet; -pub use self::secret_store::{SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore}; +pub use self::secret_store::{ + SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore, + Derivation, IndexDerivation, +}; pub use self::random::{random_phrase, random_string}; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 442c48a1df51c740a017d29cbc207af29fda6231..1eff95335d52ef14eb211f9ad1bfac1f3cc0e5c3 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::Uuid; +use util::H256; /// Key directory reference #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -40,10 +41,12 @@ pub struct StoreAccountRef { pub trait SimpleSecretStore: Send + Sync { fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result; + fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>; fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>; - + fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result; + fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result; fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; fn accounts(&self) -> Result, Error>; @@ -116,3 +119,21 @@ impl Hash for StoreAccountRef { self.address.hash(state); } } + +/// Node in hierarchical derivation. +pub struct IndexDerivation { + /// Node is soft (allows proof of parent from parent node). + pub soft: bool, + /// Index sequence of the node. + pub index: u32, +} + +/// Derivation scheme for keys +pub enum Derivation { + /// Hierarchical derivation + Hierarchical(Vec), + /// Hash derivation, soft. + SoftHash(H256), + /// Hash derivation, hard. + HardHash(H256), +} diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 150ae8108efe0fd0835f970ce560398cc8b1227e..45e2aab09d5771542bc8f3824eebbf71af60f553 100755 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -74,4 +74,8 @@ impl KeyDirectory for TransientDir { fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } + + fn unique_repr(&self) -> Result { + self.dir.unique_repr() + } } diff --git a/evmbin/Cargo.toml b/evmbin/Cargo.toml index ad2d69d5723d1fae2537f57a754467d9c034581c..98193a5c35c81f8b494efaaf5ac939c948f1ab5b 100644 --- a/evmbin/Cargo.toml +++ b/evmbin/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "evm" +name = "evmbin" description = "Parity's EVM implementation" version = "0.1.0" authors = ["Parity Technologies "] diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs index 1b5f17c052ba005b70e5def9e54347df7764da19..bcce9adc1427ab4b7b44e7873187ce7c9ff8d72f 100644 --- a/evmbin/src/ext.rs +++ b/evmbin/src/ext.rs @@ -18,7 +18,7 @@ use std::sync::Arc; use std::collections::HashMap; -use util::{U256, H256, Address, Bytes, FixedHash}; +use util::{U256, H256, Address, Bytes, FixedHash, trie}; use ethcore::client::EnvInfo; use ethcore::evm::{self, Ext, ContractCreateResult, MessageCallResult, Schedule, CallType}; @@ -31,7 +31,7 @@ pub struct FakeExt { impl Default for FakeExt { fn default() -> Self { FakeExt { - schedule: Schedule::new_homestead_gas_fix(), + schedule: Schedule::new_post_eip150(usize::max_value(), true, true, true), store: HashMap::new(), depth: 1, } @@ -39,27 +39,28 @@ impl Default for FakeExt { } impl Ext for FakeExt { - fn storage_at(&self, key: &H256) -> H256 { - self.store.get(key).unwrap_or(&H256::new()).clone() + fn storage_at(&self, key: &H256) -> trie::Result { + Ok(self.store.get(key).unwrap_or(&H256::new()).clone()) } - fn set_storage(&mut self, key: H256, value: H256) { + fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()> { self.store.insert(key, value); + Ok(()) } - fn exists(&self, _address: &Address) -> bool { + fn exists(&self, _address: &Address) -> trie::Result { unimplemented!(); } - fn exists_and_not_null(&self, address: &Address) -> bool { - unimplemented!(); + fn exists_and_not_null(&self, _address: &Address) -> trie::Result { + unimplemented!(); } - fn origin_balance(&self) -> U256 { + fn origin_balance(&self) -> trie::Result { unimplemented!(); } - fn balance(&self, _address: &Address) -> U256 { + fn balance(&self, _address: &Address) -> trie::Result { unimplemented!(); } @@ -83,11 +84,11 @@ impl Ext for FakeExt { unimplemented!(); } - fn extcode(&self, _address: &Address) -> Arc { + fn extcode(&self, _address: &Address) -> trie::Result> { unimplemented!(); } - fn extcodesize(&self, _address: &Address) -> usize { + fn extcodesize(&self, _address: &Address) -> trie::Result { unimplemented!(); } @@ -99,7 +100,7 @@ impl Ext for FakeExt { Ok(*gas) } - fn suicide(&mut self, _refund_address: &Address) { + fn suicide(&mut self, _refund_address: &Address) -> trie::Result<()> { unimplemented!(); } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 5ba4e59507ec63e48c276c8c3a3fffce6b45e4d9..6fbe5a280748e3948e176355b358f5d563745eeb 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -21,7 +21,6 @@ extern crate ethcore; extern crate rustc_serialize; extern crate docopt; -#[macro_use] extern crate ethcore_util as util; mod ext; diff --git a/evmjit/Cargo.toml b/evmjit/Cargo.toml index 0d5d16ea115c7b72dcb43ed4dbcd21642ac55ac6..2f84a7efdd3468506635d2cda0d5c0539e777514 100644 --- a/evmjit/Cargo.toml +++ b/evmjit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evmjit" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [lib] diff --git a/hash-fetch/Cargo.toml b/hash-fetch/Cargo.toml index f5f31c0a0f08bf1ddf1b84ca11e6d3aee483c475..d24315eb0f34022f7d248f466048593372062286 100644 --- a/hash-fetch/Cargo.toml +++ b/hash-fetch/Cargo.toml @@ -3,7 +3,7 @@ description = "Fetching hash-addressed content." homepage = "http://parity.io" license = "GPL-3.0" name = "parity-hash-fetch" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index 453f8925aa4141435b18bd3f2d957fa71d40e950..cffc10e636b8366c448faad66db89cd2cce69139 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -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 for Error { fn from(error: FetchError) -> Self { Error::Fetch(error) @@ -115,6 +134,10 @@ impl HashFetch for Client { let future = self.fetch.fetch(&url).then(move |result| { fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { 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; + + fn new() -> Result 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); + } +} diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 227f24dc339e88d8139bbaf7ac5bb5249d947b8a..1588b54823f87eca8fd66eead4a911c044b02b73 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -264,7 +264,7 @@ fn as_string(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>>, pub responses: Mutex>>, } - 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( diff --git a/hw/Cargo.toml b/hw/Cargo.toml index 39baa675d60478130b00e7cd7c45e2eb0751ed31..19b680a55777ed023623f1278f82108ef03b8442 100644 --- a/hw/Cargo.toml +++ b/hw/Cargo.toml @@ -3,7 +3,7 @@ description = "Hardware wallet support." homepage = "http://parity.io" license = "GPL-3.0" name = "hardware-wallet" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/hw/src/ledger.rs b/hw/src/ledger.rs index 50a9ee5f37d4d408b401bcddca5a3d92a5fe5b91..255fa8515460331a1df46939496b86718f0bed82 100644 --- a/hw/src/ledger.rs +++ b/hw/src/ledger.rs @@ -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); diff --git a/ipc-common-types/Cargo.toml b/ipc-common-types/Cargo.toml index 844962b2d88749f20e3b9bed18bac3632138bb2c..1eb4a4d0a11310f382c8380da52dc0898cd669c8 100644 --- a/ipc-common-types/Cargo.toml +++ b/ipc-common-types/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Types that implement IPC and are common to multiple modules." name = "ipc-common-types" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/ipc/codegen/Cargo.toml b/ipc/codegen/Cargo.toml index a257fd98cc450dde4ee796e97d8d406bcacedcd1..63e0fc7c64320d2a139e4b7e72b49eb446b04862 100644 --- a/ipc/codegen/Cargo.toml +++ b/ipc/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-codegen" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" description = "Macros to auto-generate implementations for ipc call" diff --git a/ipc/nano/Cargo.toml b/ipc/nano/Cargo.toml index 07ebf41efe87ba241b5ee4a20daa63d2c0ceaceb..9948820fefa997e13ce0ef41528fa86194deae37 100644 --- a/ipc/nano/Cargo.toml +++ b/ipc/nano/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-nano" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml index fdfc4ba80c35b670f74f3ba7aeb9b68c021af643..d8be8b444e0737f331f4246447690a81f744f6db 100644 --- a/ipc/rpc/Cargo.toml +++ b/ipc/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b7b84b66b702297c61bf1b8a7f52c6472d11c904 --- /dev/null +++ b/ipfs/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "Parity IPFS-compatible API" +name = "parity-ipfs-api" +version = "1.7.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[dependencies] +ethcore = { path = "../ethcore" } +ethcore-util = { path = "../util" } +jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } +rlp = { path = "../util/rlp" } +mime = "0.2" +hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } +cid = "0.2.1" +multihash = "0.5" diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..1cbd54f1cf6a92fb583fb64baca4be55137c9ccb --- /dev/null +++ b/ipfs/src/error.rs @@ -0,0 +1,97 @@ +// 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 . + +use {multihash, cid, hyper}; +use route::Out; + +pub type Result = ::std::result::Result; + +/// IPFS server error +#[derive(Debug)] +pub enum ServerError { + /// Wrapped `std::io::Error` + IoError(::std::io::Error), + /// Other `hyper` error + Other(hyper::error::Error), + /// Invalid --ipfs-api-interface + InvalidInterface +} + +#[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 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 for Error { + fn from(_: cid::Error) -> Error { + Error::CidParsingFailed + } +} + +/// Convert multihash errors (multihash being part of CID). +impl From 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 for ServerError { + fn from(err: hyper::error::Error) -> ServerError { + ServerError::Other(err) + } +} + +impl From for String { + fn from(err: ServerError) -> String { + match err { + ServerError::IoError(err) => err.to_string(), + ServerError::Other(err) => err.to_string(), + ServerError::InvalidInterface => "Invalid --ipfs-api-interface parameter".into(), + } + } +} diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..80e910f9e90acfea5d8919f9c55eacdbb7041680 --- /dev/null +++ b/ipfs/src/lib.rs @@ -0,0 +1,306 @@ +// 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 . + +#[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; +extern crate jsonrpc_http_server; + +pub mod error; +mod route; + +use std::io::Write; +use std::sync::Arc; +use std::net::{SocketAddr, IpAddr}; +use error::ServerError; +use route::Out; +use jsonrpc_http_server::cors; +use hyper::server::{Listening, Handler, Request, Response}; +use hyper::net::HttpStream; +use hyper::header::{Vary, ContentLength, ContentType, AccessControlAllowOrigin}; +use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; +use ethcore::client::BlockChainClient; + + +/// Request/response handler +pub struct IpfsHandler { + /// Response to send out + out: Out, + /// How many bytes from the response have been written + out_progress: usize, + /// Origin request header + origin: Option, + /// Allowed CORS domains + cors_domains: Option>, + /// Hostnames allowed in the `Host` request header + allowed_hosts: Option>, + /// Reference to the Blockchain Client + client: Arc, +} + +impl IpfsHandler { + pub fn client(&self) -> &BlockChainClient { + &*self.client + } + + pub fn new(cors: Option>, hosts: Option>, client: Arc) -> Self { + fn origin_to_header(origin: String) -> AccessControlAllowOrigin { + match origin.as_str() { + "*" => AccessControlAllowOrigin::Any, + "null" | "" => AccessControlAllowOrigin::Null, + _ => AccessControlAllowOrigin::Value(origin), + } + } + + IpfsHandler { + out: Out::Bad("Invalid Request"), + out_progress: 0, + origin: None, + cors_domains: cors.map(|vec| vec.into_iter().map(origin_to_header).collect()), + allowed_hosts: hosts, + client: client, + } + } + + fn is_host_allowed(&self, req: &Request) -> bool { + match self.allowed_hosts { + Some(ref hosts) => jsonrpc_http_server::is_host_header_valid(&req, hosts), + None => true, + } + } + + fn is_origin_allowed(&self) -> bool { + // Check origin header first, no header passed is good news + let origin = match self.origin { + Some(ref origin) => origin, + None => return true, + }; + + let cors_domains = match self.cors_domains { + Some(ref domains) => domains, + None => return false, + }; + + cors_domains.iter().any(|domain| match *domain { + AccessControlAllowOrigin::Value(ref allowed) => origin == allowed, + AccessControlAllowOrigin::Any => true, + AccessControlAllowOrigin::Null => origin == "", + }) + } +} + +/// Implement Hyper's HTTP handler +impl Handler for IpfsHandler { + fn on_request(&mut self, req: Request) -> Next { + if *req.method() != Method::Get { + return Next::write(); + } + + self.origin = cors::read_origin(&req); + + if !self.is_host_allowed(&req) { + self.out = Out::Bad("Disallowed Host header"); + + return Next::write(); + } + + if !self.is_origin_allowed() { + self.out = Out::Bad("Disallowed Origin header"); + + 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.out = self.route(path, query); + + Next::write() + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> 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)); + + }, + NotFound(reason) => { + res.set_status(StatusCode::NotFound); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType(mime!(Text/Plain))); + }, + Bad(reason) => { + res.set_status(StatusCode::BadRequest); + + res.headers_mut().set(ContentLength(reason.len() as u64)); + res.headers_mut().set(ContentType(mime!(Text/Plain))); + } + } + + if let Some(cors_header) = cors::get_cors_header(&self.cors_domains, &self.origin) { + res.headers_mut().set(cors_header); + res.headers_mut().set(Vary::Items(vec!["Origin".into()])); + } + + Next::write() + } + + fn on_response_writable(&mut self, transport: &mut Encoder) -> 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) + } +} + +/// Attempt to write entire `data` from current `progress` +fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next { + // Skip any bytes that have already been written + let chunk = &data[*progress..]; + + // Write an get the amount of bytes written. End the connection in case of an error. + let written = match transport.write(chunk) { + Ok(written) => written, + Err(_) => return Next::end(), + }; + + *progress += written; + + // Close the connection if the entire remaining chunk has been written + if written < chunk.len() { + Next::write() + } else { + Next::end() + } +} + +/// Add current interface (default: "127.0.0.1:5001") to list of allowed hosts +fn include_current_interface(mut hosts: Vec, interface: String, port: u16) -> Vec { + hosts.push(match port { + 80 => interface, + _ => format!("{}:{}", interface, port), + }); + + hosts +} + +pub fn start_server( + port: u16, + interface: String, + cors: Option>, + hosts: Option>, + client: Arc +) -> Result { + + let ip: IpAddr = interface.parse().map_err(|_| ServerError::InvalidInterface)?; + let addr = SocketAddr::new(ip, port); + let hosts = hosts.map(move |hosts| include_current_interface(hosts, interface, port)); + + Ok( + hyper::Server::http(&addr)? + .handle(move |_| IpfsHandler::new(cors.clone(), hosts.clone(), 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); + } +} diff --git a/ipfs/src/route.rs b/ipfs/src/route.rs new file mode 100644 index 0000000000000000000000000000000000000000..5b571885daca34e35871842dfd9b372902d0702b --- /dev/null +++ b/ipfs/src/route.rs @@ -0,0 +1,245 @@ +// 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 . + +use {rlp, multihash, IpfsHandler}; +use error::{Error, Result}; +use cid::{ToCid, Codec}; + +use multihash::Hash; +use util::{Bytes, H256}; +use ethcore::client::{BlockId, TransactionId}; + +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), +} + +impl IpfsHandler { + /// Route path + query string to a specialized method + pub fn route(&self, path: &str, query: Option<&str>) -> 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") + } + } + + /// 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 std::sync::Arc; + use super::*; + use ethcore::client::TestBlockChainClient; + + fn get_mocked_handler() -> IpfsHandler { + IpfsHandler::new(None, None, 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 handler = get_mocked_handler(); + + let out = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(out, Out::NotFound("Block not found")); + } + + #[test] + fn route_block_missing_query() { + let handler = get_mocked_handler(); + + let out = handler.route("/api/v0/block/get", None); + + assert_eq!(out, Out::Bad("CID parsing failed")); + } + + #[test] + fn route_block_invalid_query() { + let handler = get_mocked_handler(); + + let out = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(out, Out::Bad("CID parsing failed")); + } + + #[test] + fn route_invalid_route() { + let handler = get_mocked_handler(); + + let out = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + + assert_eq!(out, Out::NotFound("Route not found")); + } +} diff --git a/js/.babelrc b/js/.babelrc index 6fea8d2861197c4ae4d4542eb2d465e7232b0742..5087af80dcf62269a8e603916b6f5a8d0a2a3950 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -19,8 +19,8 @@ }, "development": { "plugins": [ - "react-hot-loader/babel", - ["react-intl", { "messagesDir": "./.build/i18n/" }] + [ "react-intl", { "messagesDir": "./.build/i18n/" } ], + "react-hot-loader/babel" ] }, "test": { diff --git a/js/.gitignore b/js/.gitignore index 555c4b4bbd451223f4c6402fed37641e70b8edf7..786a104986386ff02e748faeec08361dbfc11759 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -8,3 +8,4 @@ docs .happypack .npmjs .eslintcache +yarn.lock diff --git a/js/assets/fonts/Roboto/font.css b/js/assets/fonts/Roboto/font.css index 5eea2bfd1fe980b8e55c9ca9483a13da24995f8e..75226e62185177b5dd8fa2102aeb36da94b69269 100644 --- a/js/assets/fonts/Roboto/font.css +++ b/js/assets/fonts/Roboto/font.css @@ -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; } diff --git a/js/assets/fonts/RobotoMono/font.css b/js/assets/fonts/RobotoMono/font.css index 5e9ab5721c1c8ef8630df6cae54949872434a253..6a86af729e4fb38974843b3c1309d9dd3eabda49 100644 --- a/js/assets/fonts/RobotoMono/font.css +++ b/js/assets/fonts/RobotoMono/font.css @@ -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; } diff --git a/js/package.json b/js/package.json index 9097cd18a33171d5a8f6a1f9936206ff21b6c9a9..85d906b887bf04ea5e837ed16e706234f5f955c9 100644 --- a/js/package.json +++ b/js/package.json @@ -1,15 +1,16 @@ { "name": "parity.js", - "version": "0.3.81", + "version": "1.7.3", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", "maintainers": [ "Jaco Greeff", - "Nicolas Gotchac", + "Nicolas Gotchac" + ], + "contributors": [ "Jannis Redmann" ], - "contributors": [], "license": "GPL-3.0", "repository": { "type": "git", @@ -32,6 +33,7 @@ "build:markdown": "babel-node ./scripts/build-rpc-markdown.js", "build:json": "babel-node ./scripts/build-rpc-json.js", "build:embed": "EMBED=1 node webpack/embed", + "build:i18n": "npm run clean && npm run build && babel-node ./scripts/build-i18n.js", "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app && npm run ci:build:embed", "ci:build:app": "NODE_ENV=production webpack --config webpack/app", "ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries", @@ -41,7 +43,7 @@ "ci:build:embed": "NODE_ENV=production EMBED=1 node webpack/embed", "start": "npm install && npm run build:lib && npm run build:dll && npm run start:app", "start:app": "node webpack/dev.server", - "clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build", + "clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build ./node_modules/.cache", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "npm run lint:css && npm run lint:js", "lint:cached": "npm run lint:css && npm run lint:js:cached", @@ -55,28 +57,28 @@ "prepush": "npm run lint:cached" }, "devDependencies": { - "babel-cli": "6.22.2", - "babel-core": "6.22.1", + "babel-cli": "6.23.0", + "babel-core": "6.23.1", "babel-eslint": "7.1.1", - "babel-loader": "6.2.10", + "babel-loader": "6.3.2", "babel-plugin-lodash": "3.2.11", "babel-plugin-react-intl": "2.3.1", "babel-plugin-recharts": "1.1.0", - "babel-plugin-transform-class-properties": "6.22.0", + "babel-plugin-transform-class-properties": "6.23.0", "babel-plugin-transform-decorators-legacy": "1.3.4", - "babel-plugin-transform-object-rest-spread": "6.22.0", - "babel-plugin-transform-react-remove-prop-types": "0.3.0", - "babel-plugin-transform-runtime": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.23.0", + "babel-plugin-transform-react-remove-prop-types": "0.3.2", + "babel-plugin-transform-runtime": "6.23.0", "babel-plugin-webpack-alias": "2.1.2", - "babel-polyfill": "6.22.0", - "babel-preset-env": "1.1.8", + "babel-polyfill": "6.23.0", + "babel-preset-env": "1.1.9", "babel-preset-es2015": "6.22.0", "babel-preset-es2016": "6.22.0", "babel-preset-es2017": "6.22.0", - "babel-preset-react": "6.22.0", + "babel-preset-react": "6.23.0", "babel-preset-stage-0": "6.22.0", - "babel-register": "6.22.0", - "babel-runtime": "6.22.0", + "babel-register": "6.23.0", + "babel-runtime": "6.23.0", "chai": "3.5.0", "chai-as-promised": "6.0.0", "chai-enzyme": "0.6.1", @@ -84,61 +86,62 @@ "circular-dependency-plugin": "2.0.0", "copy-webpack-plugin": "4.0.1", "core-js": "2.4.1", - "coveralls": "2.11.15", + "coveralls": "2.11.16", "css-loader": "0.26.1", "ejs-loader": "0.3.0", "ejsify": "1.0.0", - "enzyme": "2.7.0", - "eslint": "3.11.1", + "enzyme": "2.7.1", + "eslint": "3.16.1", "eslint-config-semistandard": "7.0.0", "eslint-config-standard": "6.2.1", "eslint-config-standard-react": "4.2.0", - "eslint-plugin-promise": "3.4.0", - "eslint-plugin-react": "6.8.0", + "eslint-plugin-promise": "3.4.2", + "eslint-plugin-react": "6.10.0", "eslint-plugin-standard": "2.0.1", - "express": "4.14.0", + "express": "4.14.1", "extract-loader": "0.1.0", "extract-text-webpack-plugin": "2.0.0-beta.4", - "file-loader": "0.9.0", - "happypack": "3.0.2", + "file-loader": "0.10.0", + "happypack": "3.0.3", "html-loader": "0.4.4", - "html-webpack-plugin": "2.24.1", + "html-webpack-plugin": "2.28.0", "http-proxy-middleware": "0.17.3", - "husky": "0.11.9", + "husky": "0.13.1", "ignore-styles": "5.0.1", - "image-webpack-loader": "3.1.0", + "image-webpack-loader": "3.2.0", "istanbul": "1.0.0-alpha.2", - "jsdom": "9.9.1", + "jsdom": "9.11.0", "json-loader": "0.5.4", "mocha": "3.2.0", "mock-local-storage": "1.0.2", "mock-socket": "6.0.4", - "nock": "9.0.2", - "postcss-import": "9.0.0", - "postcss-loader": "1.2.1", + "nock": "9.0.7", + "postcss-import": "9.1.0", + "postcss-loader": "1.3.2", "postcss-nested": "1.0.0", "postcss-simple-vars": "3.0.0", "progress": "1.1.8", - "progress-bar-webpack-plugin": "1.9.1", + "progress-bar-webpack-plugin": "1.9.3", "raw-loader": "0.5.1", - "react-addons-perf": "15.4.1", - "react-addons-test-utils": "15.4.1", + "react-addons-perf": "15.4.2", + "react-addons-test-utils": "15.4.2", "react-hot-loader": "3.0.0-beta.6", "react-intl-aggregate-webpack-plugin": "0.0.1", "rucksack-css": "0.9.1", - "script-ext-html-webpack-plugin": "1.3.5", - "serviceworker-webpack-plugin": "0.1.7", - "sinon": "1.17.6", + "script-ext-html-webpack-plugin": "1.7.1", + "serviceworker-webpack-plugin": "0.2.0", + "sinon": "1.17.7", "sinon-as-promised": "4.0.2", "sinon-chai": "2.8.0", "style-loader": "0.13.1", - "stylelint": "7.7.0", - "stylelint-config-standard": "15.0.1", + "stylelint": "7.9.0", + "stylelint-config-standard": "16.0.0", + "to-source": "2.0.3", "url-loader": "0.5.7", "webpack": "2.2.1", - "webpack-dev-middleware": "1.9.0", + "webpack-dev-middleware": "1.10.1", "webpack-error-notification": "0.1.6", - "webpack-hot-middleware": "2.14.0", + "webpack-hot-middleware": "2.17.1", "websocket": "1.0.24", "yargs": "6.6.0" }, @@ -151,7 +154,7 @@ "debounce": "1.0.0", "es6-error": "4.0.0", "es6-promise": "4.0.5", - "ethereumjs-tx": "1.1.4", + "ethereumjs-tx": "1.2.5", "eventemitter3": "2.0.2", "file-saver": "1.3.3", "flat": "2.0.1", @@ -183,6 +186,7 @@ "react-element-to-jsx-string": "6.0.0", "react-event-listener": "0.4.1", "react-intl": "2.1.5", + "react-markdown": "2.4.4", "react-portal": "3.0.0", "react-redux": "4.4.6", "react-router": "3.0.0", @@ -197,6 +201,9 @@ "scryptsy": "2.0.0", "solc": "ngotchac/solc-js", "store": "1.3.20", + "u2f-api": "0.0.9", + "u2f-api-polyfill": "0.4.3", + "uglify-js": "2.8.2", "useragent.js": "0.5.6", "utf8": "2.1.2", "valid-url": "1.0.9", diff --git a/js/scripts/build-i18n.js b/js/scripts/build-i18n.js new file mode 100644 index 0000000000000000000000000000000000000000..1acf2c852f50c4c369e81009d33f2516ccb9faff --- /dev/null +++ b/js/scripts/build-i18n.js @@ -0,0 +1,154 @@ +// 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 . + +const fs = require('fs'); +const _ = require('lodash'); +const path = require('path'); +const toSource = require('to-source'); + +const FILE_HEADER = `// 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 .\n\n`; +const SECTION_HEADER = 'export default '; +const SECTION_FOOTER = ';\n'; +const INDENT = ' '; +const DESTPATH = path.join(__dirname, '../src/i18n/_default'); +const ENPATH = path.join(__dirname, '../src/i18n/en'); +const SRCPATH = path.join(__dirname, '../.build/i18n/i18n/en.json'); + +// main entry point +(function main () { + const { sections, sectionNames } = createSectionMap(); + + sectionNames.forEach((name) => outputSection(name, sections[name])); + outputIndex(sectionNames); +})(); + +// sort an object based on its keys +function sortObject (object) { + return Object + .keys(object) + .sort() + .reduce((sorted, key) => { + if (typeof object[key] === 'object') { + sorted[key] = sortObject(object[key]); + } else { + sorted[key] = object[key]; + } + + return sorted; + }, {}); +} + +// create an object map of the actual inputs +function createSectionMap () { + console.log(`Reading strings from ${SRCPATH}`); + + const i18nstrings = require(SRCPATH); + const sections = sortObject( + Object + .keys(i18nstrings) + .reduce((sections, fullKey) => { + const defaultMessage = i18nstrings[fullKey].defaultMessage; + const keys = fullKey.split('.'); + let outputs = sections; + + keys.forEach((key, index) => { + if (index === keys.length - 1) { + outputs[key] = defaultMessage; + } else { + if (!outputs[key]) { + outputs[key] = {}; + } + + outputs = outputs[key]; + } + }); + + return sections; + }, {}) + ); + const sectionNames = Object.keys(sections); + + console.log(`Found ${sectionNames.length} sections`); + + return { + sections, + sectionNames + }; +} + +// load the available deafults (non-exported strings) for a section +function readDefaults (sectionName) { + let defaults = {}; + + try { + defaults = require(path.join(ENPATH, `${sectionName}.js`)).default; + } catch (error) { + defaults = {}; + } + + return defaults; +} + +// create the index.js file +function outputIndex (sectionNames) { + console.log(`Writing index.js to ${DESTPATH}`); + + const defaults = readDefaults('index'); + const dest = path.join(DESTPATH, 'index.js'); + const exports = _.uniq(Object.keys(defaults).concat(sectionNames)) + .sort() + .map((name) => `export ${name} from './${name}';`) + .join('\n'); + + fs.writeFileSync(dest, `${FILE_HEADER}${exports}\n`, 'utf8'); +} + +// export a section as a flatenned JS export string +function createJSSection (section) { + const source = toSource(section, { + enclose: true, + quoteChar: '`', + tabChar: INDENT, + tabDepth: 0 + }); + + return `${SECTION_HEADER}${source}${SECTION_FOOTER}`; +} + +// create the individual section files +function outputSection (sectionName, section) { + console.log(`Writing ${sectionName}.js to ${DESTPATH}`); + + const defaults = readDefaults(sectionName); + const dest = path.join(DESTPATH, `${sectionName}.js`); + const sectionText = createJSSection(_.defaultsDeep(section, defaults)); + + fs.writeFileSync(dest, `${FILE_HEADER}${sectionText}`, 'utf8'); +} diff --git a/js/src/3rdparty/etherscan/account.js b/js/src/3rdparty/etherscan/account.js index 8a8f4b1fc4aa89ee27bc915cecd809e9721b0b0b..7a6844759117bca1289bfbf5df4ee2b78411fb16 100644 --- a/js/src/3rdparty/etherscan/account.js +++ b/js/src/3rdparty/etherscan/account.js @@ -21,15 +21,15 @@ const PAGE_SIZE = 25; import util from '../../api/util'; import { call } from './call'; -function _call (method, params, test) { - return call('account', method, params, test); +function _call (method, params, test, netVersion) { + return call('account', method, params, test, netVersion); } -function balance (address, test = false) { +function balance (address, test, netVersion) { return _call('balance', { address: address, tag: 'latest' - }, test).then((balance) => { + }, test, netVersion).then((balance) => { // same format as balancemulti below return { account: address, @@ -38,21 +38,21 @@ function balance (address, test = false) { }); } -function balances (addresses, test = false) { +function balances (addresses, test, netVersion) { return _call('balancemulti', { address: addresses.join(','), tag: 'latest' - }, test); + }, test, netVersion); } -function transactions (address, page, test = false) { +function transactions (address, page, test, netVersion) { // page offset from 0 return _call('txlist', { address: address, offset: PAGE_SIZE, page: (page || 0) + 1, sort: 'desc' - }, test).then((transactions) => { + }, test, netVersion).then((transactions) => { return transactions.map((tx) => { return { blockNumber: new BigNumber(tx.blockNumber || 0), @@ -67,9 +67,9 @@ function transactions (address, page, test = false) { } const account = { - balance: balance, - balances: balances, - transactions: transactions + balance, + balances, + transactions }; export { account }; diff --git a/js/src/3rdparty/etherscan/call.js b/js/src/3rdparty/etherscan/call.js index 3c3d1ef06490ca135326aa34d2377e110453df69..6b72e1bea2b3f3c075c97882c677e31d0dc4a1a1 100644 --- a/js/src/3rdparty/etherscan/call.js +++ b/js/src/3rdparty/etherscan/call.js @@ -23,14 +23,32 @@ const options = { } }; -export function call (module, action, _params, test) { - const host = test ? 'testnet.etherscan.io' : 'api.etherscan.io'; +export function call (module, action, _params, test, netVersion) { + let prefix = 'api.'; + + switch (netVersion) { + case '2': + case '3': + prefix = 'testnet.'; + break; + + case '42': + prefix = 'kovan.'; + break; + + case '0': + default: + if (test) { + prefix = 'testnet.'; + } + break; + } const query = stringify(Object.assign({ module, action }, _params || {})); - return fetch(`https://${host}/api?${query}`, options) + return fetch(`https://${prefix}etherscan.io/api?${query}`, options) .then((response) => { if (!response.ok) { throw { code: response.status, message: response.statusText }; // eslint-disable-line diff --git a/js/src/3rdparty/etherscan/helpers.spec.js b/js/src/3rdparty/etherscan/helpers.spec.js index aeb6ef23069998a9ebacf87f0eacfde1b7e2c5f1..fa29c3d97715003893163b11bae6b13db043a2cb 100644 --- a/js/src/3rdparty/etherscan/helpers.spec.js +++ b/js/src/3rdparty/etherscan/helpers.spec.js @@ -19,8 +19,8 @@ import { stringify } from 'qs'; import { url } from './links'; -function mockget (requests, test) { - let scope = nock(url(test)); +function mockget (requests, test, netVersion) { + let scope = nock(url(test, netVersion)); requests.forEach((request) => { scope = scope diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js index 59ad51de71eb1aed75f6eec12d22c810064f52d3..8c9101268095d383f36971e28d7b03e8b9b5c003 100644 --- a/js/src/3rdparty/etherscan/links.js +++ b/js/src/3rdparty/etherscan/links.js @@ -14,14 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export const url = (isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; +// NOTE: Keep 'isTestnet' for backwards library compatibility +export const url = (isTestnet = false, netVersion = '0') => { + let prefix = ''; + + switch (netVersion) { + case '2': + case '3': + prefix = 'testnet.'; + break; + + case '42': + prefix = 'kovan.'; + break; + + case '0': + default: + if (isTestnet) { + prefix = 'testnet.'; + } + break; + } + + return `https://${prefix}etherscan.io`; }; -export const txLink = (hash, isTestnet = false) => { - return `${url(isTestnet)}/tx/${hash}`; +export const txLink = (hash, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/tx/${hash}`; }; -export const addressLink = (address, isTestnet = false) => { - return `${url(isTestnet)}/address/${address}`; +export const addressLink = (address, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/address/${address}`; }; diff --git a/js/src/3rdparty/ledger/index.js b/js/src/3rdparty/ledger/index.js index 3759b4bd57bf37e924edb61ffffc2b0bd047f8fd..a5b876dcac816e26d88312735ad58d1871c23480 100644 --- a/js/src/3rdparty/ledger/index.js +++ b/js/src/3rdparty/ledger/index.js @@ -14,12 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import Ledger3 from './vendor/ledger3'; -import LedgerEth from './vendor/ledger-eth'; - -export function create () { - const ledger = new Ledger3('w0w'); - const app = new LedgerEth(ledger); - - return app; -} +export default from './ledger'; diff --git a/js/src/3rdparty/ledger/ledger.js b/js/src/3rdparty/ledger/ledger.js new file mode 100644 index 0000000000000000000000000000000000000000..13a67199857c82ab10fd303665c4962478f7c9ed --- /dev/null +++ b/js/src/3rdparty/ledger/ledger.js @@ -0,0 +1,136 @@ +// 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 . + +import 'u2f-api-polyfill'; + +import BigNumber from 'bignumber.js'; +import Transaction from 'ethereumjs-tx'; +import u2fapi from 'u2f-api'; + +import Ledger3 from './vendor/ledger3'; +import LedgerEth from './vendor/ledger-eth'; + +const LEDGER_PATH_ETC = "44’/60’/160720'/0'/0"; +const LEDGER_PATH_ETH = "44'/60'/0'/0"; +const SCRAMBLE_KEY = 'w0w'; + +function numberToHex (number) { + return `0x${new BigNumber(number).toString(16)}`; +} + +export default class Ledger { + constructor (api, ledger) { + this._api = api; + this._ledger = ledger; + + this._isSupported = false; + + this.checkJSSupport(); + } + + // FIXME: Until we have https support from Parity u2f will not work. Here we mark it completely + // as unsupported until a full end-to-end environment is available. + get isSupported () { + return false && this._isSupported; + } + + checkJSSupport () { + return u2fapi + .isSupported() + .then((isSupported) => { + console.log('Ledger:checkJSSupport', isSupported); + + this._isSupported = isSupported; + }); + } + + getAppConfiguration () { + return new Promise((resolve, reject) => { + this._ledger.getAppConfiguration((response, error) => { + if (error) { + reject(error); + return; + } + + resolve(response); + }); + }); + } + + scan () { + return new Promise((resolve, reject) => { + this._ledger.getAddress(LEDGER_PATH_ETH, (response, error) => { + if (error) { + reject(error); + return; + } + + resolve([response.address]); + }, true, false); + }); + } + + signTransaction (transaction) { + return this._api.net.version().then((_chainId) => { + return new Promise((resolve, reject) => { + const chainId = new BigNumber(_chainId).toNumber(); + const tx = new Transaction({ + data: transaction.data || transaction.input, + gasPrice: numberToHex(transaction.gasPrice), + gasLimit: numberToHex(transaction.gasLimit), + nonce: numberToHex(transaction.nonce), + to: transaction.to ? transaction.to.toLowerCase() : undefined, + value: numberToHex(transaction.value), + v: Buffer.from([chainId]), // pass the chainId to the ledger + r: Buffer.from([]), + s: Buffer.from([]) + }); + const rawTransaction = tx.serialize().toString('hex'); + + this._ledger.signTransaction(LEDGER_PATH_ETH, rawTransaction, (response, error) => { + if (error) { + reject(error); + return; + } + + tx.v = Buffer.from(response.v, 'hex'); + tx.r = Buffer.from(response.r, 'hex'); + tx.s = Buffer.from(response.s, 'hex'); + + if (chainId !== Math.floor((tx.v[0] - 35) / 2)) { + reject(new Error('Invalid EIP155 signature received from Ledger.')); + return; + } + + resolve(`0x${tx.serialize().toString('hex')}`); + }); + }); + }); + } + + static create (api, ledger) { + if (!ledger) { + ledger = new LedgerEth(new Ledger3(SCRAMBLE_KEY)); + } + + return new Ledger(api, ledger); + } +} + +export { + LEDGER_PATH_ETC, + LEDGER_PATH_ETH +}; diff --git a/js/src/3rdparty/ledger/ledger.spec.js b/js/src/3rdparty/ledger/ledger.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..406a4bfcd07e4f738e8d68e128ad0980bbabbe27 --- /dev/null +++ b/js/src/3rdparty/ledger/ledger.spec.js @@ -0,0 +1,120 @@ +// 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 . + +import sinon from 'sinon'; + +import Ledger from './'; + +const TEST_ADDRESS = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + +let api; +let ledger; +let vendor; + +function createApi () { + api = { + net: { + version: sinon.stub().resolves('2') + } + }; + + return api; +} + +function createVendor (error = null) { + vendor = { + getAddress: (path, callback) => { + callback({ + address: TEST_ADDRESS + }, error); + }, + getAppConfiguration: (callback) => { + callback({}, error); + }, + signTransaction: (path, rawTransaction, callback) => { + callback({ + v: [39], + r: [0], + s: [0] + }, error); + } + }; + + return vendor; +} + +function create (error) { + ledger = new Ledger(createApi(), createVendor(error)); + + return ledger; +} + +describe('3rdparty/ledger', () => { + beforeEach(() => { + create(); + + sinon.spy(vendor, 'getAddress'); + sinon.spy(vendor, 'getAppConfiguration'); + sinon.spy(vendor, 'signTransaction'); + }); + + afterEach(() => { + vendor.getAddress.restore(); + vendor.getAppConfiguration.restore(); + vendor.signTransaction.restore(); + }); + + describe('getAppConfiguration', () => { + beforeEach(() => { + return ledger.getAppConfiguration(); + }); + + it('calls into getAppConfiguration', () => { + expect(vendor.getAppConfiguration).to.have.been.called; + }); + }); + + describe('scan', () => { + beforeEach(() => { + return ledger.scan(); + }); + + it('calls into getAddress', () => { + expect(vendor.getAddress).to.have.been.called; + }); + }); + + describe('signTransaction', () => { + beforeEach(() => { + return ledger.signTransaction({ + data: '0x0', + gasPrice: 20000000, + gasLimit: 1000000, + nonce: 2, + to: '0x63Cf90D3f0410092FC0fca41846f596223979195', + value: 1 + }); + }); + + it('retrieves chainId via API', () => { + expect(api.net.version).to.have.been.called; + }); + + it('calls into signTransaction', () => { + expect(vendor.signTransaction).to.have.been.called; + }); + }); +}); diff --git a/js/src/abi/util/signature.js b/js/src/abi/util/signature.js index ccc7cc0627f8c0b2cf5cfdea50252b051ac35125..86ed6f2657fe4adbaae471099ba53db97408e946 100644 --- a/js/src/abi/util/signature.js +++ b/js/src/abi/util/signature.js @@ -21,8 +21,9 @@ export function eventSignature (eventName, params) { const { strName, name } = parseName(eventName); const types = (params || []).map(fromParamType).join(','); const id = `${strName}(${types})`; + const signature = strName ? keccak_256(id) : ''; - return { id, name, signature: keccak_256(id) }; + return { id, name, signature }; } export function methodSignature (methodName, params) { diff --git a/js/src/abi/util/signature.spec.js b/js/src/abi/util/signature.spec.js index 1e9b7a9eed76986f7deec5f174fea23e0ef38bd4..118ebf4e510faf77b7a2a30a99b8cba8f524c9fb 100644 --- a/js/src/abi/util/signature.spec.js +++ b/js/src/abi/util/signature.spec.js @@ -46,7 +46,7 @@ describe('abi/util/signature', () => { expect(eventSignature(undefined, [])).to.deep.equal({ id: '()', name: undefined, - signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' + signature: '' }); }); @@ -54,7 +54,7 @@ describe('abi/util/signature', () => { expect(eventSignature(undefined, undefined)).to.deep.equal({ id: '()', name: undefined, - signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' + signature: '' }); }); }); @@ -96,7 +96,7 @@ describe('abi/util/signature', () => { expect(methodSignature(undefined, [])).to.deep.equal({ id: '()', name: undefined, - signature: '861731d5' + signature: '' }); }); @@ -104,7 +104,7 @@ describe('abi/util/signature', () => { expect(methodSignature(undefined, undefined)).to.deep.equal({ id: '()', name: undefined, - signature: '861731d5' + signature: '' }); }); }); diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 3869bddcad7637eae112ad32951959caf0d2290f..dd36afead1057acf1b9181c1992f11ade9df1743 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -107,34 +107,26 @@ export default class Contract { }); } - deploy (options, values, statecb) { - const setState = (state) => { - if (!statecb) { - return; - } - - return statecb(null, state); - }; - - setState({ state: 'estimateGas' }); + deploy (options, values, statecb = () => {}) { + statecb(null, { state: 'estimateGas' }); return this .deployEstimateGas(options, values) .then(([gasEst, gas]) => { options.gas = gas.toFixed(0); - setState({ state: 'postTransaction', gas }); + statecb(null, { state: 'postTransaction', gas }); - const _options = this._encodeOptions(this.constructors[0], options, values); + const encodedOptions = this._encodeOptions(this.constructors[0], options, values); return this._api.parity - .postTransaction(_options) + .postTransaction(encodedOptions) .then((requestId) => { - setState({ state: 'checkRequest', requestId }); + statecb(null, { state: 'checkRequest', requestId }); return this._pollCheckRequest(requestId); }) .then((txhash) => { - setState({ state: 'getTransactionReceipt', txhash }); + statecb(null, { state: 'getTransactionReceipt', txhash }); return this._pollTransactionReceipt(txhash, gas); }) .then((receipt) => { @@ -142,23 +134,23 @@ export default class Contract { throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); } - setState({ state: 'hasReceipt', receipt }); + statecb(null, { state: 'hasReceipt', receipt }); this._receipt = receipt; this._address = receipt.contractAddress; return this._address; - }); - }) - .then((address) => { - setState({ state: 'getCode' }); - return this._api.eth.getCode(this._address); - }) - .then((code) => { - if (code === '0x') { - throw new Error('Contract not deployed, getCode returned 0x'); - } + }) + .then((address) => { + statecb(null, { state: 'getCode' }); + return this._api.eth.getCode(this._address); + }) + .then((code) => { + if (code === '0x') { + throw new Error('Contract not deployed, getCode returned 0x'); + } - setState({ state: 'completed' }); - return this._address; + statecb(null, { state: 'completed' }); + return this._address; + }); }); } @@ -241,13 +233,32 @@ export default class Contract { _bindFunction = (func) => { func.contract = this; - func.call = (options, values = []) => { - const callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + func.call = (_options = {}, values = []) => { + const rawTokens = !!_options.rawTokens; + const options = { + ..._options + }; + + delete options.rawTokens; + + let callParams; + + try { + callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } return this._api.eth .call(callParams) .then((encoded) => func.decodeOutput(encoded)) - .then((tokens) => tokens.map((token) => token.value)) + .then((tokens) => { + if (rawTokens) { + return tokens; + } + + return tokens.map((token) => token.value); + }) .then((returns) => returns.length === 1 ? returns[0] : returns) .catch((error) => { console.warn(`${func.name}.call`, values, error); @@ -257,7 +268,13 @@ export default class Contract { if (!func.constant) { func.postTransaction = (options, values = []) => { - const _options = this._encodeOptions(func, this._addOptionsTo(options), values); + let _options; + + try { + _options = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } return this._api.parity .postTransaction(_options) diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index dc84215163063956bfe7c34f50e0ae4c65cce5f8..952002b606f89a0f9ecfd8a99d1fe6734af78c54 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -128,6 +128,18 @@ export function outLog (log) { return log; } +export function outHwAccountInfo (infos) { + return Object + .keys(infos) + .reduce((ret, _address) => { + const address = outAddress(_address); + + ret[address] = infos[_address]; + + return ret; + }, {}); +} + export function outNumber (number) { return new BigNumber(number || 0); } @@ -181,6 +193,16 @@ export function outReceipt (receipt) { return receipt; } +export function outRecentDapps (recentDapps) { + if (recentDapps) { + Object.keys(recentDapps).forEach((url) => { + recentDapps[url] = outDate(recentDapps[url]); + }); + } + + return recentDapps; +} + export function outSignerRequest (request) { if (request) { Object.keys(request).forEach((key) => { @@ -193,6 +215,13 @@ export function outSignerRequest (request) { request[key].signTransaction = outTransaction(request[key].signTransaction); request[key].sendTransaction = outTransaction(request[key].sendTransaction); break; + + case 'origin': + const type = Object.keys(request[key])[0]; + const details = request[key][type]; + + request[key] = { type, details }; + break; } }); } diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index 504cc0687aae8ea63c931bbf93b24b4941b4b898..c2375167080fcef8657ab7691f75e13481c45cd4 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outHwAccountInfo, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; describe('api/format/output', () => { @@ -163,6 +163,16 @@ describe('api/format/output', () => { }); }); + describe('outHwAccountInfo', () => { + it('returns objects with formatted addresses', () => { + expect(outHwAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } + }); + }); + }); + describe('outNumber', () => { it('returns a BigNumber equalling the value', () => { const bn = outNumber('0x123456'); @@ -337,6 +347,14 @@ describe('api/format/output', () => { }); }); + describe('outRecentDapps', () => { + it('formats the URLs with timestamps', () => { + expect(outRecentDapps({ testing: 0x57513668 })).to.deep.equal({ + testing: new Date('2016-06-03T07:48:56.000Z') + }); + }); + }); + describe('outSyncing', () => { ['currentBlock', 'highestBlock', 'startingBlock', 'warpChunksAmount', 'warpChunksProcessed'].forEach((input) => { it(`formats ${input} numbers as a number`, () => { diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 7001a9c49497213987e6a87311cc7cb7d5fec9d7..31de948dc3f9f2b4ac669077784c8163835cf207 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; -import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction, outVaultMeta } from '../../format/output'; +import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; export default class Parity { constructor (transport) { @@ -170,24 +170,42 @@ 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) .then(outVaultMeta); } + hardwareAccountsInfo () { + return this._transport + .execute('parity_hardwareAccountsInfo') + .then(outHwAccountInfo); + } + hashContent (url) { return this._transport .execute('parity_hashContent', url); @@ -195,7 +213,7 @@ export default class Parity { importGethAccounts (accounts) { return this._transport - .execute('parity_importGethAccounts', inAddresses) + .execute('parity_importGethAccounts', inAddresses(accounts)) .then(outAddresses); } @@ -222,7 +240,8 @@ export default class Parity { listRecentDapps () { return this._transport - .execute('parity_listRecentDapps'); + .execute('parity_listRecentDapps') + .then(outRecentDapps); } listStorageKeys (address, count, hash = null, blockNumber = 'latest') { @@ -390,9 +409,14 @@ export default class Parity { .execute('parity_setAuthor', inAddress(address)); } - setDappsAddresses (dappId, addresses) { + setDappAddresses (dappId, addresses) { + return this._transport + .execute('parity_setDappAddresses', dappId, inAddresses(addresses)); + } + + setDappDefaultAddress (dappId, address) { return this._transport - .execute('parity_setDappsAddresses', dappId, inAddresses(addresses)); + .execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null); } setEngineSigner (address, password) { @@ -430,9 +454,14 @@ export default class Parity { .execute('parity_setMode', mode); } - setNewDappsWhitelist (addresses) { + setNewDappsAddresses (addresses) { + return this._transport + .execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null); + } + + setNewDappsDefaultAddress (address) { return this._transport - .execute('parity_setNewDappsWhitelist', addresses ? inAddresses(addresses) : null); + .execute('parity_setNewDappsDefaultAddress', inAddress(address)); } setTransactionsLimit (quantity) { diff --git a/js/src/api/rpc/parity/parity.spec.js b/js/src/api/rpc/parity/parity.spec.js index e25d6668c293d120ee20b74c6a8453dbdd20344d..d7eb410476b3d902eb46bc3bf953be9afd4e6a0f 100644 --- a/js/src/api/rpc/parity/parity.spec.js +++ b/js/src/api/rpc/parity/parity.spec.js @@ -73,6 +73,21 @@ describe('api/rpc/parity', () => { }); }); + describe('importGethAccounts', () => { + const ACCOUNTS = ['0x63cf90d3f0410092fc0fca41846f596223979195']; + let scope; + + beforeEach(() => { + scope = mockHttp([{ method: 'parity_importGethAccounts', reply: { result: ACCOUNTS } }]); + }); + + it('passes the addresses through', () => { + return instance.importGethAccounts(ACCOUNTS).then((result) => { + expect(scope.body['parity_importGethAccounts'].params).to.deep.equal([ACCOUNTS]); + }); + }); + }); + describe('minGasPrice', () => { it('returns the min gasprice, formatted', () => { mockHttp([{ method: 'parity_minGasPrice', reply: { result: '0x123456' } }]); diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 4f386b7c8443878bc11034707747755945c5cdae..15b037b42c298bef1935010404712087e8a0139e 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -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; } }); diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 478c3e8c5d49c4a075cc222f40581a3409acb126..4b41935cdc22e104cc211bf83cb6a4f59dc1aa35 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -28,6 +28,7 @@ export default class Ws extends JsonRpcBase { this._url = url; this._token = token; this._messages = {}; + this._sessionHash = null; this._connecting = false; this._connected = false; @@ -78,12 +79,14 @@ export default class Ws extends JsonRpcBase { this._ws.onmessage = null; this._ws.close(); this._ws = null; + this._sessionHash = null; } this._connecting = true; this._connected = false; this._lastError = null; + this._sessionHash = sha3; this._ws = new WebSocket(this._url, hash); this._ws.onerror = this._onError; this._ws.onopen = this._onOpen; @@ -255,6 +258,10 @@ export default class Ws extends JsonRpcBase { return this._token; } + get sessionHash () { + return this._sessionHash; + } + get isAutoConnect () { return this._autoConnect; } diff --git a/js/src/api/util/encode.js b/js/src/api/util/encode.js index d727d1e632b346df66dcff12dc1966e9ef0c8e34..5b5fb5eacbc42d4bd5f84dc0015dd67d137a990d 100644 --- a/js/src/api/util/encode.js +++ b/js/src/api/util/encode.js @@ -34,7 +34,5 @@ export function abiEncode (methodName, inputTypes, data) { }) }, data); - return methodName === null - ? `0x${result.substr(10)}` - : result; + return result; } diff --git a/js/src/config.js b/js/src/config.js index 7a919e73f4e9e6bc6abbdd6994a99f2a1f1bae83..5113e9399b68530fe7a9c96e3b2bbf568447271f 100644 --- a/js/src/config.js +++ b/js/src/config.js @@ -21,6 +21,10 @@ export const LOG_KEYS = { key: 'balances', desc: 'Balances fetching' }, + CertificationsMiddleware: { + key: 'certifications.middleware', + desc: 'Certifications Middleware' + }, TransferModalStore: { key: 'modalsTransferStore', desc: 'Transfer modal MobX store' diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 7a959a2ef04a564d30c49a74d6758a12a130f4c7..8985d869ee85696962dfa5715298ed7688074b49 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -14,34 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import badgereg from './badgereg.json'; -import basiccoin from './basiccoin.json'; -import basiccoinmanager from './basiccoinmanager.json'; -import dappreg from './dappreg.json'; -import eip20 from './eip20.json'; -import emailverification from './email-verification.json'; -import gavcoin from './gavcoin.json'; -import githubhint from './githubhint.json'; -import owned from './owned.json'; -import registry from './registry.json'; -import signaturereg from './signaturereg.json'; -import smsverification from './sms-verification.json'; -import tokenreg from './tokenreg.json'; -import wallet from './wallet.json'; - -export { - badgereg, - basiccoin, - basiccoinmanager, - dappreg, - eip20, - emailverification, - gavcoin, - githubhint, - owned, - registry, - signaturereg, - smsverification, - tokenreg, - wallet -}; +export badgereg from './badgereg.json'; +export basiccoin from './basiccoin.json'; +export basiccoinmanager from './basiccoinmanager.json'; +export dappreg from './dappreg.json'; +export eip20 from './eip20.json'; +export emailverification from './email-verification.json'; +export gavcoin from './gavcoin.json'; +export githubhint from './githubhint.json'; +export owned from './owned.json'; +export registry from './registry.json'; +export registry2 from './registry2.json'; +export signaturereg from './signaturereg.json'; +export smsverification from './sms-verification.json'; +export tokenreg from './tokenreg.json'; +export wallet from './wallet.json'; diff --git a/js/src/contracts/abi/old-wallet.json b/js/src/contracts/abi/old-wallet.json new file mode 100644 index 0000000000000000000000000000000000000000..9300697422f5c730aff1d7a802cf6b2e6777b04f --- /dev/null +++ b/js/src/contracts/abi/old-wallet.json @@ -0,0 +1,466 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_addr", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_numOwners", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_lastDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resetSpentToday", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_spentToday", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_h", + "type": "bytes32" + } + ], + "name": "confirm", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "name": "_r", + "type": "bytes32" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + } + ], + "name": "revoke", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newRequired", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + }, + { + "name": "_owner", + "type": "address" + } + ], + "name": "hasConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "ownerIndex", + "type": "uint256" + } + ], + "name": "getOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "name": "kill", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + }, + { + "name": "_daylimit", + "type": "uint256" + } + ], + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Revoke", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + } + ], + "name": "OwnerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "RequirementChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "SingleTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "MultiTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "initiator", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "ConfirmationNeeded", + "type": "event" + } +] diff --git a/js/src/contracts/abi/registry2.json b/js/src/contracts/abi/registry2.json new file mode 100644 index 0000000000000000000000000000000000000000..922b9b7e24ab05ff864e83fda4ddb124acfc246b --- /dev/null +++ b/js/src/contracts/abi/registry2.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"key","type":"string"},{"indexed":false,"name":"plainKey","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] diff --git a/js/src/contracts/abi/wallet.json b/js/src/contracts/abi/wallet.json index 8048d239c502b5162fb5312815bb9b26cb81899b..752407e62521735187cfb94d932bf37cbd30baef 100644 --- a/js/src/contracts/abi/wallet.json +++ b/js/src/contracts/abi/wallet.json @@ -1 +1,476 @@ -[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] +[ + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_addr", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_numOwners", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_lastDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resetSpentToday", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_spentToday", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_h", + "type": "bytes32" + } + ], + "name": "confirm", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "name": "_r", + "type": "bytes32" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + } + ], + "name": "revoke", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newRequired", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + }, + { + "name": "_owner", + "type": "address" + } + ], + "name": "hasConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "ownerIndex", + "type": "uint256" + } + ], + "name": "getOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "name": "kill", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + }, + { + "name": "_daylimit", + "type": "uint256" + } + ], + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Revoke", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + } + ], + "name": "OwnerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "RequirementChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "created", + "type": "address" + } + ], + "name": "SingleTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "created", + "type": "address" + } + ], + "name": "MultiTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "initiator", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "ConfirmationNeeded", + "type": "event" + } +] diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js index 10e2c5699743d51b525627c44662d2fdcb67529b..7b172c3f1c0fcc3a40bbb9d5f778005ec1a8dffe 100644 --- a/js/src/contracts/code/wallet.js +++ b/js/src/contracts/code/wallet.js @@ -15,15 +15,16 @@ // along with Parity. If not, see . /** - * @version Solidity v0.4.6 + * @version Solidity v0.4.9 - Optimized * @from https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol - * @date 09-Dec-2016 @ 16h00 UTC + * @date 07-Mar-2017 @ 16h00 UTC */ -export const wallet = '0x6060604052346100005760405161041b38038061041b83398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173__WalletLibrary_________________________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b610337806100e46000396000f36060604052361561006c5760e060020a60003504632f54bf6e81146101245780634123cb6b146101485780635237509314610167578063659010e714610186578063746c9171146101a5578063c2cf7326146101c4578063c41a360a146101eb578063f1736d8614610217575b6101225b60003411156100c15760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a161011e565b600036111561011e5773__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750505b5b5b565b005b3461000057610134600435610236565b604080519115158252519081900360200190f35b3461000057610155610297565b60408051918252519081900360200190f35b346100005761015561029d565b60408051918252519081900360200190f35b34610000576101556102a3565b60408051918252519081900360200190f35b34610000576101556102a9565b60408051918252519081900360200190f35b34610000576101346004356024356102af565b604080519115158252519081900360200190f35b34610000576101fb600435610311565b60408051600160a060020a039092168252519081900360200190f35b3461000057610155610331565b60408051918252519081900360200190f35b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b92915050565b6000600582600101610100811015610000570160005b505490505b919050565b6002548156'; -export const walletLibrary = '0x606060405234610000575b611381806100186000396000f3606060405236156100da5760e060020a6000350463173825d981146100df5780632f54bf6e146100f157806352375093146101155780635c52c2f514610134578063659010e7146101435780637065cb4814610162578063797af627146101745780639da5e0eb14610198578063b20d30a9146101aa578063b61d27f6146101bc578063b75c7dc614610227578063ba51a6df14610239578063c2cf73261461024b578063c57c5f6014610272578063cbf0b0c0146102c6578063e46dcfeb146102d8578063f00d4b5d14610331578063f1736d8614610346575b610000565b34610000576100ef600435610365565b005b3461000057610101600435610452565b604080519115158252519081900360200190f35b3461000057610122610473565b60408051918252519081900360200190f35b34610000576100ef610479565b005b34610000576101226104b0565b60408051918252519081900360200190f35b34610000576100ef6004356104b6565b005b34610000576101016004356105a5565b604080519115158252519081900360200190f35b34610000576100ef60043561081e565b005b34610000576100ef600435610832565b005b3461000057604080516020600460443581810135601f810184900484028501840190955284845261010194823594602480359560649492939190920191819084018382808284375094965061086a95505050505050565b604080519115158252519081900360200190f35b34610000576100ef600435610bcc565b005b34610000576100ef600435610c77565b005b3461000057610101600435602435610cf9565b604080519115158252519081900360200190f35b34610000576100ef6004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610d4e92505050565b005b34610000576100ef600435610e13565b005b34610000576100ef60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284375094965050843594602001359350610e5192505050565b005b34610000576100ef600435602435610e6a565b005b3461000057610122610f63565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061038e81610f69565b1561044b57600160a060020a0383166000908152610105602052604090205491508115156103bb5761044b565b60016001540360005411156103cf5761044b565b6000600583610100811015610000570160005b5055600160a060020a03831660009081526101056020526040812055610406611108565b61040e6111dc565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60045481565b6000366040518083838082843782019150509250505060405180910390206104a081610f69565b156104ab5760006003555b5b5b50565b60035481565b6000366040518083838082843782019150509250505060405180910390206104dd81610f69565b1561059f576104eb82610452565b156104f55761059f565b6104fd611108565b60015460fa9010610510576105106111dc565b5b60015460fa90106105215761059f565b60018054810190819055600160a060020a03831690600590610100811015610000570160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b6000816105b181610f69565b156108155760008381526101086020526040902054600160a060020a0316156108155760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106665780601f1061063b57610100808354040283529160200191610666565b820191906000526020600020905b81548152906001019060200180831161064957829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561076b5780601f106107405761010080835404028352916020019161076b565b820191906000526020600020905b81548152906001019060200180831161074e57829003601f168201915b5050965050505050505060405180910390a1600083815261010860205260408120805473ffffffffffffffffffffffffffffffffffffffff19168155600180820183905560028083018054858255939493909281161561010002600019011604601f8190106107da575061080c565b601f01602090049060005260206000209081019061080c91905b8082111561080857600081556001016107f4565b5090565b5b505050600191505b5b5b5b50919050565b600281905561082b61130b565b6004555b50565b60003660405180838380828437820191505092505050604051809103902061085981610f69565b1561059f5760028290555b5b5b5050565b6000600061087733610452565b15610bc05761088584611315565b156109bc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004338587866040518085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109335780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a184600160a060020a03168484604051808280519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1925050509150610bc0565b600036436040518084848082843782019150508281526020019350505050604051809103902090506109ed816105a5565b158015610a10575060008181526101086020526040902054600160a060020a0316155b15610bc057600081815261010860209081526040822080546c01000000000000000000000000808a020473ffffffffffffffffffffffffffffffffffffffff199091161781556001808201889055865160029283018054818752958590209095601f9381161561010002600019011693909304820184900483019390929190880190839010610aaa57805160ff1916838001178555610ad7565b82800160010185558215610ad7579182015b82811115610ad7578251825591602001919060010190610abc565b5b50610af89291505b8082111561080857600081556001016107f4565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887604051808660001916815260200185600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610bae5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a15b5b5b5b5b509392505050565b600160a060020a033316600090815261010560205260408120549080821515610bf457610c70565b50506000828152610106602052604081206001810154600284900a929083161115610c705780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610c9e81610f69565b1561059f57600154821115610cb25761059f565b6000829055610cbf611108565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610d315760009350610d45565b8160020a9050808360010154166000141593505b50505092915050565b815160019081019055600033600160a060020a03166006825b505550600160a060020a033316600090815261010560205260408120600190558181555b825181101561044b57828181518110156100005790602001906020020151600160a060020a0316600582600201610100811015610000570160005b5081905550806002016101056000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610d8b565b5b505050565b600036604051808383808284378201915050925050506040518091039020610e3a81610f69565b1561059f5781600160a060020a0316ff5b5b5b5050565b610e5b8383610d4e565b61044b8161081e565b5b505050565b6000600036604051808383808284378201915050925050506040518091039020610e9381610f69565b15610c7057610ea183610452565b15610eab57610c70565b600160a060020a038416600090815261010560205260409020549150811515610ed357610c70565b610edb611108565b82600160a060020a0316600583610100811015610000570160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a033316600090815261010560205260408120548180821515610f91576110fe565b60008581526101066020526040902080549092501515611025576000805483556001808401919091556101078054918201808255828015829011610ffa57600083815260209020610ffa9181019083015b8082111561080857600081556001016107f4565b5090565b5b50505060028301819055610107805487929081101561000057906000526020600020900160005b50555b8260020a905080826001015416600014156110fe5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116110eb5760008581526101066020526040902060020154610107805490919081101561000057906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506110fe566110fe565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156111855761010781815481101561000057906000526020600020900160005b50541561117c57610106600061010783815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161110f565b610107805460008083559190915261044b907f47c4908e245f386bfc1825973249847f4053a761ddb4880ad63c323a7b5a2a25908101905b8082111561080857600081556001016107f4565b5090565b5b505b5050565b60015b6001548110156104ab575b6001548110801561120c5750600581610100811015610000570160005b505415155b15611219576001016111ea565b5b600160015411801561123e57506005600154610100811015610000570160005b5054155b15611252576001805460001901905561121a565b6001548110801561127657506005600154610100811015610000570160005b505415155b80156112925750600581610100811015610000570160005b5054155b15611302576005600154610100811015610000570160005b5054600582610100811015610000570160005b5055806101056000600583610100811015610000570160005b505481526020019081526020016000208190555060006005600154610100811015610000570160005b50555b6111df565b5b50565b6201518042045b90565b600061132033610452565b1561046e5760045461133061130b565b111561134757600060035561134361130b565b6004555b600354828101108015906113615750600254826003540111155b1561137657506003805482019055600161046e565b5060005b5b5b91905056'; +export const wallet = '0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173_____________WalletLibrary______________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a723058204a75c2f5c8009054bd9e9998e8bb6f4bca0b201484709f357b482793957c47130029'; +export const walletLibrary = '0x6060604052341561000c57fe5b5b6116d88061001c6000396000f300606060405236156101015763ffffffff60e060020a600035041663173825d981146101575780632f54bf6e146101755780634123cb6b146101a557806352375093146101c75780635c52c2f5146101e9578063659010e7146101fb5780637065cb481461021d578063746c91711461023b578063797af6271461025d5780639da5e0eb14610284578063b20d30a914610299578063b61d27f6146102ae578063b75c7dc6146102ec578063ba51a6df14610301578063c2cf732614610316578063c41a360a14610349578063c57c5f6014610378578063cbf0b0c0146103cf578063e46dcfeb146103ed578063f00d4b5d14610449578063f1736d861461046d575b6101555b60003411156101525760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561015f57fe5b610155600160a060020a036004351661048f565b005b341561017d57fe5b610191600160a060020a036004351661057d565b604080519115158252519081900360200190f35b34156101ad57fe5b6101b561059e565b60408051918252519081900360200190f35b34156101cf57fe5b6101b56105a4565b60408051918252519081900360200190f35b34156101f157fe5b6101556105aa565b005b341561020357fe5b6101b56105e1565b60408051918252519081900360200190f35b341561022557fe5b610155600160a060020a03600435166105e7565b005b341561024357fe5b6101b56106d7565b60408051918252519081900360200190f35b341561026557fe5b6101916004356106dd565b604080519115158252519081900360200190f35b341561028c57fe5b610155600435610a2f565b005b34156102a157fe5b610155600435610a43565b005b34156102b657fe5b6101b560048035600160a060020a0316906024803591604435918201910135610a7b565b60408051918252519081900360200190f35b34156102f457fe5b610155600435610d5d565b005b341561030957fe5b610155600435610e08565b005b341561031e57fe5b610191600435600160a060020a0360243516610e8a565b604080519115158252519081900360200190f35b341561035157fe5b61035c600435610edf565b60408051600160a060020a039092168252519081900360200190f35b341561038057fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610f0092505050565b005b34156103d757fe5b610155600160a060020a0360043516610fd4565b005b34156103f557fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505084359460200135935061101292505050565b005b341561045157fe5b610155600160a060020a036004358116906024351661102b565b005b341561047557fe5b6101b5611125565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206104b88161112b565b1561057657600160a060020a0383166000908152610105602052604090205491508115156104e557610576565b60016001540360005411156104f957610576565b6000600583610100811061050957fe5b0160005b5055600160a060020a03831660009081526101056020526040812055610531611296565b610539611386565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60015481565b60045481565b6000366040518083838082843782019150509250505060405180910390206105d18161112b565b156105dc5760006003555b5b5b50565b60035481565b60003660405180838380828437820191505092505050604051809103902061060e8161112b565b156106d15761061c8261057d565b15610626576106d1565b61062e611296565b60015460fa901061064157610641611386565b5b60015460fa9010610652576106d1565b60018054810190819055600160a060020a03831690600590610100811061067557fe5b0160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60006000826106eb8161112b565b15610a255760008481526101086020526040902054600160a060020a031615158061072757506000848152610108602052604090206001015415155b80610754575060008481526101086020526040902060029081015461010060018216150260001901160415155b15610a255760008481526101086020526040902054600160a060020a0316151561082c57600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261082594909391929183018282801561081b5780601f106107f05761010080835404028352916020019161081b565b820191906000526020600020905b8154815290600101906020018083116107fe57829003601f168201915b50505050506114c2565b91506108e3565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156108be5780601f10610893576101008083540402835291602001916108be565b820191906000526020600020905b8154815290600101906020018083116108a157829003601f168201915b505091505060006040518083038185876185025a03f19250505015156108e357610000565b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156109d95780601f106109ae576101008083540402835291602001916109d9565b820191906000526020600020905b8154815290600101906020018083116109bc57829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610a1e6002830182611557565b5050600192505b5b5b5b5050919050565b6002819055610a3c6114dc565b6004555b50565b600036604051808383808284378201915050925050506040518091039020610a6a8161112b565b156106d15760028290555b5b5b5050565b60006000610a883361057d565b15610d505782158015610a9f5750610a9f856114eb565b5b80610aad57506000546001145b15610bef57600160a060020a0386161515610b0357610afc8585858080601f016020809104026020016040519081016040528093929190818152602001838380828437506114c2945050505050565b9050610b43565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610b4357610000565b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610d50565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610c4e575060008281526101086020526040902060010154155b8015610c7b5750600082815261010860205260409020600290810154610100600182161502600019011604155b15610cbf576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610cbd90600201858561159f565b505b610cc8826106dd565b1515610d505760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010560205260408120549080821515610d8557610e01565b50506000828152610106602052604081206001810154600284900a929083161115610e015780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610e2f8161112b565b156106d157600154821115610e43576106d1565b6000829055610e50611296565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610ec25760009350610ed6565b8160020a9050808360010154166000141593505b50505092915050565b60006005600183016101008110610ef257fe5b0160005b505490505b919050565b815160019081018155600090600160a060020a033316906005905b0160005b505550600160a060020a033316600090815261010560205260408120600190555b8251811015610fc9578281815181101515610f5757fe5b60209081029091010151600160a060020a03166005600283016101008110610f7b57fe5b0160005b50819055508060020161010560008584815181101515610f9b57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610f40565b60008290555b505050565b600036604051808383808284378201915050925050506040518091039020610ffb8161112b565b156106d15781600160a060020a0316ff5b5b5b5050565b61101b81610a2f565b6105768383610f00565b5b505050565b60006000366040518083838082843782019150509250505060405180910390206110548161112b565b15610e01576110628361057d565b1561106c57610e01565b600160a060020a03841660009081526101056020526040902054915081151561109457610e01565b61109c611296565b600160a060020a03831660058361010081106110b457fe5b0160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a0333166000908152610105602052604081205481808215156111535761128c565b600085815261010660205260409020805490925015156111b65760008054835560018084019190915561010780549161118e9190830161161e565b60028301819055610107805487929081106111a557fe5b906000526020600020900160005b50555b8260020a9050808260010154166000141561128c5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a181546001901161127957600085815261010660205260409020600201546101078054909190811061123c57fe5b906000526020600020900160005b50600090819055858152610106602052604081208181556001808201839055600290910191909155935061128c565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b81811015611374576101086000610107838154811015156112ba57fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a031916815560018101829055906112ff6002830182611557565b505061010780548290811061131057fe5b906000526020600020900160005b50541561136b5761010660006101078381548110151561133a57fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161129d565b6106d16101076000611648565b5b5050565b60015b6001548110156105dc575b600154811080156113b7575060058161010081106113ae57fe5b0160005b505415155b156113c457600101611394565b5b60016001541180156113eb575060015460059061010081106113e357fe5b0160005b5054155b156113ff57600180546000190190556113c4565b600154811080156114255750600154600590610100811061141c57fe5b0160005b505415155b80156114425750600581610100811061143a57fe5b0160005b5054155b156114b957600154600590610100811061145857fe5b0160005b5054600582610100811061146c57fe5b0160005b5055806101056000600583610100811061148657fe5b0160005b505481526020019081526020016000208190555060006005600154610100811015156114b257fe5b0160005b50555b611389565b5b50565b600081516020830184f09050803b15610000575b92915050565b600062015180425b0490505b90565b60006114f63361057d565b15610599576004546115066114dc565b111561151d5760006003556115196114dc565b6004555b600354828101108015906115375750600254826003540111155b1561154c575060038054820190556001610599565b5060005b5b5b919050565b50805460018160011615610100020316600290046000825580601f1061157d57506105dc565b601f0160209004906000526020600020908101906105dc919061166a565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106115e05782800160ff1982351617855561160d565b8280016001018555821561160d579182015b8281111561160d5782358255916020019190600101906115f2565b5b5061161a92915061166a565b5090565b8154818355818115116105765760008381526020902061057691810190830161166a565b5b505050565b50805460008255906000526020600020908101906105dc919061166a565b5b50565b6114e891905b8082111561161a5760008155600101611670565b5090565b90565b6114e891905b8082111561161a5760008155600101611670565b5090565b905600a165627a7a723058206560ca68304798da7e3be68397368a30b63db1453ff138ff8f765e80080025af0029'; +export const walletLibraryABI = '[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"o_success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_limit","type":"uint256"}],"name":"initDaylimit","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"o_hash","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"}],"name":"initMultiowned","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"name":"initWallet","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"created","type":"address"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"created","type":"address"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]'; export const walletSourceURL = 'https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol'; -export const walletLibraryRegKey = 'walletLibrary'; +export const walletLibraryRegKey = 'walletLibrary.v.2'; // Used if no Wallet Library found in registry... -// Compiled from `wallet.sol` using Solidity v0.4.6 -export const fullWalletCode = '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; +// Compiled from `wallet.sol` using Solidity v0.4.9 - Optimized +export const fullWalletCode = '0x6060604052341561000c57fe5b60405161166d38038061166d83398101604090815281516020830151918301519201915b805b83835b815160019081018155600090600160a060020a033316906002905b0160005b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100fd57828181518110151561008c57fe5b60209081029091010151600160a060020a0316600282810161010081106100af57fe5b0160005b508190555080600201610102600085848151811015156100cf57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610075565b60008290555b50505061010581905561012164010000000061138f61013082021704565b610107555b505b50505061013f565b600062015180425b0490505b90565b61151f8061014e6000396000f300606060405236156100e05763ffffffff60e060020a600035041663173825d981146101365780632f54bf6e146101545780634123cb6b1461018457806352375093146101a65780635c52c2f5146101c8578063659010e7146101da5780637065cb48146101fc578063746c91711461021a578063797af6271461023c578063b20d30a914610263578063b61d27f614610278578063b75c7dc6146102b6578063ba51a6df146102cb578063c2cf7326146102e0578063c41a360a14610313578063cbf0b0c014610342578063f00d4b5d14610360578063f1736d8614610384575b6101345b60003411156101315760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561013e57fe5b610134600160a060020a03600435166103a6565b005b341561015c57fe5b610170600160a060020a0360043516610494565b604080519115158252519081900360200190f35b341561018c57fe5b6101946104b5565b60408051918252519081900360200190f35b34156101ae57fe5b6101946104bb565b60408051918252519081900360200190f35b34156101d057fe5b6101346104c2565b005b34156101e257fe5b6101946104fa565b60408051918252519081900360200190f35b341561020457fe5b610134600160a060020a0360043516610501565b005b341561022257fe5b6101946105f1565b60408051918252519081900360200190f35b341561024457fe5b6101706004356105f7565b604080519115158252519081900360200190f35b341561026b57fe5b610134600435610949565b005b341561028057fe5b61019460048035600160a060020a0316906024803591604435918201910135610982565b60408051918252519081900360200190f35b34156102be57fe5b610134600435610c64565b005b34156102d357fe5b610134600435610d0f565b005b34156102e857fe5b610170600435600160a060020a0360243516610d91565b604080519115158252519081900360200190f35b341561031b57fe5b610326600435610de6565b60408051600160a060020a039092168252519081900360200190f35b341561034a57fe5b610134600160a060020a0360043516610e07565b005b341561036857fe5b610134600160a060020a0360043581169060243516610e45565b005b341561038c57fe5b610194610f3f565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206103cf81610f46565b1561048d57600160a060020a0383166000908152610102602052604090205491508115156103fc5761048d565b60016001540360005411156104105761048d565b6000600283610100811061042057fe5b0160005b5055600160a060020a038316600090815261010260205260408120556104486110b1565b610450611132565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b6000366040518083838082843782019150509250505060405180910390206104e981610f46565b156104f5576000610106555b5b5b50565b6101065481565b60003660405180838380828437820191505092505050604051809103902061052881610f46565b156105eb5761053682610494565b15610540576105eb565b6105486110b1565b60015460fa901061055b5761055b611132565b5b60015460fa901061056c576105eb565b60018054810190819055600160a060020a03831690600290610100811061058f57fe5b0160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b600060008261060581610f46565b1561093f5760008481526101086020526040902054600160a060020a031615158061064157506000848152610108602052604090206001015415155b8061066e575060008481526101086020526040902060029081015461010060018216150260001901160415155b1561093f5760008481526101086020526040902054600160a060020a0316151561074657600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261073f9490939192918301828280156107355780601f1061070a57610100808354040283529160200191610735565b820191906000526020600020905b81548152906001019060200180831161071857829003601f168201915b505050505061126e565b91506107fd565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156107d85780601f106107ad576101008083540402835291602001916107d8565b820191906000526020600020905b8154815290600101906020018083116107bb57829003601f168201915b505091505060006040518083038185876185025a03f19250505015156107fd57610000565b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156108f35780601f106108c8576101008083540402835291602001916108f3565b820191906000526020600020905b8154815290600101906020018083116108d657829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610938600283018261139e565b5050600192505b5b5b5b5050919050565b60003660405180838380828437820191505092505050604051809103902061097081610f46565b156105eb576101058290555b5b5b5050565b6000600061098f33610494565b15610c5757821580156109a657506109a685611288565b5b806109b457506000546001145b15610af657600160a060020a0386161515610a0a57610a038585858080601f0160208091040260200160405190810160405280939291908181526020018383808284375061126e945050505050565b9050610a4a565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610a4a57610000565b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610c57565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610b55575060008281526101086020526040902060010154155b8015610b825750600082815261010860205260409020600290810154610100600182161502600019011604155b15610bc6576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610bc49060020185856113e6565b505b610bcf826105f7565b1515610c575760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010260205260408120549080821515610c8c57610d08565b50506000828152610103602052604081206001810154600284900a929083161115610d085780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610d3681610f46565b156105eb57600154821115610d4a576105eb565b6000829055610d576110b1565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610dc95760009350610ddd565b8160020a9050808360010154166000141593505b50505092915050565b60006002600183016101008110610df957fe5b0160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610e2e81610f46565b156105eb5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610e6e81610f46565b15610d0857610e7c83610494565b15610e8657610d08565b600160a060020a038416600090815261010260205260409020549150811515610eae57610d08565b610eb66110b1565b600160a060020a0383166002836101008110610ece57fe5b0160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610f6e576110a7565b60008581526101036020526040902080549092501515610fd157600080548355600180840191909155610104805491610fa991908301611465565b6002830181905561010480548792908110610fc057fe5b906000526020600020900160005b50555b8260020a905080826001015416600014156110a75760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a181546001901161109457600085815261010360205260409020600201546101048054909190811061105757fe5b906000526020600020900160005b5060009081905585815261010360205260408120818155600180820183905560029091019190915593506110a7565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015611125576101086000610104838154811015156110d557fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600181018290559061111a600283018261139e565b50505b6001016110b8565b6105eb6112fb565b5b5050565b60015b6001548110156104f5575b600154811080156111635750600281610100811061115a57fe5b0160005b505415155b1561117057600101611140565b5b60016001541180156111975750600154600290610100811061118f57fe5b0160005b5054155b156111ab5760018054600019019055611170565b600154811080156111d1575060015460029061010081106111c857fe5b0160005b505415155b80156111ee575060028161010081106111e657fe5b0160005b5054155b1561126557600154600290610100811061120457fe5b0160005b5054600282610100811061121857fe5b0160005b5055806101026000600283610100811061123257fe5b0160005b5054815260200190815260200160002081905550600060026001546101008110151561125e57fe5b0160005b50555b611135565b5b50565b600081516020830184f09050803b15610000575b92915050565b600061129333610494565b156104b057610107546112a461138f565b11156112bd576000610106556112b861138f565b610107555b61010654828101108015906112da57506101055482610106540111155b156112f0575061010680548201905560016104b0565b5060005b5b5b919050565b6101045460005b8181101561137d5761010480548290811061131957fe5b906000526020600020900160005b5054156113745761010360006101048381548110151561134357fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b600101611302565b6105eb610104600061148f565b5b5050565b600062015180425b0490505b90565b50805460018160011615610100020316600290046000825580601f106113c457506104f5565b601f0160209004906000526020600020908101906104f591906114b1565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106114275782800160ff19823516178555611454565b82800160010185558215611454579182015b82811115611454578235825591602001919060010190611439565b5b506114619291506114b1565b5090565b81548183558181151161048d5760008381526020902061048d9181019083016114b1565b5b505050565b50805460008255906000526020600020908101906104f591906114b1565b5b50565b61139b91905b8082111561146157600081556001016114b7565b5090565b90565b61139b91905b8082111561146157600081556001016114b7565b5090565b905600a165627a7a723058203a7ac7072dc640002704b704af82b742650362cd55debf72fca105c2b916e01d0029'; diff --git a/js/src/contracts/snippets/enhanced-wallet.sol b/js/src/contracts/snippets/enhanced-wallet.sol index 374eb595fe4c06199c2e834cfb0e96eb1485ce16..1f89b1f6f21a861ac41a71f126694e009f217c19 100644 --- a/js/src/contracts/snippets/enhanced-wallet.sol +++ b/js/src/contracts/snippets/enhanced-wallet.sol @@ -8,453 +8,454 @@ // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. -pragma solidity ^0.4.6; - -contract multisig { - // EVENTS - - // this contract can accept a confirmation, in which case - // we record owner and operation (hash) alongside it. - event Confirmation(address owner, bytes32 operation); - event Revoke(address owner, bytes32 operation); - - // some others are in the case of an owner changing. - event OwnerChanged(address oldOwner, address newOwner); - event OwnerAdded(address newOwner); - event OwnerRemoved(address oldOwner); - - // the last one is emitted if the required signatures change - event RequirementChanged(uint newRequirement); - - // Funds has arrived into the wallet (record how much). - event Deposit(address _from, uint value); - // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). - event SingleTransact(address owner, uint value, address to, bytes data); - // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). - event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); - // Confirmation still needed for a transaction. - event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); -} - -contract multisigAbi is multisig { - function isOwner(address _addr) returns (bool); - - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); - function confirm(bytes32 _h) returns(bool); +pragma solidity ^0.4.9; - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit); +contract WalletEvents { + // EVENTS - function addOwner(address _owner); + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); - function removeOwner(address _owner); + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); - function changeRequirement(uint _newRequired); + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation); - - function changeOwner(address _from, address _to); - - function execute(address _to, uint _value, bytes _data) returns(bool); + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); } -contract WalletLibrary is multisig { - // TYPES +contract WalletAbi { + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external; - // struct for the status of a pending operation. - struct PendingState { - uint yetNeeded; - uint ownersDone; - uint index; - } + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) external; - // Transaction structure to remember details of transaction lest it need be saved for a later call. - struct Transaction { - address to; - uint value; - bytes data; - } + function addOwner(address _owner) external; - /****************************** - ***** MULTI OWNED SECTION **** - ******************************/ + function removeOwner(address _owner) external; - // MODIFIERS + function changeRequirement(uint _newRequired) external; - // simple single-sig function modifier. - modifier onlyowner { - if (isOwner(msg.sender)) - _; - } - // multi-sig function modifier: the operation must have an intrinsic hash in order - // that later attempts can be realised as the same underlying operation and - // thus count as confirmations. - modifier onlymanyowners(bytes32 _operation) { - if (confirmAndCheck(_operation)) - _; - } + function isOwner(address _addr) constant returns (bool); - // METHODS - - // constructor is given number of sigs required to do protected "onlymanyowners" transactions - // as well as the selection of addresses capable of confirming them. - function initMultiowned(address[] _owners, uint _required) { - m_numOwners = _owners.length + 1; - m_owners[1] = uint(msg.sender); - m_ownerIndex[uint(msg.sender)] = 1; - m_required = _required; - - for (uint i = 0; i < _owners.length; ++i) - { - m_owners[2 + i] = uint(_owners[i]); - m_ownerIndex[uint(_owners[i])] = 2 + i; - } - } + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) { - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - uint ownerIndexBit = 2**ownerIndex; - var pending = m_pending[_operation]; - if (pending.ownersDone & ownerIndexBit > 0) { - pending.yetNeeded++; - pending.ownersDone -= ownerIndexBit; - Revoke(msg.sender, _operation); - } - } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) external; - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { - if (isOwner(_to)) return; - uint ownerIndex = m_ownerIndex[uint(_from)]; - if (ownerIndex == 0) return; - - clearPending(); - m_owners[ownerIndex] = uint(_to); - m_ownerIndex[uint(_from)] = 0; - m_ownerIndex[uint(_to)] = ownerIndex; - OwnerChanged(_from, _to); - } + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); +} - function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { - if (isOwner(_owner)) return; - - clearPending(); - if (m_numOwners >= c_maxOwners) - reorganizeOwners(); - if (m_numOwners >= c_maxOwners) - return; - m_numOwners++; - m_owners[m_numOwners] = uint(_owner); - m_ownerIndex[uint(_owner)] = m_numOwners; - OwnerAdded(_owner); +contract WalletLibrary is WalletEvents { + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; } - - function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { - uint ownerIndex = m_ownerIndex[uint(_owner)]; - if (ownerIndex == 0) return; - if (m_required > m_numOwners - 1) return; - - m_owners[ownerIndex] = 0; - m_ownerIndex[uint(_owner)] = 0; - clearPending(); - reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot - OwnerRemoved(_owner); + m_required = _required; + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); } - - function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { - if (_newRequired > m_numOwners) return; - m_required = _newRequired; - clearPending(); - RequirementChanged(_newRequired); + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + initDaylimit(_daylimit); + initMultiowned(_owners, _required); + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } } + } - function isOwner(address _addr) returns (bool) { - return m_ownerIndex[uint(_addr)] > 0; + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(invalidJumpLabel, iszero(extcodesize(o_addr))) } - - - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - var pending = m_pending[_operation]; - uint ownerIndex = m_ownerIndex[uint(_owner)]; - - // make sure they're an owner - if (ownerIndex == 0) return false; - - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - return !(pending.ownersDone & ownerIndexBit == 0); + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; } - - // INTERNAL METHODS - - function confirmAndCheck(bytes32 _operation) internal returns (bool) { - // determine what index the present sender is: - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - - var pending = m_pending[_operation]; - // if we're not yet working on this operation, switch over and reset the confirmation status. - if (pending.yetNeeded == 0) { - // reset count of confirmations needed. - pending.yetNeeded = m_required; - // reset which owners have confirmed (none) - set our bitmap to 0. - pending.ownersDone = 0; - pending.index = m_pendingIndex.length++; - m_pendingIndex[pending.index] = _operation; - } - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - // make sure we (the message sender) haven't confirmed this operation previously. - if (pending.ownersDone & ownerIndexBit == 0) { - Confirmation(msg.sender, _operation); - // ok - check if count is enough to go ahead. - if (pending.yetNeeded <= 1) { - // enough confirmations: reset and run interior. - delete m_pendingIndex[m_pending[_operation].index]; - delete m_pending[_operation]; - return true; - } - else - { - // not enough: record that this owner in particular confirmed. - pending.yetNeeded--; - pending.ownersDone |= ownerIndexBit; - } - } + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; } - - function reorganizeOwners() private { - uint free = 1; - while (free < m_numOwners) - { - while (free < m_numOwners && m_owners[free] != 0) free++; - while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; - if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) - { - m_owners[free] = m_owners[m_numOwners]; - m_ownerIndex[m_owners[free]] = free; - m_owners[m_numOwners] = 0; - } - } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } } - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - if (m_pendingIndex[i] != 0) - delete m_pending[m_pendingIndex[i]]; - delete m_pendingIndex; + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } } - - - /****************************** - ****** DAY LIMIT SECTION ***** - ******************************/ - - // MODIFIERS - - // simple modifier for daily limit. - modifier limitedDaily(uint _value) { - if (underLimit(_value)) - _; - } - - // METHODS - - // constructor - stores initial daily limit and records the present day's index. - function initDaylimit(uint _limit) { - m_dailyLimit = _limit; - m_lastDay = today(); + } + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); } - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) { - m_dailyLimit = _newLimit; - } - // resets the amount already spent today. needs many of the owners to confirm. - function resetSpentToday() onlymanyowners(sha3(msg.data)) { - m_spentToday = 0; - } - - // INTERNAL METHODS - - // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and - // returns true. otherwise just returns false. - function underLimit(uint _value) internal onlyowner returns (bool) { - // reset the spend limit if we're on a different day to last time. - if (today() > m_lastDay) { - m_spentToday = 0; - m_lastDay = today(); - } - // check to see if there's enough left - if so, subtract and return true. - // overflow protection // dailyLimit check - if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { - m_spentToday += _value; - return true; - } - return false; + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; } + return false; + } - // determines today's index. - function today() private constant returns (uint) { return now / 1 days; } + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + function clearPending() internal { + uint length = m_pendingIndex.length; - /****************************** - ********* WALLET SECTION ***** - ******************************/ + for (uint i = 0; i < length; ++i) { + delete m_txs[m_pendingIndex[i]]; - // METHODS - - // constructor - just pass on the owner array to the multiowned and - // the limit to daylimit - function initWallet(address[] _owners, uint _required, uint _daylimit) { - initMultiowned(_owners, _required); - initDaylimit(_daylimit) ; + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; } - // kills the contract sending everything to `_to`. - function kill(address _to) onlymanyowners(sha3(msg.data)) { - suicide(_to); - } + delete m_pendingIndex; + } - // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. - // If not, goes into multisig process. We provide a hash on return to allow the sender to provide - // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value - // and _data arguments). They still get the option of using them if they want, anyways. - function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) { - // first, take the opportunity to check that we're under the daily limit. - if (underLimit(_value)) { - SingleTransact(msg.sender, _value, _to, _data); - // yes - just execute the call. - _callValue =_to.call.value(_value)(_data); - } else { - // determine our operation hash. - bytes32 _r = sha3(msg.data, block.number); - if (!confirm(_r) && m_txs[_r].to == 0) { - m_txs[_r].to = _to; - m_txs[_r].value = _value; - m_txs[_r].data = _data; - ConfirmationNeeded(_r, msg.sender, _value, _to, _data); - } - } - } + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order - // to determine the body of the transaction from the hash provided. - function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { - if (m_txs[_h].to != 0) { - m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); - MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); - delete m_txs[_h]; - return true; - } - } - - // INTERNAL METHODS - - function clearWalletPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - delete m_txs[m_pendingIndex[i]]; - clearPending(); - } - - // FIELDS - address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - - // the number of owners that must confirm the same operation before it is run. - uint m_required; - // pointer used to find a free slot in m_owners - uint m_numOwners; + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; - // list of owners - uint[256] m_owners; - uint constant c_maxOwners = 250; + // list of owners + uint[256] m_owners; - // index on the list of owners to allow reverse lookup - mapping(uint => uint) m_ownerIndex; - // the ongoing operations. - mapping(bytes32 => PendingState) m_pending; - bytes32[] m_pendingIndex; + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; - // pending transactions we have at present. - mapping (bytes32 => Transaction) m_txs; + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; } - -contract Wallet is multisig { - - // WALLET CONSTRUCTOR - // calls the `initWallet` method of the Library in this context - function Wallet(address[] _owners, uint _required, uint _daylimit) { - // Signature of the Wallet Library's init function - bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); - address target = _walletLibrary; - - // Compute the size of the call data : arrays has 2 - // 32bytes for offset and length, plus 32bytes per element ; - // plus 2 32bytes for each uint - uint argarraysize = (2 + _owners.length); - uint argsize = (2 + argarraysize) * 32; - - assembly { - // Add the signature first to memory - mstore(0x0, sig) - // Add the call data, which is at the end of the - // code - codecopy(0x4, sub(codesize, argsize), argsize) - // Delegate call to the library - delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) - } +contract Wallet is WalletEvents { + + // WALLET CONSTRUCTOR + // calls the `initWallet` method of the Library in this context + function Wallet(address[] _owners, uint _required, uint _daylimit) { + // Signature of the Wallet Library's init function + bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); + address target = _walletLibrary; + + // Compute the size of the call data : arrays has 2 + // 32bytes for offset and length, plus 32bytes per element ; + // plus 2 32bytes for each uint + uint argarraysize = (2 + _owners.length); + uint argsize = (2 + argarraysize) * 32; + + assembly { + // Add the signature first to memory + mstore(0x0, sig) + // Add the call data, which is at the end of the + // code + codecopy(0x4, sub(codesize, argsize), argsize) + // Delegate call to the library + delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) } + } - // METHODS + // METHODS - // gets called when no other function matches - function() payable { - // just being sent some cash? - if (msg.value > 0) - Deposit(msg.sender, msg.value); - else if (msg.data.length > 0) - _walletLibrary.delegatecall(msg.data); - } + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } - // Gets an owner by 0-indexed position (using numOwners as the count) - function getOwner(uint ownerIndex) constant returns (address) { - return address(m_owners[ownerIndex + 1]); - } + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } - // As return statement unavailable in fallback, explicit the method here + // As return statement unavailable in fallback, explicit the method here - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - return _walletLibrary.delegatecall(msg.data); - } + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } - function isOwner(address _addr) returns (bool) { - return _walletLibrary.delegatecall(msg.data); - } + function isOwner(address _addr) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } - // FIELDS - address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - // the number of owners that must confirm the same operation before it is run. - uint public m_required; - // pointer used to find a free slot in m_owners - uint public m_numOwners; + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; - // list of owners - uint[256] m_owners; + // list of owners + uint[256] m_owners; } diff --git a/js/src/contracts/snippets/wallet.sol b/js/src/contracts/snippets/wallet.sol index b369eea76ea1880c5a063283bf76db2e5a3ac495..18de9df68c161461a41133317a91aef07c8d6ed9 100644 --- a/js/src/contracts/snippets/wallet.sol +++ b/js/src/contracts/snippets/wallet.sol @@ -8,221 +8,222 @@ // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. -pragma solidity ^0.4.6; -contract multiowned { - - // TYPES - - // struct for the status of a pending operation. - struct PendingState { - uint yetNeeded; - uint ownersDone; - uint index; - } - - // EVENTS - - // this contract only has six types of events: it can accept a confirmation, in which case - // we record owner and operation (hash) alongside it. - event Confirmation(address owner, bytes32 operation); - event Revoke(address owner, bytes32 operation); - // some others are in the case of an owner changing. - event OwnerChanged(address oldOwner, address newOwner); - event OwnerAdded(address newOwner); - event OwnerRemoved(address oldOwner); - // the last one is emitted if the required signatures change - event RequirementChanged(uint newRequirement); - - // MODIFIERS - - // simple single-sig function modifier. - modifier onlyowner { - if (isOwner(msg.sender)) - _; - } - // multi-sig function modifier: the operation must have an intrinsic hash in order - // that later attempts can be realised as the same underlying operation and - // thus count as confirmations. - modifier onlymanyowners(bytes32 _operation) { - if (confirmAndCheck(_operation)) - _; - } - - // METHODS - - // constructor is given number of sigs required to do protected "onlymanyowners" transactions - // as well as the selection of addresses capable of confirming them. - function multiowned(address[] _owners, uint _required) { - m_numOwners = _owners.length + 1; - m_owners[1] = uint(msg.sender); - m_ownerIndex[uint(msg.sender)] = 1; - for (uint i = 0; i < _owners.length; ++i) - { - m_owners[2 + i] = uint(_owners[i]); - m_ownerIndex[uint(_owners[i])] = 2 + i; - } - m_required = _required; - } +pragma solidity ^0.4.9; - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) external { - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - uint ownerIndexBit = 2**ownerIndex; - var pending = m_pending[_operation]; - if (pending.ownersDone & ownerIndexBit > 0) { - pending.yetNeeded++; - pending.ownersDone -= ownerIndexBit; - Revoke(msg.sender, _operation); - } - } - - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_to)) return; - uint ownerIndex = m_ownerIndex[uint(_from)]; - if (ownerIndex == 0) return; - - clearPending(); - m_owners[ownerIndex] = uint(_to); - m_ownerIndex[uint(_from)] = 0; - m_ownerIndex[uint(_to)] = ownerIndex; - OwnerChanged(_from, _to); - } - - function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_owner)) return; - - clearPending(); - if (m_numOwners >= c_maxOwners) - reorganizeOwners(); - if (m_numOwners >= c_maxOwners) - return; - m_numOwners++; - m_owners[m_numOwners] = uint(_owner); - m_ownerIndex[uint(_owner)] = m_numOwners; - OwnerAdded(_owner); - } - - function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - uint ownerIndex = m_ownerIndex[uint(_owner)]; - if (ownerIndex == 0) return; - if (m_required > m_numOwners - 1) return; - - m_owners[ownerIndex] = 0; - m_ownerIndex[uint(_owner)] = 0; - clearPending(); - reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot - OwnerRemoved(_owner); - } - - function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { - if (_newRequired > m_numOwners) return; - m_required = _newRequired; - clearPending(); - RequirementChanged(_newRequired); - } - - // Gets an owner by 0-indexed position (using numOwners as the count) - function getOwner(uint ownerIndex) external constant returns (address) { - return address(m_owners[ownerIndex + 1]); - } +contract multiowned { - function isOwner(address _addr) returns (bool) { - return m_ownerIndex[uint(_addr)] > 0; + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // EVENTS + + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function multiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; } - - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - var pending = m_pending[_operation]; - uint ownerIndex = m_ownerIndex[uint(_owner)]; - - // make sure they're an owner - if (ownerIndex == 0) return false; - - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - return !(pending.ownersDone & ownerIndexBit == 0); + m_required = _required; + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); } - - // INTERNAL METHODS - - function confirmAndCheck(bytes32 _operation) internal returns (bool) { - // determine what index the present sender is: - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - - var pending = m_pending[_operation]; - // if we're not yet working on this operation, switch over and reset the confirmation status. - if (pending.yetNeeded == 0) { - // reset count of confirmations needed. - pending.yetNeeded = m_required; - // reset which owners have confirmed (none) - set our bitmap to 0. - pending.ownersDone = 0; - pending.index = m_pendingIndex.length++; - m_pendingIndex[pending.index] = _operation; - } - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - // make sure we (the message sender) haven't confirmed this operation previously. - if (pending.ownersDone & ownerIndexBit == 0) { - Confirmation(msg.sender, _operation); - // ok - check if count is enough to go ahead. - if (pending.yetNeeded <= 1) { - // enough confirmations: reset and run interior. - delete m_pendingIndex[m_pending[_operation].index]; - delete m_pending[_operation]; - return true; - } - else - { - // not enough: record that this owner in particular confirmed. - pending.yetNeeded--; - pending.ownersDone |= ownerIndexBit; - } - } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; } - - function reorganizeOwners() private { - uint free = 1; - while (free < m_numOwners) - { - while (free < m_numOwners && m_owners[free] != 0) free++; - while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; - if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) - { - m_owners[free] = m_owners[m_numOwners]; - m_ownerIndex[m_owners[free]] = free; - m_owners[m_numOwners] = 0; - } - } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } } - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - if (m_pendingIndex[i] != 0) - delete m_pending[m_pendingIndex[i]]; - delete m_pendingIndex; + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } } - - // FIELDS - - // the number of owners that must confirm the same operation before it is run. - uint public m_required; - // pointer used to find a free slot in m_owners - uint public m_numOwners; - - // list of owners - uint[256] m_owners; - uint constant c_maxOwners = 250; - // index on the list of owners to allow reverse lookup - mapping(uint => uint) m_ownerIndex; - // the ongoing operations. - mapping(bytes32 => PendingState) m_pending; - bytes32[] m_pendingIndex; + } + + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + delete m_pendingIndex; + } + + // FIELDS + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + // list of owners + uint[256] m_owners; + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; } // inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) @@ -230,79 +231,70 @@ contract multiowned { // uses is specified in the modifier. contract daylimit is multiowned { - // MODIFIERS - - // simple modifier for daily limit. - modifier limitedDaily(uint _value) { - if (underLimit(_value)) - _; - } - - // METHODS - - // constructor - stores initial daily limit and records the present day's index. - function daylimit(uint _limit) { - m_dailyLimit = _limit; - m_lastDay = today(); - } - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { - m_dailyLimit = _newLimit; + // METHODS + + // constructor - stores initial daily limit and records the present day's index. + function daylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // INTERNAL METHODS + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); } - // resets the amount already spent today. needs many of the owners to confirm. - function resetSpentToday() onlymanyowners(sha3(msg.data)) external { - m_spentToday = 0; + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; } + return false; + } + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } - // INTERNAL METHODS - - // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and - // returns true. otherwise just returns false. - function underLimit(uint _value) internal onlyowner returns (bool) { - // reset the spend limit if we're on a different day to last time. - if (today() > m_lastDay) { - m_spentToday = 0; - m_lastDay = today(); - } - // check to see if there's enough left - if so, subtract and return true. - // overflow protection // dailyLimit check - if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { - m_spentToday += _value; - return true; - } - return false; - } - // determines today's index. - function today() private constant returns (uint) { return now / 1 days; } + // FIELDS - // FIELDS - - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; } // interface contract for multisig proxy contracts; see below for docs. contract multisig { - // EVENTS + // EVENTS - // logged events: - // Funds has arrived into the wallet (record how much). - event Deposit(address _from, uint value); - // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). - event SingleTransact(address owner, uint value, address to, bytes data); - // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). - event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); - // Confirmation still needed for a transaction. - event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); + // logged events: + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); - // FUNCTIONS + // FUNCTIONS - // TODO: document - function changeOwner(address _from, address _to) external; - function execute(address _to, uint _value, bytes _data) external returns (bytes32); - function confirm(bytes32 _h) returns (bool); + // TODO: document + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) external returns (bool o_success); } // usage: @@ -310,79 +302,102 @@ contract multisig { // Wallet(w).from(anotherOwner).confirm(h); contract Wallet is multisig, multiowned, daylimit { - // TYPES - - // Transaction structure to remember details of transaction lest it need be saved for a later call. - struct Transaction { - address to; - uint value; - bytes data; + // TYPES + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // METHODS + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function Wallet(address[] _owners, uint _required, uint _daylimit) + multiowned(_owners, _required) daylimit(_daylimit) { + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } } + } - // METHODS - - // constructor - just pass on the owner array to the multiowned and - // the limit to daylimit - function Wallet(address[] _owners, uint _required, uint _daylimit) - multiowned(_owners, _required) daylimit(_daylimit) { + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(invalidJumpLabel, iszero(extcodesize(o_addr))) } - - // kills the contract sending everything to `_to`. - function kill(address _to) onlymanyowners(sha3(msg.data)) external { - suicide(_to); + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; } + } - // gets called when no other function matches - function() payable { - // just being sent some cash? - if (msg.value > 0) - Deposit(msg.sender, msg.value); - } + // INTERNAL METHODS - // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. - // If not, goes into multisig process. We provide a hash on return to allow the sender to provide - // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value - // and _data arguments). They still get the option of using them if they want, anyways. - function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { - // first, take the opportunity to check that we're under the daily limit. - if (underLimit(_value)) { - SingleTransact(msg.sender, _value, _to, _data); - // yes - just execute the call. - _to.call.value(_value)(_data); - return 0; - } - // determine our operation hash. - _r = sha3(msg.data, block.number); - if (!confirm(_r) && m_txs[_r].to == 0) { - m_txs[_r].to = _to; - m_txs[_r].value = _value; - m_txs[_r].data = _data; - ConfirmationNeeded(_r, msg.sender, _value, _to, _data); - } - } - - // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order - // to determine the body of the transaction from the hash provided. - function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { - if (m_txs[_h].to != 0) { - m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); - MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); - delete m_txs[_h]; - return true; - } - } - - // INTERNAL METHODS - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - delete m_txs[m_pendingIndex[i]]; - super.clearPending(); - } + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + super.clearPending(); + } - // FIELDS + // FIELDS - // pending transactions we have at present. - mapping (bytes32 => Transaction) m_txs; + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; } diff --git a/js/src/dapps/registry/Events/events.js b/js/src/dapps/registry/Events/events.js index ae5e4517605bb67e833f04462d00fd70363743fa..05b138bc5f0a062752eeaf95e606878297687bb0 100644 --- a/js/src/dapps/registry/Events/events.js +++ b/js/src/dapps/registry/Events/events.js @@ -53,7 +53,13 @@ const renderEvent = (classNames, verb) => (e) => { return ( -
+
{ verb } @@ -80,17 +86,23 @@ const renderDataChanged = (e) => { return ( -
+
updated - { 'key ' } + key  { new Buffer(e.parameters.plainKey.value).toString('utf8') } - { 'of ' } +  of  diff --git a/js/src/dapps/registry/actions.js b/js/src/dapps/registry/actions.js index d56c76f4b8489c2178132b46279d946313fca273..00d851a37fc44bfd7ab78e396139e29809ee9e78 100644 --- a/js/src/dapps/registry/actions.js +++ b/js/src/dapps/registry/actions.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { registry as registryAbi } from '~/contracts/abi'; +import { registry as registryAbi, registry2 as registryAbi2 } from '~/contracts/abi'; import { api } from './parity.js'; import * as addresses from './addresses/actions.js'; @@ -27,15 +27,17 @@ import * as reverse from './Reverse/actions.js'; export { addresses, accounts, lookup, events, names, records, reverse }; -export const setIsTestnet = (isTestnet) => ({ type: 'set isTestnet', isTestnet }); +const REGISTRY_V1_HASHES = [ + '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten + '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead +]; + +export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion }); export const fetchIsTestnet = () => (dispatch) => api.net.version() .then((netVersion) => { - dispatch(setIsTestnet( - netVersion === '2' || // morden - netVersion === '3' // ropsten - )); + dispatch(setNetVersion(netVersion)); }) .catch((err) => { console.error('could not check if testnet'); @@ -47,13 +49,28 @@ export const fetchIsTestnet = () => (dispatch) => export const setContract = (contract) => ({ type: 'set contract', contract }); export const fetchContract = () => (dispatch) => - api.parity.registryAddress() + api.parity + .registryAddress() .then((address) => { - const contract = api.newContract(registryAbi, address); - - dispatch(setContract(contract)); - dispatch(fetchFee()); - dispatch(fetchOwner()); + return api.eth + .getCode(address) + .then((code) => { + const codeHash = api.util.sha3(code); + const isVersion1 = REGISTRY_V1_HASHES.includes(codeHash); + + console.log(`registry at ${address}, code ${codeHash}, version ${isVersion1 ? 1 : 2}`); + + const contract = api.newContract( + isVersion1 + ? registryAbi + : registryAbi2, + address + ); + + dispatch(setContract(contract)); + dispatch(fetchFee()); + dispatch(fetchOwner()); + }); }) .catch((err) => { console.error('could not fetch contract'); diff --git a/js/src/dapps/registry/reducers.js b/js/src/dapps/registry/reducers.js index cf5c82f024e52619cd54196e757fae5934f7852a..6d0816273fd98406048f1bce3b68f80aafdde897 100644 --- a/js/src/dapps/registry/reducers.js +++ b/js/src/dapps/registry/reducers.js @@ -22,8 +22,8 @@ import namesReducer from './Names/reducers.js'; import recordsReducer from './Records/reducers.js'; import reverseReducer from './Reverse/reducers.js'; -const isTestnetReducer = (state = null, action) => - action.type === 'set isTestnet' ? action.isTestnet : state; +const netVersionReducer = (state = null, action) => + action.type === 'set netVersion' ? action.netVersion : state; const contractReducer = (state = null, action) => action.type === 'set contract' ? action.contract : state; @@ -35,7 +35,7 @@ const ownerReducer = (state = null, action) => action.type === 'set owner' ? action.owner : state; const initialState = { - isTestnet: isTestnetReducer(undefined, { type: '' }), + netVersion: netVersionReducer(undefined, { type: '' }), accounts: accountsReducer(undefined, { type: '' }), contacts: contactsReducer(undefined, { type: '' }), contract: contractReducer(undefined, { type: '' }), @@ -49,7 +49,7 @@ const initialState = { }; export default (state = initialState, action) => ({ - isTestnet: isTestnetReducer(state.isTestnet, action), + netVersion: netVersionReducer(state.netVersion, action), accounts: accountsReducer(state.accounts, action), contacts: contactsReducer(state.contacts, action), contract: contractReducer(state.contract, action), diff --git a/js/src/dapps/registry/ui/address.js b/js/src/dapps/registry/ui/address.js index ab050053219467d761c84c03fee0f5219128deec..a01811fc433d42a7ebe0b40d05b84a3e35494b2d 100644 --- a/js/src/dapps/registry/ui/address.js +++ b/js/src/dapps/registry/ui/address.js @@ -28,7 +28,7 @@ class Address extends Component { static propTypes = { address: PropTypes.string.isRequired, account: nullableProptype(PropTypes.object.isRequired), - isTestnet: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, key: PropTypes.string, shortenHash: PropTypes.bool }; @@ -56,7 +56,7 @@ class Address extends Component { } renderCaption () { - const { address, account, isTestnet, shortenHash } = this.props; + const { address, account, netVersion, shortenHash } = this.props; if (account) { const { name } = account; @@ -64,7 +64,7 @@ class Address extends Component { return ( { - const { isTestnet } = state; + const { netVersion } = state; const { address = '' } = props; const account = allAccounts[address] || null; return { account, - isTestnet + netVersion }; }; } diff --git a/js/src/dapps/registry/ui/hash.js b/js/src/dapps/registry/ui/hash.js index 88099fcf0b4b58f0bb2c1be6b4b1f7d9f6c525bc..fe404f5b2d78af6943d08590d3f16a28f0971832 100644 --- a/js/src/dapps/registry/ui/hash.js +++ b/js/src/dapps/registry/ui/hash.js @@ -26,7 +26,7 @@ const leading0x = /^0x/; class Hash extends Component { static propTypes = { hash: PropTypes.string.isRequired, - isTestnet: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, linked: PropTypes.bool } @@ -35,7 +35,7 @@ class Hash extends Component { } render () { - const { hash, isTestnet, linked } = this.props; + const { hash, netVersion, linked } = this.props; let shortened = hash.toLowerCase().replace(leading0x, ''); @@ -47,7 +47,7 @@ class Hash extends Component { return ( { shortened } @@ -61,7 +61,7 @@ class Hash extends Component { export default connect( (state) => ({ // mapStateToProps - isTestnet: state.isTestnet + netVersion: state.netVersion }), null // mapDispatchToProps )(Hash); diff --git a/js/src/dapps/registry/util/etherscan-url.js b/js/src/dapps/registry/util/etherscan-url.js index 88094a9119b94702e882be8fda8a9fd275b18b18..bb4e2fe9878e32c672716d29c6bbeac9d1523c5c 100644 --- a/js/src/dapps/registry/util/etherscan-url.js +++ b/js/src/dapps/registry/util/etherscan-url.js @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { url as externalUrl } from '~/3rdparty/etherscan/links'; + const leading0x = /^0x/; -const etherscanUrl = (hash, isTestnet) => { +const etherscanUrl = (hash, isTestnet, netVersion) => { hash = hash.toLowerCase().replace(leading0x, ''); const type = hash.length === 40 ? 'address' : 'tx'; - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/${type}/0x${hash}`; + return `https://${externalUrl(isTestnet, netVersion)}/${type}/0x${hash}`; }; export default etherscanUrl; diff --git a/js/src/dapps/static/console.js b/js/src/dapps/static/console.js index d228a91670b3bc7fdd42baa21a7992184bc613c4..b6faee7f049627b971ac9b3bc3c659b27a14f2d0 100755 --- a/js/src/dapps/static/console.js +++ b/js/src/dapps/static/console.js @@ -14,53 +14,62 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/* eslint-disable */ -// TODO: Fix linting issues - -if (typeof(window.parent.secureApi) == 'object') - window.api = window.parent.secureApi; -else if (typeof(window.parity) == 'object') +/* global web3 */ + +if (typeof (window.parent.secureApi) === 'object') { + window.api = window.parent.secureApi; + + if (typeof (window.Web3) === 'function') { + Promise.all([ + window.api.parity.dappsInterface(), + window.api.parity.dappsPort() + ]).then(res => { + window.web3 = new window.Web3(new window.Web3.providers.HttpProvider(`http://${res.join(':')}/rpc/`)); + }); + } +} else if (typeof (window.parity) === 'object') { window.api = window.parity.api; +} -if (typeof(window.api) === 'object') - window.api.subscribe('eth_blockNumber', function (error, blockNumber) { - if (error) { - console.log('error', error); - return; - } - refreshWatches(); - }); - -function escapeHtml(str) { - var div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML; +if (typeof (window.api) === 'object') { + window.api.subscribe('eth_blockNumber', function (error, blockNumber) { + if (error) { + console.log('error', error); + return; + } + refreshWatches(); + }); } -function getAllPropertyNames(obj) { - var props = {}; - do { - Object.getOwnPropertyNames(obj).forEach(n => props[n] = true); - } while (obj = Object.getPrototypeOf(obj)); - return Object.keys(props); +function escapeHtml (str) { + let div = document.createElement('div'); + + div.appendChild(document.createTextNode(str)); + return div.innerHTML; } -function htmlToElement(html) { - var template = document.createElement('template'); - template.innerHTML = html; - return template.content.firstChild; +function getAllPropertyNames (obj) { + let props = {}; + + do { + Object.getOwnPropertyNames(obj).forEach(n => { + props[n] = true; + }); + obj = Object.getPrototypeOf(obj); + } while (obj); + + return Object.keys(props); } -function evaluate(x) { +function evaluate (x) { try { - return eval(x); - } - catch (err) { - return eval('(()=>{var x = ' + x + "; return x;})()") + return eval(x); // eslint-disable-line no-eval + } catch (err) { + return eval('(()=>{let x = ' + x + '; return x;})()'); // eslint-disable-line no-eval } } -function safeAccess(obj, prop) { +function safeAccess (obj, prop) { try { return obj[prop]; } catch (e) { @@ -68,23 +77,26 @@ function safeAccess(obj, prop) { } } -function displayReady(x, visited = []) { +function displayReady (x, visited = []) { visited.push(x); - var toString = Object.prototype.toString; - if (x === undefined) - return 'undefined'; - if (x === null) + let toString = Object.prototype.toString; + + if (x === undefined) { return 'undefined'; } + if (x === null) { return 'null'; - if (typeof(x) == "string") - return `"${escapeHtml(x)}"`; - if (toString.call(x) === '[object Array]') + } + if (typeof (x) === 'string') { + return `"${escapeHtml(x)}"`; + } + if (toString.call(x) === '[object Array]') { return `[${x.map(el => displayReady(el, visited)).join(', ')}]`; - if (typeof(x) == "function") - return `function () { /* ... */ }`; - if (typeof(x) == "object") { - var constructor = x.constructor || Object; - var objToString = typeof(x.toString) == "function" ? x.toString : toString; - if (objToString.call(x).indexOf('[object ') != 0) { + } + if (typeof (x) === 'function') { return `function () { /* ... */ }`; } + if (typeof (x) === 'object') { + let constructor = x.constructor || Object; + let objToString = typeof (x.toString) === 'function' ? x.toString : toString; + + if (objToString.call(x).indexOf('[object ') !== 0) { return `${escapeHtml(objToString.call(x))}`; } @@ -95,144 +107,212 @@ function displayReady(x, visited = []) { ${escapeHtml(f)}: ${displayReady(safeAccess(x, f), visited)} `).join(', ')} } - `; + + `; } - return `${escapeHtml(JSON.stringify(x))}`; + return `${escapeHtml(JSON.stringify(x))}`; } -if (!localStorage.history) - localStorage.history = "[]"; +if (!localStorage.history) { + localStorage.history = '[]'; +} window.historyData = JSON.parse(localStorage.history); window.historyIndex = window.historyData.length; -if (!localStorage.watches) - localStorage.watches = "[]"; +if (!localStorage.watches) { + localStorage.watches = '[]'; +} window.watches = {}; -function watch(name, f) { - let status = document.getElementById("status"); +function watch (name, f) { + let status = document.getElementById('status'); let cleanName = name.replace(/[^a-zA-Z0-9]/, ''); - status.innerHTML += `
${escapeHtml(name)}
`; + + status.innerHTML += ` +
+ ${escapeHtml(name)} + +
+ `; window.watches[name] = f; } -var savedWatches = JSON.parse(localStorage.watches); +let savedWatches = JSON.parse(localStorage.watches); + savedWatches.forEach(w => watch(w[1], () => evaluate(w[0]))); -if (typeof(window.web3) == 'object' && window.watches.latest == undefined) +if (typeof (window.web3) === 'object' && window.watches.latest === undefined) { watch('latest', () => window.web3.eth.blockNumber); +} - -function refreshWatches() { - for (n in window.watches) { +function refreshWatches () { + for (let n in window.watches) { let r = window.watches[n](); let cn = n.replace(/[^a-zA-Z0-9]/, ''); let e = document.getElementById(`res_${cn}`); - if (typeof(r) == 'object' && r.constructor && r.constructor.name == "Promise") - r.then(r => e.innerHTML = displayReady(r)); - else + + if (typeof (r) === 'object' && r.then && r.then.call) { + r.then(r => { + e.innerHTML = displayReady(r); + }); + } else { e.innerHTML = displayReady(r); + } } } -function removeWatch(name) { +function removeWatch (name) { let e = document.getElementById(`watch_${name}`); + e.parentNode.removeChild(e); delete window.watches[name]; } -function newLog(level, text) { +function newLog (level, text) { let icon = { - debug: " ", - log: " ", - warn: "âš ", - error: "✖", - info: "ℹ" + debug: ' ', + log: ' ', + warn: 'âš ', + error: '✖', + info: 'ℹ' }; - pushLine('
' + icon[level] + '' + escapeHtml(text) + '
'); + + pushLine([ + '
', + icon[level], + '', + escapeHtml(text), + '
' + ].join('')); } -function exec() { - let command = document.getElementById("command"); +function exec () { + let command = document.getElementById('command'); let c = command.value; - if (c != '') { - command.value = ""; + if (c !== '') { + command.value = ''; window.historyData.push(c); - while (window.historyData.length > 1000) - window.historyData.shift; + while (window.historyData.length > 1000) { + window.historyData.shift(); + } + localStorage.history = JSON.stringify(window.historyData); window.historyIndex = window.historyData.length; - var html = ''; - if (c.indexOf("//") == 0) { + if (c.indexOf('//') === 0) { let n = c.substr(2); - savedWatches = savedWatches.filter(x => x[1] != n); + + savedWatches = savedWatches.filter(x => x[1] !== n); localStorage.watches = JSON.stringify(savedWatches); removeWatch(n); - } - else if (c.indexOf("//") != -1) { - x = c.split("//"); + } else if (c.indexOf('//') !== -1) { + let x = c.split('//'); let e = x[0]; + savedWatches.push(x); localStorage.watches = JSON.stringify(savedWatches); watch(x[1], () => evaluate(e)); - pushLine('
>' + escapeHtml(c) + '
'); - pushLine('
✓Watch added
'); - } - else { - pushLine('
>' + escapeHtml(c) + '
'); + + pushLine([ + '
>', + escapeHtml(c), + '
' + ].join('')); + + pushLine([ + '
✓', + 'Watch added', + '
' + ].join('')); + } else { + pushLine([ + '
>', + escapeHtml(c), + '
' + ].join('')); + let res; + try { res = evaluate(c); - if (typeof(res) == 'object' && res !== null && res.constructor && res.constructor.name == "Promise") { + if (typeof (res) === 'object' && res !== null && typeof res.then === 'function') { let id = window.historyData.length; - pushLine('
<...
'); - res.then(r => document.getElementById('pending' + id).innerHTML = displayReady(r)); + + pushLine([ + '
<...
' + ].join('')); + + res.then(r => { + document.getElementById('pending' + id).innerHTML = displayReady(r); + }); } else { - pushLine('
<' + displayReady(res) + '
'); + pushLine([ + '
<', + displayReady(res), + '
' + ].join('')); } - } - catch (err) { - pushLine('
✖Unhandled exception: ' + escapeHtml(err.message) + '
'); + } catch (err) { + pushLine([ + '
✖Unhandled exception: ', + escapeHtml(err.message), + '
' + ]); } } } + refreshWatches(); } -function pushLine(l) { - document.getElementById("history").innerHTML += l - var h = document.getElementById("history-wrap"); +function pushLine (l) { + document.getElementById('history').innerHTML += l; + let h = document.getElementById('history-wrap'); + h.scrollTop = h.scrollHeight; } -var autocompletes = []; -var currentAuto = null; -var currentPots = []; -var currentStem = null; +let autocompletes = []; +let currentAuto = null; +let currentPots = []; +let currentStem = null; -function updateAutocomplete() { - let v = document.getElementById("command").value; - if (v.length == 0) { +function updateAutocomplete () { + let v = document.getElementById('command').value; + + if (!v.length) { cancelAutocomplete(); return; } + let t = v.split('.'); let last = t.pop(); let tj = t.join('.'); let ex = t.length > 0 ? tj : 'window'; - if (currentStem != tj) { - autocompletes = eval('getAllPropertyNames('+ex+')'); + + if (currentStem !== tj) { + autocompletes = getAllPropertyNames(evaluate(ex)); currentStem = tj; } - let dl = document.getElementById("autocomplete"); + + let dl = document.getElementById('autocomplete'); + currentPots = autocompletes.filter(n => n.startsWith(last)); if (currentPots.length > 0) { - if (currentPots.indexOf(currentAuto) == -1) + if (currentPots.indexOf(currentAuto) === -1) { currentAuto = currentPots[0]; + } + dl.innerHTML = currentPots -// .map(n => `${tj != '' ? tj + '.' : ''}${n}`) - .map((n, i) => `
${escapeHtml(last)}${escapeHtml(n.substr(last.length))}
`) + .map((n, i) => ` +
+ ${escapeHtml(last)}${escapeHtml(n.substr(last.length))} +
` + ) .join(''); dl.hidden = false; } else { @@ -240,391 +320,411 @@ function updateAutocomplete() { } } -function enactAutocomplete() { +function enactAutocomplete () { if (currentAuto != null) { - document.getElementById("command").value = (currentStem != '' ? currentStem + '.' : '') + currentAuto; + document.getElementById('command').value = (currentStem !== '' ? currentStem + '.' : '') + currentAuto; cancelAutocomplete(); } } -function cancelAutocomplete() { - document.getElementById("autocomplete").hidden = true; +function cancelAutocomplete () { + document.getElementById('autocomplete').hidden = true; currentAuto = null; } -function scrollAutocomplete(positive) { +function scrollAutocomplete (positive) { if (currentAuto != null) { - var i = currentPots.indexOf(currentAuto); + let i = currentPots.indexOf(currentAuto); + document.getElementById('pot' + i).classList = ['ac-unselected']; - if (positive && i < currentPots.length - 1) + if (positive && i < currentPots.length - 1) { ++i; - else if (!positive && i > 0) + } else if (!positive && i > 0) { --i; + } currentAuto = currentPots[i]; let sel = document.getElementById('pot' + i); + sel.classList = ['ac-selected']; sel.scrollIntoViewIfNeeded(); } } -document.getElementById("command").addEventListener("paste", updateAutocomplete); -document.getElementById("command").addEventListener("input", updateAutocomplete); -document.getElementById("command").addEventListener("focusout", cancelAutocomplete); -document.getElementById("command").addEventListener("blur", cancelAutocomplete); +document.getElementById('command').addEventListener('paste', updateAutocomplete); +document.getElementById('command').addEventListener('input', updateAutocomplete); +document.getElementById('command').addEventListener('focusout', cancelAutocomplete); +document.getElementById('command').addEventListener('blur', cancelAutocomplete); + +document.getElementById('command').addEventListener('keydown', function (event) { + let el = document.getElementById('command'); -document.getElementById("command").addEventListener("keydown", function(event) { - let el = document.getElementById("command"); if (currentAuto != null) { - if (event.keyCode == 38 || event.keyCode == 40) { + if (event.keyCode === 38 || event.keyCode === 40) { event.preventDefault(); - scrollAutocomplete(event.keyCode == 40); - } - else if ((event.keyCode == 39 || event.keyCode == 9 || event.keyCode == 13) && el.selectionStart == el.value.length) { + scrollAutocomplete(event.keyCode === 40); + } else if ((event.keyCode === 39 || event.keyCode === 9 || event.keyCode === 13) && el.selectionStart === el.value.length) { event.preventDefault(); enactAutocomplete(); - } - else if (event.keyCode == 27) { + } else if (event.keyCode === 27) { event.preventDefault(); cancelAutocomplete(); } } else { - let command = document.getElementById("command"); - if (event.keyCode == 38 && window.historyIndex > 0) { + let command = document.getElementById('command'); + + if (event.keyCode === 38 && window.historyIndex > 0) { event.preventDefault(); window.historyIndex--; command.value = window.historyData[window.historyIndex]; } - if (event.keyCode == 40 && window.historyIndex < window.historyData.length) { + if (event.keyCode === 40 && window.historyIndex < window.historyData.length) { event.preventDefault(); window.historyIndex++; - command.value = window.historyIndex < window.historyData.length ? window.historyData[window.historyIndex] : ""; + command.value = window.historyIndex < window.historyData.length ? window.historyData[window.historyIndex] : ''; } } - if (event.keyCode >= 48 || event.keyCode == 8) { - let t = document.getElementById("command").value; + + if (event.keyCode >= 48 || event.keyCode === 8) { + let t = document.getElementById('command').value; + setTimeout(() => { - if (t != document.getElementById("command").value) + if (t !== document.getElementById('command').value) { updateAutocomplete(); + } }, 0); - } - else { + } else { setTimeout(() => { - if (el.selectionStart != el.value.length) + if (el.selectionStart !== el.value.length) { cancelAutocomplete(); + } }, 0); } }); -document.getElementById("command").addEventListener("keyup", function(event) { - if (event.keyCode == 13) { +document.getElementById('command').addEventListener('keyup', function (event) { + if (event.keyCode === 13) { event.preventDefault(); exec(); } }); -document.getElementById("command").focus(); -if (typeof(web3) === 'object') +document.getElementById('command').focus(); +if (typeof (web3) === 'object') { window.web3 = web3; +} refreshWatches(); -["debug", "error", "info", "log", "warn"].forEach(n => { +['debug', 'error', 'info', 'log', 'warn'].forEach(n => { let old = window.console[n].bind(window.console); - window.console[n] = x => { old(x); newLog(n, x); }; -}); - - - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///// Home comforts. - - -if (typeof(web3) === 'object') { -// Usage example: -// web3.eth.traceCall({ -// to: theChicken.address, -// data: theChicken.withdraw.getData(100000000000000000), -// gas: 100000 -// }, -// `["trace", "vmTrace", "stateDiff"] -// ) -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceCall', - call: 'trace_call', - params: 2, - inputFormatter: [web3._extend.formatters.inputCallFormatter, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceSendRawTransaction', - call: 'trace_rawTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceReplayTransaction', - call: 'trace_replayTransaction', - params: 2, - inputFormatter: [null, null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setMode', - call: 'ethcore_setMode', - params: 1, - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'mode', - call: 'ethcore_mode', - params: 0, - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'traceTransaction', - call: 'trace_Transaction', - params: 1, - inputFormatter: [null] - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'gasPriceStatistics', - call: 'ethcore_gasPriceStatistics', - params: 0, - outputFormatter: function(a) { return a.map(web3.toBigNumber); } - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'registryAddress', - call: 'ethcore_registryAddress', - params: 0 - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'accountsInfo', - call: 'personal_accountsInfo', - outputFormatter: function(m) { Object.keys(m).forEach(k => { - m[k].meta = JSON.parse(m[k].meta); - m[k].meta.name = m[k].name; - m[k].meta.uuid = m[k].uuid; - m[k] = m[k].meta; - }); return m; }, - params: 0 - }) - ] -}); - -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountName', - call: 'personal_setAccountName', - params: 2, - }) - ] -}); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'setAccountMeta', - call: 'personal_setAccountMeta', - params: 2, - inputFormatter: [a => a, JSON.stringify] - }) - ] + window.console[n] = x => { + old(x); + newLog(n, x); + }; }); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postTransaction', - call: 'eth_postTransaction', - params: 1, - inputFormatter: [web3._extend.formatters.inputCallFormatter] - }) - ] -}); +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// /// Home comforts. + +if (typeof (web3) === 'object') { + // Usage example: + // web3.eth.traceCall({ + // to: theChicken.address, + // data: theChicken.withdraw.getData(100000000000000000), + // gas: 100000 + // }, + // `["trace", "vmTrace", "stateDiff"] + // ) + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceCall', + call: 'trace_call', + params: 2, + inputFormatter: [web3._extend.formatters.inputCallFormatter, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceSendRawTransaction', + call: 'trace_rawTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceReplayTransaction', + call: 'trace_replayTransaction', + params: 2, + inputFormatter: [null, null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setMode', + call: 'parity_setMode', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'mode', + call: 'parity_mode', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'traceTransaction', + call: 'trace_Transaction', + params: 1, + inputFormatter: [null] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'gasPriceStatistics', + call: 'parity_gasPriceStatistics', + params: 0, + outputFormatter: function (a) { return a.map(web3.toBigNumber); } + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'registryAddress', + call: 'parity_registryAddress', + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'accountsInfo', + call: 'personal_accountsInfo', + outputFormatter: function (m) { + Object.keys(m).forEach(k => { + m[k].meta = JSON.parse(m[k].meta); + m[k].meta.name = m[k].name; + m[k].meta.uuid = m[k].uuid; + m[k] = m[k].meta; + }); return m; + }, + params: 0 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountName', + call: 'personal_setAccountName', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'setAccountMeta', + call: 'personal_setAccountMeta', + params: 2, + inputFormatter: [a => a, JSON.stringify] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postTransaction', + call: 'eth_postTransaction', + params: 1, + inputFormatter: [web3._extend.formatters.inputCallFormatter] + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'postSign', + call: 'eth_postSign', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'encryptMessage', + call: 'parity_encryptMessage', + params: 2 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'checkRequest', + call: 'eth_checkRequest', + params: 1 + }) + ] + }); + + web3._extend({ + property: 'eth', + methods: [ + new web3._extend.Method({ + name: 'listAccounts', + call: 'parity_listAccounts', + params: 0 + }) + ] + }); + + { + let postTransaction = web3.eth.postTransaction.bind(web3.eth); + let sendTransaction = web3.eth.sendTransaction.bind(web3.eth); + + web3.eth.sendTransaction = function (options, f) { + // No callback - do sync API. + if (typeof f !== 'function') { + return sendTransaction(options); + } + // Callback - use async API. + let id = postTransaction(options); + + console.log('Posted trasaction id=' + id); + let timerId = window.setInterval(check, 500); + + function check () { + try { + let r = web3.eth.checkRequest(id); + + if (typeof r === 'string') { + clearInterval(timerId); + if (r === '0x0000000000000000000000000000000000000000000000000000000000000000') { + f('Rejected', r); + } else { + f(null, r); + } + } else if (r !== null) { + console.log('checkRequest returned: ' + r); + } + } catch (e) { + clearInterval(timerId); + f('Rejected', null); + } + } + }; + } -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'postSign', - call: 'eth_postSign', - params: 1 - }) - ] -}); + web3.eth.installInterceptor = function (interceptor) { + let oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'encryptMessage', - call: 'ethcore_encryptMessage', - params: 2 - }) - ] -}); + web3.eth.sendTransaction = function (options, f) { + if (!interceptor(options)) { + return '0x0000000000000000000000000000000000000000000000000000000000000000'; + } -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'checkRequest', - call: 'eth_checkRequest', - params: 1 - }) - ] -}); + return oldSendTransaction(options, f); + }; + }; -web3._extend({ - property: 'eth', - methods: [ - new web3._extend.Method({ - name: 'listAccounts', - call: 'ethcore_listAccounts', - params: 0 - }) - ] -}); + web3.eth.reporter = function (e, r) { + if (e) { + console.log('Error confirming transaction: ' + e); + } else { + let addr = r; + let confirmed = false; + let timerId = window.setInterval(function check () { + let receipt = web3.eth.getTransactionReceipt(addr); + + if (receipt != null) { + if (!confirmed) { + console.log('Transaction confirmed (' + r + '); used ' + receipt.gasUsed + ' gas; left ' + receipt.logs.length + ' logs; mining...'); + confirmed = true; + } + if (typeof receipt.blockHash === 'string') { + clearInterval(timerId); + console.log('Mined into block ' + receipt.blockNumber); + } + } + }, 500); + } + }; -{ - var postTransaction = web3.eth.postTransaction.bind(web3.eth); - var sendTransaction = web3.eth.sendTransaction.bind(web3.eth); - web3.eth.sendTransaction = function(options, f) { - // No callback - do sync API. - if (typeof f != "function") - return sendTransaction(options); - // Callback - use async API. - var id = postTransaction(options); - console.log("Posted trasaction id=" + id); - var timerId = window.setInterval(check, 500); - function check() { - try { - let r = web3.eth.checkRequest(id); - if (typeof r == 'string') { - clearInterval(timerId); - if (r == "0x0000000000000000000000000000000000000000000000000000000000000000") - f("Rejected", r); - else - f(null, r); - } else if (r !== null) { - console.log("checkRequest returned: " + r); - } - } - catch (e) { - clearInterval(timerId); - f("Rejected", null); - } - } - } -} + { + let oldSha3 = web3.sha3; -web3.eth.installInterceptor = function(interceptor) { - var oldSendTransaction = web3.eth.sendTransaction.bind(web3.eth); - web3.eth.sendTransaction = function(options, f) { - if (interceptor(options) == false) - return "0x0000000000000000000000000000000000000000000000000000000000000000"; - return oldSendTransaction(options, f); - }; -} + web3.sha3 = function (data, format) { + if (typeof format !== 'string' || (format !== 'hex' && format !== 'bin')) { + format = data.startsWith('0x') ? 'hex' : 'bin'; + } + return oldSha3(data, { encoding: format }); + }; + } -web3.eth.reporter = function(e, r) { - if (e) { - console.log("Error confirming transaction: " + e); - } else { - var addr = r; - var confirmed = false; - var timer_id = window.setInterval(check, 500); - function check() { - var receipt = web3.eth.getTransactionReceipt(addr); - if (receipt != null) { - if (!confirmed) { - console.log("Transaction confirmed (" + r + "); used " + receipt.gasUsed + " gas; left " + receipt.logs.length + " logs; mining..."); - confirmed = true; - } - if (typeof receipt.blockHash == 'string') { - clearInterval(timer_id); - console.log("Mined into block " + receipt.blockNumber); - } - } - } - } -} + { + let Registry = web3.eth.contract([{ 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }], 'name': 'confirmReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserve', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'set', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'drop', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getAddress', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_amount', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_to', 'type': 'address' }], 'name': 'transfer', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }], 'name': 'reserved', 'outputs': [{ 'name': 'reserved', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'string' }, { 'name': '_who', 'type': 'address' }], 'name': 'proposeReverse', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'getUint', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }], 'name': 'get', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '', 'type': 'address' }], 'name': 'reverse', 'outputs': [{ 'name': '', 'type': 'string' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'uint256' }], 'name': 'setUint', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'removeReverse', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_name', 'type': 'bytes32' }, { 'name': '_key', 'type': 'string' }, { 'name': '_value', 'type': 'address' }], 'name': 'setAddress', 'outputs': [{ 'name': 'success', 'type': 'bool' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'Drained', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'name': 'amount', 'type': 'uint256' }], 'name': 'FeeChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Reserved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'oldOwner', 'type': 'address' }, { 'indexed': true, 'name': 'newOwner', 'type': 'address' }], 'name': 'Transferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }], 'name': 'Dropped', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'bytes32' }, { 'indexed': true, 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'name': 'key', 'type': 'string' }, { 'indexed': false, 'name': 'plainKey', 'type': 'string' }], 'name': 'DataChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseProposed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseConfirmed', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'name', 'type': 'string' }, { 'indexed': true, 'name': 'reverse', 'type': 'address' }], 'name': 'ReverseRemoved', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); -{ - var oldSha3 = web3.sha3 - web3.sha3 = function(data, format) { - if (typeof format !== 'string' || (format != 'hex' && format != 'bin')) - format = data.startsWith('0x') ? 'hex' : 'bin'; - return oldSha3(data, {encoding: format}); - } -} + web3.eth.registry = Registry.at(web3.eth.registryAddress()); + web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); + web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); + web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); -{ - var Registry = web3.eth.contract([{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"key","type":"string"},{"indexed":false,"name":"plainKey","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]); - web3.eth.registry = Registry.at(web3.eth.registryAddress()); - web3.eth.registry.lookup = (name, field) => web3.eth.registry.get(web3.sha3(name), field); - web3.eth.registry.lookupAddress = (name, field) => web3.eth.registry.getAddress(web3.sha3(name), field); - web3.eth.registry.lookupUint = (name, field) => web3.eth.registry.getUint(web3.sha3(name), field); + let TokenReg = web3.eth.contract([{ 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'token', 'outputs': [{ 'name': 'addr', 'type': 'address' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_new', 'type': 'address' }], 'name': 'setOwner', 'outputs': [], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }], 'name': 'register', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_fee', 'type': 'uint256' }], 'name': 'setFee', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }], 'name': 'meta', 'outputs': [{ 'name': '', 'type': 'bytes32' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_addr', 'type': 'address' }, { 'name': '_tla', 'type': 'string' }, { 'name': '_base', 'type': 'uint256' }, { 'name': '_name', 'type': 'string' }, { 'name': '_owner', 'type': 'address' }], 'name': 'registerAs', 'outputs': [{ 'name': '', 'type': 'bool' }], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_tla', 'type': 'string' }], 'name': 'fromTLA', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'addr', 'type': 'address' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{ 'name': '', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [], 'name': 'drain', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'tokenCount', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }], 'name': 'unregister', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [{ 'name': '_addr', 'type': 'address' }], 'name': 'fromAddress', 'outputs': [{ 'name': 'id', 'type': 'uint256' }, { 'name': 'tla', 'type': 'string' }, { 'name': 'base', 'type': 'uint256' }, { 'name': 'name', 'type': 'string' }, { 'name': 'owner', 'type': 'address' }], 'type': 'function' }, { 'constant': false, 'inputs': [{ 'name': '_id', 'type': 'uint256' }, { 'name': '_key', 'type': 'bytes32' }, { 'name': '_value', 'type': 'bytes32' }], 'name': 'setMeta', 'outputs': [], 'type': 'function' }, { 'constant': true, 'inputs': [], 'name': 'fee', 'outputs': [{ 'name': '', 'type': 'uint256' }], 'type': 'function' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'name': 'addr', 'type': 'address' }, { 'indexed': false, 'name': 'name', 'type': 'string' }], 'name': 'Registered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'tla', 'type': 'string' }, { 'indexed': true, 'name': 'id', 'type': 'uint256' }], 'name': 'Unregistered', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'id', 'type': 'uint256' }, { 'indexed': true, 'name': 'key', 'type': 'bytes32' }, { 'indexed': false, 'name': 'value', 'type': 'bytes32' }], 'name': 'MetaChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'name': 'old', 'type': 'address' }, { 'indexed': true, 'name': 'current', 'type': 'address' }], 'name': 'NewOwner', 'type': 'event' }]); - var TokenReg = web3.eth.contract([{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"token","outputs":[{"name":"addr","type":"address"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"}],"name":"register","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_tla","type":"string"}],"name":"fromTLA","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"tokenCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"},{"indexed":false,"name":"name","type":"string"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]); - web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); -} + web3.eth.tokenReg = TokenReg.at(web3.eth.registry.lookupAddress('tokenreg', 'A')); + } } -/* eslint-enable */ diff --git a/js/src/dapps/tokendeploy/services.js b/js/src/dapps/tokendeploy/services.js index 6853c8ac4b302bd5508c7a98ffbcb15ccc6ba183..9ca4c4f5650b937c88f372a996234931dad88dc9 100644 --- a/js/src/dapps/tokendeploy/services.js +++ b/js/src/dapps/tokendeploy/services.js @@ -16,6 +16,7 @@ import BigNumber from 'bignumber.js'; +import { url as etherscanUrl } from '~/3rdparty/etherscan/links'; import * as abis from '~/contracts/abi'; import { api } from './parity'; @@ -28,7 +29,7 @@ const subscriptions = {}; let defaultSubscriptionId; let nextSubscriptionId = 1000; -let isTest = false; +let netVersion = '0'; export function subscribeEvents (addresses, callback) { const subscriptionId = nextSubscriptionId++; @@ -117,15 +118,16 @@ export function attachInstances () { return Promise .all([ api.parity.registryAddress(), - api.parity.netChain() + api.parity.netChain(), + api.partiy.netVersion() ]) - .then(([registryAddress, netChain]) => { + .then(([registryAddress, netChain, _netVersion]) => { const registry = api.newContract(abis.registry, registryAddress).instance; - isTest = ['morden', 'ropsten', 'testnet'].includes(netChain); + netVersion = _netVersion; console.log(`contract was found at registry=${registryAddress}`); - console.log(`running on ${netChain}, isTest=${isTest}`); + console.log(`running on ${netChain}, network ${netVersion}`); return Promise .all([ @@ -287,5 +289,5 @@ export function loadTokenBalance (tokenAddress, address) { } export function txLink (txHash) { - return `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${txHash}`; + return `https://${etherscanUrl(false, netVersion)}/tx/${txHash}`; } diff --git a/js/src/dapps/tokenreg/Chip/chip.js b/js/src/dapps/tokenreg/Chip/chip.js index 1fa16c77463496f683d3d1f3308205691b1aff77..7c19a671caefe83ed90cf744ede22a943b8a8037 100644 --- a/js/src/dapps/tokenreg/Chip/chip.js +++ b/js/src/dapps/tokenreg/Chip/chip.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import { Chip } from 'material-ui'; -import IdentityIcon from '../IdentityIcon' ; +import IdentityIcon from '../IdentityIcon'; import styles from './chip.css'; diff --git a/js/src/i18n/_default/account.js b/js/src/i18n/_default/account.js new file mode 100644 index 0000000000000000000000000000000000000000..ce5d277ddb5c8db858c9b2f71939eeed7b459d32 --- /dev/null +++ b/js/src/i18n/_default/account.js @@ -0,0 +1,35 @@ +// 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 . + +export default { + button: { + delete: `delete account`, + edit: `edit`, + password: `password`, + shapeshift: `shapeshift`, + transfer: `transfer`, + verify: `verify` + }, + header: { + outgoingTransactions: `{count} outgoing transactions`, + uuid: `uuid: {uuid}` + }, + title: `Account Management`, + transactions: { + poweredBy: `Transaction list powered by {etherscan}`, + title: `transactions` + } +}; diff --git a/js/src/i18n/_default/accounts.js b/js/src/i18n/_default/accounts.js new file mode 100644 index 0000000000000000000000000000000000000000..d22c5a504ec5072be1e6a549d5dd91fcd25d6fcb --- /dev/null +++ b/js/src/i18n/_default/accounts.js @@ -0,0 +1,31 @@ +// 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 . + +export default { + button: { + newAccount: `new account`, + newWallet: `new wallet`, + vaults: `vaults` + }, + summary: { + minedBlock: `Mined at block #{blockNumber}` + }, + title: `Accounts Overview`, + tooltip: { + actions: `actions relating to the current view are available on the toolbar for quick access, be it for performing actions or creating a new item`, + overview: `your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account` + } +}; diff --git a/js/src/i18n/_default/addAddress.js b/js/src/i18n/_default/addAddress.js new file mode 100644 index 0000000000000000000000000000000000000000..38fd7e3612e41def1807e68e51c4f2fccad590e5 --- /dev/null +++ b/js/src/i18n/_default/addAddress.js @@ -0,0 +1,37 @@ +// 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 . + +export default { + button: { + add: `Save Address`, + close: `Cancel` + }, + input: { + address: { + hint: `the network address for the entry`, + label: `network address` + }, + description: { + hint: `an expanded description for the entry`, + label: `(optional) address description` + }, + name: { + hint: `a descriptive name for the entry`, + label: `address name` + } + }, + label: `add saved address` +}; diff --git a/js/src/i18n/_default/addContract.js b/js/src/i18n/_default/addContract.js new file mode 100644 index 0000000000000000000000000000000000000000..edbecf1183969631d33364463ea8829ed9aa2e93 --- /dev/null +++ b/js/src/i18n/_default/addContract.js @@ -0,0 +1,60 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + abi: { + hint: `the abi for the contract`, + label: `contract abi` + }, + abiType: { + custom: { + description: `Contract created from custom ABI`, + label: `Custom Contract` + }, + multisigWallet: { + description: `Ethereum Multisig contract {link}`, + label: `Multisig Wallet`, + link: `see contract code` + }, + token: { + description: `A standard {erc20} token`, + erc20: `ERC 20`, + label: `Token` + } + }, + address: { + hint: `the network address for the contract`, + label: `network address` + }, + button: { + add: `Add Contract`, + cancel: `Cancel`, + next: `Next`, + prev: `Back` + }, + description: { + hint: `an expanded description for the entry`, + label: `(optional) contract description` + }, + name: { + hint: `a descriptive name for the contract`, + label: `contract name` + }, + title: { + details: `enter contract details`, + type: `choose a contract type` + } +}; diff --git a/js/src/i18n/_default/addressSelect.js b/js/src/i18n/_default/addressSelect.js new file mode 100644 index 0000000000000000000000000000000000000000..108ac80f56c3785a3027efaae88bc07b42af1517 --- /dev/null +++ b/js/src/i18n/_default/addressSelect.js @@ -0,0 +1,26 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + fromEmail: `Verified using email {email}`, + fromRegistry: `{name} (from registry)`, + labels: { + accounts: `accounts`, + contacts: `contacts`, + contracts: `contracts` + }, + noAccount: `No account matches this query...` +}; diff --git a/js/src/i18n/_default/application.js b/js/src/i18n/_default/application.js new file mode 100644 index 0000000000000000000000000000000000000000..1edafff6df079017e2e98713a1852977fd3333a0 --- /dev/null +++ b/js/src/i18n/_default/application.js @@ -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 . + +export default { + status: { + consensus: { + capable: `Capable`, + capableUntil: `Capable until #{blockNumber}`, + incapableSince: `Incapable since #{blockNumber}`, + unknown: `Unknown capability` + }, + upgrade: `Upgrade` + } +}; diff --git a/js/src/i18n/_default/connection.js b/js/src/i18n/_default/connection.js new file mode 100644 index 0000000000000000000000000000000000000000..e51943178e738e974ab10e5533b3a97c7f92a251 --- /dev/null +++ b/js/src/i18n/_default/connection.js @@ -0,0 +1,26 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + connectingAPI: `Connecting to the Parity Secure API.`, + connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`, + invalidToken: `invalid signer token`, + noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.`, + token: { + hint: `a generated token from Parity`, + label: `secure token` + } +}; diff --git a/js/src/i18n/_default/contract.js b/js/src/i18n/_default/contract.js new file mode 100644 index 0000000000000000000000000000000000000000..e6a2a110bf02c5b621034dba91324553b52dc64b --- /dev/null +++ b/js/src/i18n/_default/contract.js @@ -0,0 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + minedBlock: `Mined at block #{blockNumber}` +}; diff --git a/js/src/i18n/_default/createAccount.js b/js/src/i18n/_default/createAccount.js new file mode 100644 index 0000000000000000000000000000000000000000..485a877f8822a47739a4f1e0ad96f641d365dc5c --- /dev/null +++ b/js/src/i18n/_default/createAccount.js @@ -0,0 +1,156 @@ +// 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 . + +export default { + accountDetails: { + address: { + hint: `the network address for the account`, + label: `address` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + phrase: { + hint: `the account recovery phrase`, + label: `owner recovery phrase (keep private and secure, it allows full and unlimited access to the account)` + } + }, + accountDetailsGeth: { + imported: `You have imported {number} addresses from the Geth keystore:` + }, + button: { + back: `Back`, + cancel: `Cancel`, + close: `Close`, + create: `Create`, + import: `Import`, + next: `Next`, + print: `Print Phrase` + }, + creationType: { + fromGeth: { + label: `Import accounts from Geth keystore` + }, + fromJSON: { + label: `Import account from a backup JSON file` + }, + fromNew: { + label: `Create new account manually` + }, + fromPhrase: { + label: `Recover account from recovery phrase` + }, + fromPresale: { + label: `Import account from an Ethereum pre-sale wallet` + }, + fromRaw: { + label: `Import raw private key` + } + }, + newAccount: { + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + } + }, + newGeth: { + noKeys: `There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance` + }, + newImport: { + file: { + hint: `the wallet file for import`, + label: `wallet file` + }, + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `the password to unlock the wallet`, + label: `password` + } + }, + rawKey: { + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + private: { + hint: `the raw hex encoded private key`, + label: `private key` + } + }, + recoveryPhrase: { + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a descriptive name for the account`, + label: `account name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + phrase: { + hint: `the account recovery phrase`, + label: `account recovery phrase` + }, + windowsKey: { + label: `Key was created with Parity <1.4.5 on Windows` + } + }, + title: { + accountInfo: `account information`, + createAccount: `create account`, + createType: `creation type`, + importWallet: `import wallet` + } +}; diff --git a/js/src/i18n/_default/createWallet.js b/js/src/i18n/_default/createWallet.js new file mode 100644 index 0000000000000000000000000000000000000000..9c7d8df3e571a2a38c3e6e1c911cd6b535717a1a --- /dev/null +++ b/js/src/i18n/_default/createWallet.js @@ -0,0 +1,105 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + add: `Add`, + cancel: `Cancel`, + close: `Close`, + create: `Create`, + done: `Done`, + next: `Next`, + sending: `Sending...` + }, + deployment: { + message: `The deployment is currently in progress` + }, + details: { + address: { + hint: `the wallet contract address`, + label: `wallet address` + }, + dayLimitMulti: { + hint: `amount of ETH spendable without confirmations`, + label: `wallet day limit` + }, + description: { + hint: `the local description for this wallet`, + label: `wallet description (optional)` + }, + descriptionMulti: { + hint: `the local description for this wallet`, + label: `wallet description (optional)` + }, + name: { + hint: `the local name for this wallet`, + label: `wallet name` + }, + nameMulti: { + hint: `the local name for this wallet`, + label: `wallet name` + }, + ownerMulti: { + hint: `the owner account for this contract`, + label: `from account (contract owner)` + }, + ownersMulti: { + label: `other wallet owners` + }, + ownersMultiReq: { + hint: `number of required owners to accept a transaction`, + label: `required owners` + } + }, + info: { + added: `added`, + copyAddress: `copy address to clipboard`, + created: `{name} has been {deployedOrAdded} at`, + dayLimit: `The daily limit is set to {dayLimit} ETH.`, + deployed: `deployed`, + numOwners: `{numOwners} owners are required to confirm a transaction.`, + owners: `The following are wallet owners` + }, + rejected: { + message: `The deployment has been rejected`, + state: `The wallet will not be created. You can safely close this window.`, + title: `rejected` + }, + states: { + completed: `The contract deployment has been completed`, + preparing: `Preparing transaction for network transmission`, + validatingCode: `Validating the deployed contract code`, + waitingConfirm: `Waiting for confirmation of the transaction in the Parity Secure Signer`, + waitingReceipt: `Waiting for the contract deployment transaction receipt` + }, + steps: { + deployment: `wallet deployment`, + details: `wallet details`, + info: `wallet informaton`, + type: `wallet type` + }, + type: { + multisig: { + description: `Create/Deploy a {link} Wallet`, + label: `Multi-Sig wallet`, + link: `standard multi-signature` + }, + watch: { + description: `Add an existing wallet to your accounts`, + label: `Watch a wallet` + } + } +}; diff --git a/js/src/i18n/_default/dapp.js b/js/src/i18n/_default/dapp.js new file mode 100644 index 0000000000000000000000000000000000000000..29dd2fd16b743a5f8d443c3f7db0939bc6e79bde --- /dev/null +++ b/js/src/i18n/_default/dapp.js @@ -0,0 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + loading: `Loading`, + unavailable: `The dapp cannot be reached` +}; diff --git a/js/src/i18n/_default/dapps.js b/js/src/i18n/_default/dapps.js new file mode 100644 index 0000000000000000000000000000000000000000..d13caa46d5cef55dab09ebec872e387499290107 --- /dev/null +++ b/js/src/i18n/_default/dapps.js @@ -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 . + +export default { + add: { + builtin: { + desc: `Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.`, + label: `Applications bundled with Parity` + }, + label: `visible applications`, + local: { + desc: `All applications installed locally on the machine by the user for access by the Parity client.`, + label: `Applications locally available` + }, + network: { + desc: `These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.`, + label: `Applications on the global network` + } + }, + button: { + edit: `edit`, + permissions: `permissions` + }, + external: { + accept: `I understand that these applications are not affiliated with Parity`, + warning: `Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.` + }, + label: `Decentralized Applications`, + permissions: { + description: `{activeIcon} account is available to application, {defaultIcon} account is the default account`, + label: `visible dapp accounts` + } +}; diff --git a/js/src/i18n/_default/deleteAccount.js b/js/src/i18n/_default/deleteAccount.js new file mode 100644 index 0000000000000000000000000000000000000000..4818a785abb5af815a6ae1eddc6f6258bf20b0fb --- /dev/null +++ b/js/src/i18n/_default/deleteAccount.js @@ -0,0 +1,24 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + password: { + hint: `provide the account password to confirm the account deletion`, + label: `account password` + }, + question: `Are you sure you want to permanently delete the following account?`, + title: `confirm removal` +}; diff --git a/js/src/i18n/_default/deployContract.js b/js/src/i18n/_default/deployContract.js new file mode 100644 index 0000000000000000000000000000000000000000..fe7c9a69e550752657f592eb41be026c7bd0d60b --- /dev/null +++ b/js/src/i18n/_default/deployContract.js @@ -0,0 +1,81 @@ +// 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 . + +export default { + busy: { + title: `The deployment is currently in progress` + }, + button: { + cancel: `Cancel`, + close: `Close`, + create: `Create`, + done: `Done`, + next: `Next` + }, + completed: { + description: `Your contract has been deployed at` + }, + details: { + abi: { + hint: `the abi of the contract to deploy or solc combined-output`, + label: `abi / solc combined-output` + }, + address: { + hint: `the owner account for this contract`, + label: `from account (contract owner)` + }, + code: { + hint: `the compiled code of the contract to deploy`, + label: `code` + }, + contract: { + label: `select a contract` + }, + description: { + hint: `a description for the contract`, + label: `contract description (optional)` + }, + name: { + hint: `a name for the deployed contract`, + label: `contract name` + } + }, + owner: { + noneSelected: `a valid account as the contract owner needs to be selected` + }, + parameters: { + choose: `Choose the contract parameters` + }, + rejected: { + description: `You can safely close this window, the contract deployment will not occur.`, + title: `The deployment has been rejected` + }, + state: { + completed: `The contract deployment has been completed`, + preparing: `Preparing transaction for network transmission`, + validatingCode: `Validating the deployed contract code`, + waitReceipt: `Waiting for the contract deployment transaction receipt`, + waitSigner: `Waiting for confirmation of the transaction in the Parity Secure Signer` + }, + title: { + completed: `completed`, + deployment: `deployment`, + details: `contract details`, + failed: `deployment failed`, + parameters: `contract parameters`, + rejected: `rejected` + } +}; diff --git a/js/src/i18n/_default/details_windows.js b/js/src/i18n/_default/details_windows.js new file mode 100644 index 0000000000000000000000000000000000000000..fcc5700665db8ae92fc1906d7ac966c2d9783bed --- /dev/null +++ b/js/src/i18n/_default/details_windows.js @@ -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 . + +export default `Windows`; diff --git a/js/src/i18n/_default/editMeta.js b/js/src/i18n/_default/editMeta.js new file mode 100644 index 0000000000000000000000000000000000000000..b5b5213417d0b8b0515a6db62f3b2ea1b24afca1 --- /dev/null +++ b/js/src/i18n/_default/editMeta.js @@ -0,0 +1,34 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + description: { + hint: `description for this address`, + label: `address description` + }, + name: { + label: `name` + }, + passwordHint: { + hint: `a hint to allow password recovery`, + label: `(optional) password hint` + }, + tags: { + hint: `press to add a tag`, + label: `(optional) tags` + }, + title: `edit metadata` +}; diff --git a/js/src/i18n/_default/errors.js b/js/src/i18n/_default/errors.js new file mode 100644 index 0000000000000000000000000000000000000000..76fed24cd61ab0b6628a105b28ac6f0b477adaa6 --- /dev/null +++ b/js/src/i18n/_default/errors.js @@ -0,0 +1,24 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + duplicateName: `the name already exists`, + invalidKey: `the raw key needs to be hex, 64 characters in length and contain the prefix "0x"`, + noFile: `select a valid wallet file to import`, + noKey: `you need to provide the raw private key`, + noMatchPassword: `the supplied passwords does not match`, + noName: `you need to specify a valid name` +}; diff --git a/js/src/i18n/_default/executeContract.js b/js/src/i18n/_default/executeContract.js new file mode 100644 index 0000000000000000000000000000000000000000..011264d3fe6cb1b2e265568f6ecf0943670ac95b --- /dev/null +++ b/js/src/i18n/_default/executeContract.js @@ -0,0 +1,58 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + busy: { + posted: `Your transaction has been posted to the network`, + title: `The function execution is in progress`, + waitAuth: `Waiting for authorization in the Parity Signer` + }, + button: { + cancel: `cancel`, + done: `done`, + next: `next`, + post: `post transaction`, + prev: `prev` + }, + details: { + address: { + hint: `from account`, + label: `the account to transact with` + }, + advancedCheck: { + label: `advanced sending options` + }, + amount: { + hint: `the amount to send to with the transaction`, + label: `transaction value (in ETH)` + }, + function: { + hint: `the function to call on the contract`, + label: `function to execute` + } + }, + rejected: { + state: `You can safely close this window, the function execution will not occur.`, + title: `The execution has been rejected` + }, + steps: { + advanced: `advanced options`, + complete: `complete`, + rejected: `rejected`, + sending: `sending`, + transfer: `function details` + } +}; diff --git a/js/src/i18n/_default/extension.js b/js/src/i18n/_default/extension.js new file mode 100644 index 0000000000000000000000000000000000000000..88ba336748062b2c1135a9669687af0f1f168839 --- /dev/null +++ b/js/src/i18n/_default/extension.js @@ -0,0 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + install: `Install the extension now`, + intro: `Parity now has an extension available for Chrome that allows safe browsing of Ethereum-enabled distributed applications. It is highly recommended that you install this extension to further enhance your Parity experience.` +}; diff --git a/js/src/i18n/_default/firstRun.js b/js/src/i18n/_default/firstRun.js new file mode 100644 index 0000000000000000000000000000000000000000..8439d04798e2343bf9a216199d69d18f894acde8 --- /dev/null +++ b/js/src/i18n/_default/firstRun.js @@ -0,0 +1,32 @@ +// 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 . + +export default { + button: { + close: `Close`, + create: `Create`, + next: `Next`, + print: `Print Phrase`, + skip: `Skip` + }, + title: { + completed: `completed`, + newAccount: `new account`, + recovery: `recovery`, + terms: `terms`, + welcome: `welcome` + } +}; diff --git a/js/src/i18n/_default/home.js b/js/src/i18n/_default/home.js new file mode 100644 index 0000000000000000000000000000000000000000..0b5e68c04ec62f82cdc2c644e3e1e114b616ccc4 --- /dev/null +++ b/js/src/i18n/_default/home.js @@ -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 . + +export default { + account: { + visited: `accessed {when}` + }, + accounts: { + none: `No recent accounts history available`, + title: `Recent Accounts` + }, + dapp: { + visited: `accessed {when}` + }, + dapps: { + none: `No recent Applications history available`, + title: `Recent Dapps` + }, + title: `Parity Home`, + url: { + none: `No recent URL history available`, + title: `Web Applications`, + visited: `visited {when}` + } +}; diff --git a/js/src/i18n/_default/index.js b/js/src/i18n/_default/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7e8bac8ef33140639270374fa3665d6224f418f7 --- /dev/null +++ b/js/src/i18n/_default/index.js @@ -0,0 +1,49 @@ +// 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 . + +export account from './account'; +export accounts from './accounts'; +export addAddress from './addAddress'; +export addContract from './addContract'; +export addressSelect from './addressSelect'; +export application from './application'; +export connection from './connection'; +export contract from './contract'; +export createAccount from './createAccount'; +export createWallet from './createWallet'; +export dapp from './dapp'; +export dapps from './dapps'; +export deleteAccount from './deleteAccount'; +export deployContract from './deployContract'; +export editMeta from './editMeta'; +export errors from './errors'; +export executeContract from './executeContract'; +export extension from './extension'; +export firstRun from './firstRun'; +export home from './home'; +export loadContract from './loadContract'; +export parityBar from './parityBar'; +export passwordChange from './passwordChange'; +export settings from './settings'; +export shapeshift from './shapeshift'; +export tabBar from './tabBar'; +export transfer from './transfer'; +export txEditor from './txEditor'; +export ui from './ui'; +export upgradeParity from './upgradeParity'; +export vaults from './vaults'; +export walletSettings from './walletSettings'; +export web from './web'; diff --git a/js/src/i18n/_default/loadContract.js b/js/src/i18n/_default/loadContract.js new file mode 100644 index 0000000000000000000000000000000000000000..d4edb3a81dd2c78bab93f844db6dfa5525274ca3 --- /dev/null +++ b/js/src/i18n/_default/loadContract.js @@ -0,0 +1,43 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + cancel: `Cancel`, + load: `Load`, + no: `No`, + yes: `Yes` + }, + contract: { + savedAt: `Saved {when}` + }, + header: { + saved: `Saved Contracts`, + snippets: `Contract Snippets` + }, + removal: { + confirm: `Are you sure you want to remove the following contract from your saved contracts?`, + savedAt: `Saved {when}` + }, + tab: { + local: `Local`, + snippets: `Snippets` + }, + title: { + remove: `confirm removal`, + view: `view contracts` + } +}; diff --git a/js/src/i18n/_default/parityBar.js b/js/src/i18n/_default/parityBar.js new file mode 100644 index 0000000000000000000000000000000000000000..94090a961240f100ddc2098ebb5f55ae3b3860a4 --- /dev/null +++ b/js/src/i18n/_default/parityBar.js @@ -0,0 +1,29 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + close: `Close` + }, + label: { + parity: `Parity`, + signer: `Signer` + }, + title: { + accounts: `Default Account`, + signer: `Parity Signer: Pending` + } +}; diff --git a/js/src/i18n/_default/passwordChange.js b/js/src/i18n/_default/passwordChange.js new file mode 100644 index 0000000000000000000000000000000000000000..fc05792509fd3435e341d403d44656a203bba7e5 --- /dev/null +++ b/js/src/i18n/_default/passwordChange.js @@ -0,0 +1,53 @@ +// 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 . + +export default { + button: { + cancel: `Cancel`, + change: `Change`, + test: `Test`, + wait: `Wait...` + }, + currentPassword: { + hint: `your current password for this account`, + label: `current password` + }, + newPassword: { + hint: `the new password for this account`, + label: `new password` + }, + passwordHint: { + hint: `hint for the new password`, + label: `(optional) new password hint` + }, + repeatPassword: { + error: `the supplied passwords do not match`, + hint: `repeat the new password for this account`, + label: `repeat new password` + }, + success: `Your password has been successfully changed`, + tabChange: { + label: `Change Password` + }, + tabTest: { + label: `Test Password` + }, + testPassword: { + hint: `your account password`, + label: `password` + }, + title: `Password Manager` +}; diff --git a/js/src/i18n/_default/settings.js b/js/src/i18n/_default/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..7825660d2d62564768fc838b87f2ec74b2dafb85 --- /dev/null +++ b/js/src/i18n/_default/settings.js @@ -0,0 +1,89 @@ +// 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 . + +export default { + background: { + button_more: `generate more`, + overview_0: `The background pattern you can see right now is unique to your Parity installation. It will change every time you create a new Signer token. This is so that decentralized applications cannot pretend to be trustworthy.`, + overview_1: `Pick a pattern you like and memorize it. This Pattern will always be shown from now on, unless you clear your browser cache or use a new Signer token.`, + label: `background` + }, + parity: { + languages: { + hint: `the language this interface is displayed with`, + label: `UI language` + }, + loglevels: `Choose the different logs level.`, + modes: { + hint: `the syning mode for the Parity node`, + label: `mode of operation`, + mode_active: `Parity continuously syncs the chain`, + mode_dark: `Parity syncs only when the RPC is active`, + mode_offline: `Parity doesn't sync`, + mode_passive: `Parity syncs initially, then sleeps and wakes regularly to resync` + }, + overview_0: `Control the Parity node settings and mode of operation via this interface.`, + label: `parity` + }, + proxy: { + details_0: `Instead of accessing Parity via the IP address and port, you will be able to access it via the .parity subdomain, by visiting {homeProxy}. To setup subdomain-based routing, you need to add the relevant proxy entries to your browser,`, + details_1: `To learn how to configure the proxy, instructions are provided for {windowsLink}, {macOSLink} or {ubuntuLink}.`, + details_macos: `macOS`, + details_ubuntu: `Ubuntu`, + details_windows: `Windows`, + overview_0: `The proxy setup allows you to access Parity and all associated decentralized applications via memorable addresses.`, + label: `proxy` + }, + views: { + accounts: { + description: `A list of all the accounts associated to and imported into this Parity instance. Send transactions, receive incoming values, manage your balances and fund your accounts.`, + label: `Accounts` + }, + addresses: { + description: `A list of all contacts and address book entries that is managed by this Parity instance. Watch accounts and have the details available at the click of a button when transacting.`, + label: `Addressbook` + }, + apps: { + description: `Distributed applications that interact with the underlying network. Add applications, manage you application portfolio and interact with application from around the network.`, + label: `Applications` + }, + contracts: { + description: `Watch and interact with specific contracts that have been deployed on the network. This is a more technically-focused environment, specifically for advanced users that understand the inner working of certain contracts.`, + label: `Contracts` + }, + overview_0: `Manage the available application views, using only the parts of the application that is applicable to you.`, + overview_1: `Are you an end-user? The defaults are setups for both beginner and advanced users alike.`, + overview_2: `Are you a developer? Add some features to manage contracts are interact with application deployments.`, + overview_3: `Are you a miner or run a large-scale node? Add the features to give you all the information needed to watch the node operation.`, + settings: { + description: `This view. Allows you to customize the application in term of options, operation and look and feel.`, + label: `Settings` + }, + signer: { + description: `The secure transaction management area of the application where you can approve any outgoing transactions made from the application as well as those placed into the queue by distributed applications.`, + label: `Signer` + }, + status: { + description: `See how the Parity node is performing in terms of connections to the network, logs from the actual running instance and details of mining (if enabled and configured).`, + label: `Status` + }, + label: `views`, + home: { + label: `Home` + } + }, + label: `settings` +}; diff --git a/js/src/i18n/_default/shapeshift.js b/js/src/i18n/_default/shapeshift.js new file mode 100644 index 0000000000000000000000000000000000000000..2479278349d0baa115eae93f47bf9a0af8d4df3a --- /dev/null +++ b/js/src/i18n/_default/shapeshift.js @@ -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 . + +export default { + awaitingDepositStep: { + awaitingConfirmation: `Awaiting confirmation of the deposit address for your {typeSymbol} funds exchange`, + awaitingDeposit: `{shapeshiftLink} is awaiting a {typeSymbol} deposit. Send the funds from your {typeSymbol} network client to -`, + minimumMaximum: `{minimum} minimum, {maximum} maximum` + }, + awaitingExchangeStep: { + awaitingCompletion: `Awaiting the completion of the funds exchange and transfer of funds to your Parity account.`, + receivedInfo: `{shapeshiftLink} has received a deposit of -` + }, + button: { + cancel: `Cancel`, + done: `Close`, + shift: `Shift Funds` + }, + completedStep: { + completed: `{shapeshiftLink} has completed the funds exchange.`, + parityFunds: `The change in funds will be reflected in your Parity account shortly.` + }, + errorStep: { + info: `The funds shifting via {shapeshiftLink} failed with a fatal error on the exchange. The error message received from the exchange is as follow:` + }, + optionsStep: { + noPairs: `There are currently no exchange pairs/coins available to fund with.`, + returnAddr: { + hint: `the return address for send failures`, + label: `(optional) {coinSymbol} return address` + }, + terms: { + label: `I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity` + }, + typeSelect: { + hint: `the type of crypto conversion to do`, + label: `fund account from` + } + }, + price: { + minMax: `({minimum} minimum, {maximum} maximum)` + }, + title: { + completed: `completed`, + deposit: `awaiting deposit`, + details: `details`, + error: `exchange failed`, + exchange: `awaiting exchange` + }, + warning: { + noPrice: `No price match was found for the selected type` + } +}; diff --git a/js/src/i18n/_default/tabBar.js b/js/src/i18n/_default/tabBar.js new file mode 100644 index 0000000000000000000000000000000000000000..1692a19975b59b3497253b39387e9c2c8fc175e9 --- /dev/null +++ b/js/src/i18n/_default/tabBar.js @@ -0,0 +1,21 @@ +// 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 . + +export default { + tooltip: { + overview: `navigate between the different parts and views of the application, switching between an account view, token view and distributed application view` + } +}; diff --git a/js/src/i18n/_default/transfer.js b/js/src/i18n/_default/transfer.js new file mode 100644 index 0000000000000000000000000000000000000000..742e816f1761e3a6a9a16bfc560f460abe41c91c --- /dev/null +++ b/js/src/i18n/_default/transfer.js @@ -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 . + +export default { + advanced: { + data: { + hint: `the data to pass through with the transaction`, + label: `transaction data` + } + }, + warning: { + wallet_spent_limit: `This transaction value is above the remaining daily limit. It will need to be confirmed by other owners.` + } +}; diff --git a/js/src/i18n/_default/txEditor.js b/js/src/i18n/_default/txEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..7dab159a8a7229f0bd8279daeed37226ab5c4f0e --- /dev/null +++ b/js/src/i18n/_default/txEditor.js @@ -0,0 +1,39 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + condition: { + block: { + hint: `The minimum block to send from`, + label: `Transaction send block` + }, + blocknumber: `Send after BlockNumber`, + date: { + hint: `The minimum date to send from`, + label: `Transaction send date` + }, + datetime: `Send after Date & Time`, + label: `Condition where transaction activates`, + none: `No conditions`, + time: { + hint: `The minimum time to send from`, + label: `Transaction send time` + } + }, + gas: { + info: `You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.` + } +}; diff --git a/js/src/i18n/_default/ui.js b/js/src/i18n/_default/ui.js new file mode 100644 index 0000000000000000000000000000000000000000..242f14b9b32bd88ba5231d8261ecab239a82c6db --- /dev/null +++ b/js/src/i18n/_default/ui.js @@ -0,0 +1,84 @@ +// 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 . + +export default { + balance: { + none: `There are no balances associated with this account` + }, + blockStatus: { + bestBlock: `{blockNumber} best block`, + syncStatus: `{currentBlock}/{highestBlock} syncing`, + warpRestore: `{percentage}% warp restore`, + warpStatus: `, {percentage}% historic` + }, + confirmDialog: { + no: `no`, + yes: `yes` + }, + identityName: { + null: `NULL`, + unnamed: `UNNAMED` + }, + passwordStrength: { + label: `password strength` + }, + tooltips: { + button: { + done: `Done`, + next: `Next`, + skip: `Skip` + } + }, + txHash: { + confirmations: `{count} {value, plural, one {confirmation} other {confirmations}}`, + oog: `The transaction might have gone out of gas. Try again with more gas.`, + posted: `The transaction has been posted to the network with a hash of {hashLink}`, + waiting: `waiting for confirmations` + }, + verification: { + gatherData: { + accountHasRequested: { + false: `You did not request verification from this account yet.`, + pending: `Checking if you requested verification…`, + true: `You already requested verification from this account.` + }, + accountIsVerified: { + false: `Your account is not verified yet.`, + pending: `Checking if your account is verified…`, + true: `Your account is already verified.` + }, + email: { + hint: `the code will be sent to this address`, + label: `e-mail address` + }, + fee: `The additional fee is {amount} ETH.`, + isAbleToRequest: { + pending: `Validating your input…` + }, + isServerRunning: { + false: `The verification server is not running.`, + pending: `Checking if the verification server is running…`, + true: `The verification server is running.` + }, + nofee: `There is no additional fee.`, + phoneNumber: { + hint: `the SMS will be sent to this number`, + label: `phone number in international format` + }, + termsOfService: `I agree to the terms and conditions below.` + } + } +}; diff --git a/js/src/i18n/_default/upgradeParity.js b/js/src/i18n/_default/upgradeParity.js new file mode 100644 index 0000000000000000000000000000000000000000..cca634b88cefe3786a8b9ee11f753243dbc45755 --- /dev/null +++ b/js/src/i18n/_default/upgradeParity.js @@ -0,0 +1,44 @@ +// 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 . + +export default { + busy: `Your upgrade to Parity {newversion} is currently in progress`, + button: { + close: `close`, + done: `done`, + upgrade: `upgrade now` + }, + completed: `Your upgrade to Parity {newversion} has been successfully completed.`, + consensus: { + capable: `Your current Parity version is capable of handling the network requirements.`, + capableUntil: `Your current Parity version is capable of handling the network requirements until block {blockNumber}`, + incapableSince: `Your current Parity version is incapable of handling the network requirements since block {blockNumber}`, + unknown: `Your current Parity version is capable of handling the network requirements.` + }, + failed: `Your upgrade to Parity {newversion} has failed with an error.`, + info: { + upgrade: `A new version of Parity, version {newversion} is available as an upgrade from your current version {currentversion}` + }, + step: { + completed: `upgrade completed`, + error: `error`, + info: `upgrade available`, + updating: `upgrading parity` + }, + version: { + unknown: `unknown` + } +}; diff --git a/js/src/i18n/_default/vaults.js b/js/src/i18n/_default/vaults.js new file mode 100644 index 0000000000000000000000000000000000000000..3024beed31f0e49eb86a3121c6c048a0339807d6 --- /dev/null +++ b/js/src/i18n/_default/vaults.js @@ -0,0 +1,75 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + accounts: { + button: { + cancel: `Cancel`, + execute: `Set` + }, + empty: `There are no accounts in this vault`, + title: `Manage Vault Accounts` + }, + button: { + accounts: `accounts`, + add: `create vault`, + close: `close vault`, + open: `open vault` + }, + confirmClose: { + info: `You are about to close a vault. Any accounts associated with the vault won't be visible after this operation concludes. To view the associated accounts, open the vault again.`, + title: `Close Vault` + }, + confirmOpen: { + info: `You are about to open a vault. After confirming your password, all accounts associated with this vault will be visible. Closing the vault will remove the accounts from view until the vault is opened again.`, + password: { + hint: `the password specified when creating the vault`, + label: `vault password` + }, + title: `Open Vault` + }, + create: { + button: { + close: `close`, + vault: `create vault` + }, + description: { + hint: `an extended description for the vault` + }, + descriptions: { + label: `(optional) description` + }, + hint: { + hint: `(optional) a hint to help with remembering the password`, + label: `password hint` + }, + name: { + hint: `a name for the vault`, + label: `vault name` + }, + password: { + hint: `a strong, unique password`, + label: `password` + }, + password2: { + hint: `verify your password`, + label: `password (repeat)` + }, + title: `Create a new vault` + }, + empty: `There are currently no vaults to display.`, + title: `Vault Management` +}; diff --git a/js/src/i18n/_default/walletSettings.js b/js/src/i18n/_default/walletSettings.js new file mode 100644 index 0000000000000000000000000000000000000000..57dc1a169d35a234ff86dae9966716fa37b0dd9e --- /dev/null +++ b/js/src/i18n/_default/walletSettings.js @@ -0,0 +1,58 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + changes: { + modificationString: `For your modifications to be taken into account, + other owners have to send the same modifications. They can paste + this string to make it easier:`, + none: `No modifications have been made to the Wallet settings.`, + overview: `You are about to make the following modifications` + }, + edit: { + message: `In order to edit this contract's settings, at + least {owners, number} {owners, plural, one {owner } other {owners }} have to + send the very same modifications. You can paste a stringified version + of the modifications here.` + }, + modifications: { + daylimit: { + hint: `amount of ETH spendable without confirmations`, + label: `wallet day limit` + }, + fromString: { + label: `modifications` + }, + owners: { + label: `other wallet owners` + }, + required: { + hint: `number of required owners to accept a transaction`, + label: `required owners` + }, + sender: { + hint: `send modifications as this owner`, + label: `from account (wallet owner)` + } + }, + rejected: { + busyStep: { + state: `The wallet settings will not be modified. You can safely close this window.`, + title: `The modifications have been rejected` + }, + title: `rejected` + } +}; diff --git a/js/src/i18n/_default/web.js b/js/src/i18n/_default/web.js new file mode 100644 index 0000000000000000000000000000000000000000..6136d387f60ba80287a0ee05016b5577306726e3 --- /dev/null +++ b/js/src/i18n/_default/web.js @@ -0,0 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + requestToken: `Requesting access token...` +}; diff --git a/js/src/i18n/en/settings.js b/js/src/i18n/en/settings.js index c45131da2f084cbbc930a64830bfb835342071ec..48a656b934d9fc33a9fc78759ec5da17acb2cdc0 100644 --- a/js/src/i18n/en/settings.js +++ b/js/src/i18n/en/settings.js @@ -48,6 +48,10 @@ export default { label: 'Contracts' }, + home: { + label: 'Home' + }, + status: { label: 'Status' }, diff --git a/js/src/i18n/nl/account.js b/js/src/i18n/nl/account.js new file mode 100755 index 0000000000000000000000000000000000000000..57701292a595a76b747166beaf196afe7ee30afb --- /dev/null +++ b/js/src/i18n/nl/account.js @@ -0,0 +1,35 @@ +// 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 . + +export default { + button: { + delete: `verwijder account`, + edit: `bewerk`, + password: `wachtwoord`, + shapeshift: `shapeshift`, + transfer: `verzend`, + verify: `verifieer` + }, + header: { + outgoingTransactions: `{count} uitgaande transacties`, + uuid: `uuid: {uuid}` + }, + title: `Account Beheer`, + transactions: { + poweredBy: `Transactie lijst mede mogelijk door {etherscan}`, + title: `transacties` + } +}; diff --git a/js/src/i18n/nl/accounts.js b/js/src/i18n/nl/accounts.js new file mode 100755 index 0000000000000000000000000000000000000000..3153246328db457a8bec789da19207972dafb098 --- /dev/null +++ b/js/src/i18n/nl/accounts.js @@ -0,0 +1,31 @@ +// 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 . + +export default { + button: { + newAccount: `nieuw account`, + newWallet: `nieuw wallet`, + vaults: `kluizen` + }, + summary: { + minedBlock: `Opgenomen in blok #{blockNumber}` + }, + title: `Accounts Overzicht`, + tooltip: { + actions: `voor de huidige weergave zijn koppelingen beschikbaar op de werkbalk voor snelle toegang: het uitvoeren van acties of het creëren van een nieuw item`, + overview: `hier vind je een overzichtelijke weergave van je accounts, waarin je meta informatie kunt bewerken en transacties kunt uitvoeren en bekijken` + } +}; diff --git a/js/src/i18n/nl/addAddress.js b/js/src/i18n/nl/addAddress.js new file mode 100755 index 0000000000000000000000000000000000000000..1b2281888d4c616392dd9c9cef8989a9a902ceac --- /dev/null +++ b/js/src/i18n/nl/addAddress.js @@ -0,0 +1,37 @@ +// 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 . + +export default { + button: { + add: `Adres Opslaan`, + close: `Annuleer` + }, + input: { + address: { + hint: `het netwerk adres van het item`, + label: `Netwerk Adres` + }, + description: { + hint: `een uitgebereide beschrijving voor het adres`, + label: `(optioneel) adres beschrijving` + }, + name: { + hint: `een beschrijvende naam van het adres`, + label: `Adres Naam` + } + }, + label: `voeg opgeslagen adres toe` +}; diff --git a/js/src/i18n/nl/addContract.js b/js/src/i18n/nl/addContract.js new file mode 100755 index 0000000000000000000000000000000000000000..c0b60806ccbf1c3344322c1e5a7bdb314556ee4d --- /dev/null +++ b/js/src/i18n/nl/addContract.js @@ -0,0 +1,60 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + abi: { + hint: `de abi van het contract`, + label: `contract abi` + }, + abiType: { + custom: { + description: `Contract aangemaakt met een custom ABI`, + label: `Custom Contract` + }, + multisigWallet: { + description: `Ethereum Multisig contract {link}`, + label: `Multisig Wallet`, + link: `zie contract code` + }, + token: { + description: `Een standaard {erc20} token`, + erc20: `ERC 20`, + label: `Token` + } + }, + address: { + hint: `het netwerk adres van het contract`, + label: `netwerk adres` + }, + button: { + add: `Voeg Contract toe`, + cancel: `Annuleer`, + next: `Volgende`, + prev: `Terug` + }, + description: { + hint: `een uitgebreide omschrijving van het contract`, + label: `(optioneel) contract beschrijving` + }, + name: { + hint: `een beschrijvende naam van het contract`, + label: `contract naam` + }, + title: { + details: `voer contract details in`, + type: `kies een contract type` + } +}; diff --git a/js/src/i18n/nl/addressSelect.js b/js/src/i18n/nl/addressSelect.js new file mode 100755 index 0000000000000000000000000000000000000000..f447ee08871fd76ed4298132c25984a6513fc53e --- /dev/null +++ b/js/src/i18n/nl/addressSelect.js @@ -0,0 +1,26 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + fromEmail: `Geverifieerd met behulp van e-mail {email}`, + fromRegistry: `{name} (from registry)`, + labels: { + accounts: `accounts`, + contacts: `contacten`, + contracts: `contracts` + }, + noAccount: `Geen account gevonden voor deze zoekopdracht...` +}; diff --git a/js/src/i18n/nl/application.js b/js/src/i18n/nl/application.js new file mode 100755 index 0000000000000000000000000000000000000000..1a6b2ee5c4a36bf9fdb1be894d09826fd776b60f --- /dev/null +++ b/js/src/i18n/nl/application.js @@ -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 . + +export default { + status: { + consensus: { + capable: `Capable`, + capableUntil: `Capable tot #{blockNumber}`, + incapableSince: `Incapable sinds #{blockNumber}`, + unknown: `Onbekende capability` + }, + upgrade: `Upgrade` + } +}; diff --git a/js/src/i18n/nl/connection.js b/js/src/i18n/nl/connection.js new file mode 100755 index 0000000000000000000000000000000000000000..3fa28effea6bb16f6e12b015a7d71110befcb8c4 --- /dev/null +++ b/js/src/i18n/nl/connection.js @@ -0,0 +1,26 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + connectingAPI: `Verbinden met de Parity Secure API.`, + connectingNode: `Verbinden met de Parity Node. Conroleer of je Parity node actief en op het netwerk bereikbaar is als dit bericht op je scherm blijft staan.`, + invalidToken: `ongeldige signer token`, + noConnection: `Kan geen verbinding maken met de Parity Secure API. Voer {newToken} uit en geef het token hieronder in om het secure token bij te werken of een nieuwe te genereren.`, + token: { + hint: `een Parity gegenereerd token`, + label: `secure token` + } +}; diff --git a/js/src/i18n/nl/contract.js b/js/src/i18n/nl/contract.js new file mode 100755 index 0000000000000000000000000000000000000000..5f2ca39cc57e75e273ce9d58e410f7d411114624 --- /dev/null +++ b/js/src/i18n/nl/contract.js @@ -0,0 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + minedBlock: `Opgenomen in blok #{blockNumber}` +}; diff --git a/js/src/i18n/nl/createAccount.js b/js/src/i18n/nl/createAccount.js new file mode 100755 index 0000000000000000000000000000000000000000..69585df35a1d23e56ac18bcb79b90c911cfd6830 --- /dev/null +++ b/js/src/i18n/nl/createAccount.js @@ -0,0 +1,156 @@ +// 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 . + +export default { + accountDetails: { + address: { + hint: `Het netwerk adres van het account`, + label: `adres` + }, + name: { + hint: `Een beschrijvende naam van het account`, + label: `account naam` + }, + phrase: { + hint: `De account herstel zin`, + label: `Eigenaar's herstel zin (houd deze woorden veilig en prive want hiermee kun je volledige, ongelimiteerde toegang tot het account verkrijgen).` + } + }, + accountDetailsGeth: { + imported: `Je hebt {number} adressen geïmporteerd uit de Geth keystore:` + }, + button: { + back: `Terug`, + cancel: `Annuleer`, + close: `Sluit`, + create: `Aanmaken`, + import: `Importeer`, + next: `Volgende`, + print: `Herstel zin afdrukken` + }, + creationType: { + fromGeth: { + label: `Importeer accounts uit Geth keystore` + }, + fromJSON: { + label: `Importeer account uit een opgeslagen JSON file` + }, + fromNew: { + label: `Handmatig account aanmaken` + }, + fromPhrase: { + label: `Herstel account met een herstel zin` + }, + fromPresale: { + label: `Importeer account van een Ethereum voor-verkoop (pre-sale) wallet` + }, + fromRaw: { + label: `Importeer een prive sleutel (raw private key)` + } + }, + newAccount: { + hint: { + hint: `(optioneel) een hint om je te helpen het wachtwoord te herinneren`, + label: `wachtwoord hint` + }, + name: { + hint: `een beschrijvende naam van het account`, + label: `account naam` + }, + password: { + hint: `een sterk en uniek wachtwoord`, + label: `wachtwoord` + }, + password2: { + hint: `bevestig je wachtwoord`, + label: `wachtwoord (herhaal)` + } + }, + newGeth: { + noKeys: `Er zijn momenteel geen importeerbare sleutels (keys) beschikbaar in de Geth keystore; of ze zijn al in je Parity installatie beschikbaar` + }, + newImport: { + file: { + hint: `het te importeren wallet bestand`, + label: `wallet bestand` + }, + hint: { + hint: `(optioneel) een hint om je te helpen het wachtwoord te herinneren`, + label: `wachtwoord hint` + }, + name: { + hint: `een beschrijvende naam van het account`, + label: `account naam` + }, + password: { + hint: `het wachtwoord om je wallet te openen`, + label: `wachtwoord` + } + }, + rawKey: { + hint: { + hint: `(optioneel) een hint om je te helpen het wachtwoord te herinneren`, + label: `wachtwoord hint` + }, + name: { + hint: `een beschrijvende naam van het account`, + label: `account naam` + }, + password: { + hint: `een sterk en uniek wachtwoord`, + label: `wachtwoord` + }, + password2: { + hint: `herhaal je wachtwoord ter controle`, + label: `wachtwoord (herhaling)` + }, + private: { + hint: `de hexadecimaal gecodeerde prive sleutel (raw private key)`, + label: `prive sleutel` + } + }, + recoveryPhrase: { + hint: { + hint: `(optioneel) een hint om je te helpen het wachtwoord te herinneren`, + label: `wachtwoord hint` + }, + name: { + hint: `een beschrijvende naam van het account`, + label: `account naam` + }, + password: { + hint: `een sterk en uniek wachtwoord`, + label: `wachtwoord` + }, + password2: { + hint: `herhaal je wachtwoord ter controle`, + label: `wachtwoord (herhaling)` + }, + phrase: { + hint: `de account herstel zin opgebouwd uit een aantal willekeurige woorden`, + label: `account herstel zin` + }, + windowsKey: { + label: `Sleutel (key) is aangemaakt met Parity <1.4.5 op Windows` + } + }, + title: { + accountInfo: `account informatie`, + createAccount: `account aanmaken`, + createType: `manier van aanmaken`, + importWallet: `importeer wallet` + } +}; diff --git a/js/src/i18n/nl/createWallet.js b/js/src/i18n/nl/createWallet.js new file mode 100644 index 0000000000000000000000000000000000000000..6acd2e2c8d64debc35b022a65aafe9dbfbd7430b --- /dev/null +++ b/js/src/i18n/nl/createWallet.js @@ -0,0 +1,105 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + add: `Voeg toe`, + cancel: `Annuleer`, + close: `Sluit`, + create: `Creëer`, + done: `Klaar`, + next: `Volgende`, + sending: `Verzenden...` + }, + deployment: { + message: `Het aanmaken wordt momenteel uitgevoerd` + }, + details: { + address: { + hint: `het wallet contract adres`, + label: `wallet adres` + }, + dayLimitMulti: { + hint: `hoeveelheid ETH die dagelijks kan worden uitgegeven zonder bevestigingen`, + label: `wallet dag limiet` + }, + description: { + hint: `de lokale omschrijving voor dit wallet`, + label: `wallet omschrijving (optioneel)` + }, + descriptionMulti: { + hint: `de lokale omschrijving voor dit wallet`, + label: `wallet omschrijving (optioneel)` + }, + name: { + hint: `de lokale naam voor dit wallet`, + label: `wallet naam` + }, + nameMulti: { + hint: `de lokale naam voor dit wallet`, + label: `wallet naam` + }, + ownerMulti: { + hint: `het eigenaars account van dit contract`, + label: `van account (contract eigenaar)` + }, + ownersMulti: { + label: `andere wallet eigenaren` + }, + ownersMultiReq: { + hint: `vereiste aantal eigenaren om de transactie goed te keuren`, + label: `vereiste eigenaren` + } + }, + info: { + added: `toegevoegd`, + copyAddress: `kopier adres naar klembord`, + created: `{name} is {deployedOrAdded} in`, + dayLimit: `De dag limiet is ingestel op {dayLimit} ETH.`, + deployed: `aangemaakt`, + numOwners: `{numOwners} eigenaren zijn vereist om de transactie goed te keuren.`, + owners: `De wallet eigenaren zijn:` + }, + rejected: { + message: `Het aanmaken is mislukt`, + state: `Je wallet zal niet worden aangemaakt. Je kunt dit venster nu veilig sluiten.`, + title: `mislukt` + }, + states: { + completed: `Het contract is succesvol aangemaakt`, + preparing: `Transactie aan het voorbereiden voor verzending op het netwerk`, + validatingCode: `De contract code van het aangemaakte contract wordt gevalideerd`, + waitingConfirm: `Wachten tot de transactie bevestigd is in de Parity Secure Signer`, + waitingReceipt: `Wachten tot het aanmaken van het contract bevestigd is` + }, + steps: { + deployment: `wallet aanmaken`, + details: `wallet details`, + info: `wallet informatie`, + type: `wallet type` + }, + type: { + multisig: { + description: `Creëer/Maak een {link} Wallet aan`, + label: `Multi-Sig wallet`, + link: `standaard multi-signature` + }, + watch: { + description: `Voeg een bestaand wallet toe aan je accounts`, + label: `Monitor/volg een wallet` + } + } +}; diff --git a/js/src/i18n/nl/dapp.js b/js/src/i18n/nl/dapp.js new file mode 100755 index 0000000000000000000000000000000000000000..b57a6ca16b15b4717308181e0119d164b779ea5c --- /dev/null +++ b/js/src/i18n/nl/dapp.js @@ -0,0 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + loading: `Bezig met laden`, + unavailable: `De dapp is niet bereikbaar` +}; diff --git a/js/src/i18n/nl/dapps.js b/js/src/i18n/nl/dapps.js new file mode 100644 index 0000000000000000000000000000000000000000..3c7cf6d7b5880b59a69254a4aa4a11bea3b124d2 --- /dev/null +++ b/js/src/i18n/nl/dapps.js @@ -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 . + +export default { + add: { + builtin: { + desc: `Experimentele applicaties, ontwikkeld door het Parity team om te demonstreren wat de dapp mogelijkheden, integratie en experimentele opties zijn; en om netwerkbreed client gedrag te controleren.`, + label: `Applicaties gebundeld met Parity` + }, + label: `zichtbare applicaties`, + local: { + desc: `Alle lokaal door de gebruiker geinstalleerde applicaties die toegang hebben tot de Parity client.`, + label: `Lokaal beschikbare applicaties` + }, + network: { + desc: `Deze applicaties zijn niet bij Parity aangesloten, noch worden ze gepubliceerd door Parity. Alle applicaties blijven in beheer van hun eigen auteur. Zorg ervoor dat je snapt wat het doel van een applicatie is, voordat je ermee aan de slag gaat.`, + label: `Applicaties op het wereldwijde netwerk` + } + }, + button: { + edit: `bewerk`, + permissions: `toestemming` + }, + external: { + accept: `Ik begrijp dat deze toepassingen niet bij Parity zijn aangesloten`, + warning: `Deze applicaties gepuliceerd door derde partijen zijn niet bij Parity aangesloten, noch worden ze gepubliceerd door Parity. Alle applicaties blijven in beheer van hun eigen auteur. Zorg ervoor dat je snapt wat het doel van een applicatie is voordat je ermee aan de slag gaat.` + }, + label: `Gedecentraliseerde Applicaties`, + permissions: { + description: `{activeIcon} account is beschikbaar voor applicaties, {defaultIcon} account is het standaard account`, + label: `zichtbare dapp accounts` + } +}; diff --git a/js/src/i18n/nl/deleteAccount.js b/js/src/i18n/nl/deleteAccount.js new file mode 100755 index 0000000000000000000000000000000000000000..e9e17faedc495eef5b31417fe0e646eb73022409 --- /dev/null +++ b/js/src/i18n/nl/deleteAccount.js @@ -0,0 +1,24 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + password: { + hint: `Voer ter bevestiging het wachtwoord in om je account te verwijderen`, + label: `account wachtwoord` + }, + question: `Weet je zeker dat je het volgende account permanent wilt verwijderen?`, + title: `bevestig verwijderen` +}; diff --git a/js/src/i18n/nl/deployContract.js b/js/src/i18n/nl/deployContract.js new file mode 100644 index 0000000000000000000000000000000000000000..478992464c9f5af4697966e1085e77eae80f2f83 --- /dev/null +++ b/js/src/i18n/nl/deployContract.js @@ -0,0 +1,81 @@ +// 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 . + +export default { + busy: { + title: `Het contract wordt momenteel aangemaakt` + }, + button: { + cancel: `Annuleer`, + close: `Sluit`, + create: `Creëer`, + done: `Klaar`, + next: `Volgende` + }, + completed: { + description: `Je contract is aangemaakt en opgenomen in` + }, + details: { + abi: { + hint: `de abi van het aan te maken contract of solc combined-output`, + label: `abi / solc combined-output` + }, + address: { + hint: `het account wat eigenaar is van dit contract`, + label: `van account (contract eigenaar)` + }, + code: { + hint: `de gecompileerde code van het aan te maken contract`, + label: `code` + }, + contract: { + label: `selecteer een contract` + }, + description: { + hint: `een beschrijving van het contract`, + label: `contract omschrijving (optioneel)` + }, + name: { + hint: `een naam voor het aangemaakte contract`, + label: `contract naam` + } + }, + owner: { + noneSelected: `er dient een geldig account als contract eigenaar geselecteerd te zijn` + }, + parameters: { + choose: `Kies de contract parameters` + }, + rejected: { + description: `Je kunt dit scherm veilig sluiten, het contract zal niet worden aangemaakt.`, + title: `Het aanmaken van het contract is afgewezen` + }, + state: { + completed: `Het contract is succesvol aangemaakt`, + preparing: `Transactie aan het voorbereiden om te verzenden op het netwerk`, + validatingCode: `De contract code van het aangemaakte contract valideren`, + waitReceipt: `Wachten tot het aanmaken van het contract bevestigd is`, + waitSigner: `Wachten tot de transactie bevestigd is in de Parity Secure Signer` + }, + title: { + completed: `voltooid`, + deployment: `aangemaakt`, + details: `contract details`, + failed: `aanmaken mislukt`, + parameters: `contract parameters`, + rejected: `afgewezen` + } +}; diff --git a/js/src/i18n/nl/details_windows.js b/js/src/i18n/nl/details_windows.js new file mode 100644 index 0000000000000000000000000000000000000000..fcc5700665db8ae92fc1906d7ac966c2d9783bed --- /dev/null +++ b/js/src/i18n/nl/details_windows.js @@ -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 . + +export default `Windows`; diff --git a/js/src/i18n/nl/editMeta.js b/js/src/i18n/nl/editMeta.js new file mode 100644 index 0000000000000000000000000000000000000000..9ab84af9d4f2ed6b46f435d44e88b84dca213854 --- /dev/null +++ b/js/src/i18n/nl/editMeta.js @@ -0,0 +1,34 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + description: { + hint: `omschrijving van dit adres`, + label: `adres omschrijving` + }, + name: { + label: `naam` + }, + passwordHint: { + hint: `een hint om je wachtwoord te herstellen`, + label: `(optioneel) wachtwoord hint` + }, + tags: { + hint: `druk op om een label toe te voegen`, + label: `(optional) labels` + }, + title: `bewerk metadata` +}; diff --git a/js/src/i18n/nl/errors.js b/js/src/i18n/nl/errors.js new file mode 100644 index 0000000000000000000000000000000000000000..02f7ae6bc517482762834595373eb2a363c59a23 --- /dev/null +++ b/js/src/i18n/nl/errors.js @@ -0,0 +1,24 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + duplicateName: `deze naam bestaat al`, + invalidKey: `de prive sleutel (raw key) is hexadecimaal, 64 karakters lang en en begint met "0x"`, + noFile: `selecteer een geldig wallet bestand om te importeren`, + noKey: `je dient de prive sleutel (raw private key) in te voeren`, + noMatchPassword: `het ingevoerde wachtwoord is onjuist`, + noName: `je dient een geldige naam voor het account op te geven` +}; diff --git a/js/src/i18n/nl/executeContract.js b/js/src/i18n/nl/executeContract.js new file mode 100644 index 0000000000000000000000000000000000000000..c11c799edd3a43d3d99e405e226f1ad891897c01 --- /dev/null +++ b/js/src/i18n/nl/executeContract.js @@ -0,0 +1,58 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + busy: { + posted: `Je transactie is op het netwerk gepubliceerd`, + title: `De functie wordt uitgevoerd`, + waitAuth: `Wachten op autorisatie in de Parity Signer` + }, + button: { + cancel: `annuleer`, + done: `klaar`, + next: `volgende`, + post: `verzend transactie`, + prev: `vorige` + }, + details: { + address: { + hint: `van account`, + label: `het account waarmee je de transactie wilt uitvoeren` + }, + advancedCheck: { + label: `geavanceerde verzend opties` + }, + amount: { + hint: `de in deze transactie te verzenden hoeveelheid`, + label: `transactie waarde (in ETH)` + }, + function: { + hint: `de uit het contract aan te roepen functie`, + label: `uit te voeren functie` + } + }, + rejected: { + state: `Je kunt dit venster veilig sluiten, de functie zal niet worden uitgevoerd.`, + title: `De uitvoering is afgewezen` + }, + steps: { + advanced: `geavanceerde opties`, + complete: `voltooi`, + rejected: `afgewezen`, + sending: `verzenden`, + transfer: `functie details` + } +}; diff --git a/js/src/i18n/nl/extension.js b/js/src/i18n/nl/extension.js new file mode 100644 index 0000000000000000000000000000000000000000..73d765807530b47b0f1b10a9686725b1a42953a9 --- /dev/null +++ b/js/src/i18n/nl/extension.js @@ -0,0 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + install: `Installeer de extensie nu`, + intro: `Parity heeft nu een extensie voor Chrome beschikbaar waarmee je veillig Ethereum-enabled gedistribueerde applicaties kunt bekijken. Het wordt ten zeerste aanbevolen om deze extensie te installeren om je Parity ervaring nog beter te maken.` +}; diff --git a/js/src/i18n/nl/firstRun.js b/js/src/i18n/nl/firstRun.js new file mode 100755 index 0000000000000000000000000000000000000000..d182c76e1e03866771ee2211919d1fe06e07a18e --- /dev/null +++ b/js/src/i18n/nl/firstRun.js @@ -0,0 +1,32 @@ +// 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 . + +export default { + button: { + close: `Sluiten`, + create: `creëer`, + next: `volgende`, + print: `Woorden Afdrukken`, + skip: `Overslaan` + }, + title: { + completed: `voltooid`, + newAccount: `nieuw account`, + recovery: `herstelzin`, + terms: `voorwaarden`, + welcome: `welkom` + } +}; diff --git a/js/src/i18n/nl/home.js b/js/src/i18n/nl/home.js new file mode 100644 index 0000000000000000000000000000000000000000..4a55894d7e2c23e6643b09091227e30cb45a8194 --- /dev/null +++ b/js/src/i18n/nl/home.js @@ -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 . + +export default { + account: { + visited: `bekeken {when}` + }, + accounts: { + none: `Geen recente account geschiedenis beschikbaar`, + title: `Recente Accounts` + }, + dapp: { + visited: `bekeken {when}` + }, + dapps: { + none: `Geen recente applicatie geschiedenis beschikbaar`, + title: `Recente Dapps` + }, + title: `Parity Home`, + url: { + none: `Geen recente URL geschiedenis beschikbaar`, + title: `Web Applicaties`, + visited: `bezocht {when}` + } +}; diff --git a/js/src/i18n/nl/index.js b/js/src/i18n/nl/index.js old mode 100644 new mode 100755 index 6b50c2d5344b780e8ec21d9f490741be4968957e..281e9974c74ea957ebeeff4d579c6f5473f546f1 --- a/js/src/i18n/nl/index.js +++ b/js/src/i18n/nl/index.js @@ -14,8 +14,72 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import account from './account'; +import accounts from './accounts'; +import addAddress from './addAddress'; +import addContract from './addContract'; +import addressSelect from './addressSelect'; +import application from './application'; +import connection from './connection'; +import contract from './contract'; +import createAccount from './createAccount'; +import createWallet from './createWallet'; +import dapp from './dapp'; +import dapps from './dapps'; +import deleteAccount from './deleteAccount'; +import deployContract from './deployContract'; +import editMeta from './editMeta'; +import errors from './errors'; +import executeContract from './executeContract'; +import extension from './extension'; +import firstRun from './firstRun'; +import home from './home'; +import loadContract from './loadContract'; +import parityBar from './parityBar'; +import passwordChange from './passwordChange'; import settings from './settings'; +import shapeshift from './shapeshift'; +import tabBar from './tabBar'; +import transfer from './transfer'; +import txEditor from './txEditor'; +import ui from './ui'; +import upgradeParity from './upgradeParity'; +import vaults from './vaults'; +import walletSettings from './walletSettings'; +import web from './web'; export default { - settings + account, + accounts, + addAddress, + addContract, + addressSelect, + application, + connection, + contract, + createAccount, + createWallet, + dapp, + dapps, + deleteAccount, + deployContract, + editMeta, + errors, + executeContract, + extension, + firstRun, + home, + loadContract, + parityBar, + passwordChange, + settings, + shapeshift, + tabBar, + transfer, + txEditor, + ui, + upgradeParity, + vaults, + walletSettings, + web }; diff --git a/js/src/i18n/nl/loadContract.js b/js/src/i18n/nl/loadContract.js new file mode 100644 index 0000000000000000000000000000000000000000..fc3f43c3b171cf631117fff0459265adf5f088b9 --- /dev/null +++ b/js/src/i18n/nl/loadContract.js @@ -0,0 +1,43 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + cancel: `Annuleer`, + load: `Laad`, + no: `Nee`, + yes: `Ja` + }, + contract: { + savedAt: `Opgeslagen {when}` + }, + header: { + saved: `Opgeslagen Contracten`, + snippets: `Contract Snippets` + }, + removal: { + confirm: `Weet je zeker dat je het volgende contract van je opgeslagen contracten wilt verwijderen?`, + savedAt: `Opgeslagen {when}` + }, + tab: { + local: `Lokaal`, + snippets: `Snippets` + }, + title: { + remove: `bevestig verwijderen`, + view: `bekijk contracten` + } +}; diff --git a/js/src/i18n/nl/parityBar.js b/js/src/i18n/nl/parityBar.js new file mode 100644 index 0000000000000000000000000000000000000000..eb10fe569b786f4911dc14ff4043fc4d4395acf9 --- /dev/null +++ b/js/src/i18n/nl/parityBar.js @@ -0,0 +1,29 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + button: { + close: `Sluit` + }, + label: { + parity: `Parity`, + signer: `Signer` + }, + title: { + accounts: `Standaard Account`, + signer: `Parity Signer: Wachten` + } +}; diff --git a/js/src/i18n/nl/passwordChange.js b/js/src/i18n/nl/passwordChange.js new file mode 100644 index 0000000000000000000000000000000000000000..c59e50eedfd9ea7be3af5f2df4e5d3af42d6e1cf --- /dev/null +++ b/js/src/i18n/nl/passwordChange.js @@ -0,0 +1,53 @@ +// 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 . + +export default { + button: { + cancel: `Annuleer`, + change: `Wijzig`, + test: `Test`, + wait: `Wacht...` + }, + currentPassword: { + hint: `je huidige wachtwoord voor dit account`, + label: `huidig wachtwoord` + }, + newPassword: { + hint: `het nieuwe wachtwoord vor dit account`, + label: `nieuw wachtwoord` + }, + passwordHint: { + hint: `hint voor het nieuwe wachtwoord`, + label: `(optioneel) nieuwe wachtwoord hint` + }, + repeatPassword: { + error: `de ingevoerde wachtwoorden zijn niet gelijk`, + hint: `herhaal het nieuwe wachtwoord voor dit account`, + label: `herhaal nieuw wachtwoord` + }, + success: `Je wachtwoord is succesvol aangepast`, + tabChange: { + label: `Wijzig wachtwoord` + }, + tabTest: { + label: `Test wachtwoord` + }, + testPassword: { + hint: `je account wachtwoord`, + label: `wachtwoord` + }, + title: `Wachtwoord Manager` +}; diff --git a/js/src/i18n/nl/settings.js b/js/src/i18n/nl/settings.js index 9240859ada8b3802c6f6bb0c08fd976d389ff07d..81f485696669a77bf3c7f9721c9f9b85934ed965 100644 --- a/js/src/i18n/nl/settings.js +++ b/js/src/i18n/nl/settings.js @@ -15,49 +15,75 @@ // along with Parity. If not, see . export default { - label: 'Instellingen', - background: { - label: 'Achtergrond' + button_more: `genereer meer`, + overview_0: `Het achtergrond patroon dat je nu kunt zien is uniek voor jouw Parity installatie. Het veranderd elke keer als je een nieuw Signer token genereerd. Op deze manier kunnen gedecentraliseerde applicaties niet doen alsof ze betrouwbaar zijn.`, + overview_1: `Kies het patroon dat je wilt en onthoud het. Dit patroon wordt vanaf nu altijd getoond, tenzij je je browser cache wist of een nieuw Signer token genereerd.`, + label: `achtergrond` }, - parity: { - label: 'Parity' + languages: { + hint: `de taal waarin deze interface wordt weergegeven`, + label: `Weergave taal` + }, + loglevels: `Kies hoeveel details er in het logboek worden bijgehouden.`, + modes: { + hint: `de synchronisatie modus van de Parity node`, + label: `Synchronisatie modus`, + mode_active: `Parity synchroniseert de chain continu`, + mode_dark: `Parity synchroniseert alleen als de RPC actief is`, + mode_offline: `Parity synchroniseert niet`, + mode_passive: `Parity synchroniseert in het begin. Daarna slaapt Parity en wordt regelmatig wakker voor synchronisatie` + }, + overview_0: `Pas de Parity node instellingen aan en kies de synchronisatie modus in dit menu.`, + label: `parity` }, - proxy: { - label: 'Proxy' + details_0: `In plaats van Parity te openen via het IP adres en poort-nummer, kun je toegang verkrijgen tot het .parity sub-domein door {homeProxy} te bezoeken. Om sub-domein gebaseerde routing in te stellen, dien je de proxy vermelding aan je browser proxy instellingen toe te voegen,`, + details_1: `Om je te helpen met het configureren van je proxy, zijn er instructies beschikbaar voor {windowsLink}, {macOSLink} or {ubuntuLink}.`, + details_macos: `macOS`, + details_ubuntu: `Ubuntu`, + details_windows: `Windows`, + overview_0: `Met de proxy instellingen heb je de mogelijkheid om via een makkelijk te onthouden adres toegang te verkrijgen tot Parity en alle onderliggende decentrale applicaties.`, + label: `proxy` }, - views: { - label: 'Weergaven', - accounts: { - label: 'Accounts' + description: `Een overzicht van alle aan deze Parity installatie verbonden accounts, inclusief geimporteerde accounts. Verzend transacties, ontvang inkomende transacties, berheer je saldo en financier je accounts.`, + label: `Accounts` }, - addresses: { - label: 'Adresboek' + description: `Een overzicht van alle contacten en adresboek items die door deze Parity installatie worden beheerd. Monitor en volg accounts waarbij je transactie details met slechts een muisklik kunt weergeven.`, + label: `Adresboek` }, - apps: { - label: 'Applicaties' + description: `Gedistibueerde applicaties die gebruik maken van het onderliggende Ethereum netwerk. Voeg applicaties toe, beheer je applicatie portfolio en maak gebruik van applicaties op het wereldwijde netwerk.`, + label: `Applicaties` }, - contracts: { - label: 'Contracten' + description: `Monitor, volg en maak gebruik van specifieke contracten die op het netwerk zijn gezet. Dit is een meer technisch gerichte omgeving, voornamelijk bedoeld voor geavanceerde gebruikers die de werking van bepaalde contracten goed begrijpen.`, + label: `Contracten` }, - - status: { - label: 'Status' + overview_0: `Beheer de beschikbare weergaven van deze interface en selecteer enkel de delen van de applicatie die voor jou van belang zijn.`, + overview_1: `Ben je een eind gebruiker? De standaard instellingen zijn geschikt voor zowel beginners als gevorderde gebruikers.`, + overview_2: `Ben je een ontwikkelaar? Voeg enkele functies toe om je contracten te beheren en gebruik te maken van gedecentraliseerde applicaties.`, + overview_3: `Ben je een miner of run je een grootschalige node? Voeg enkele functies toe om je alle informatie te geven die je nodig hebt om je node te monitoren.`, + settings: { + description: `Deze weergave. Hiermee kun je Parity aan passen in termen van opties, bediening en look en feel.`, + label: `Instellingen` }, - signer: { - label: 'Signer' + description: `Het beveiligde transactie beheergebied van de applicatie waar je elke uitgaande transactie die je hebt gemaakt met Parity evenals de transacties die in de wachtrij zijn geplaatst door gedistribueerde applicaties kan goedkeuren.`, + label: `Signer` }, - - settings: { - label: 'Instellingen' + status: { + description: `Volg hoe de Parity node zijn werk doet en je verbind met het netwerk en bekijk de logboeken van de momenteel draaiende node met mining details (indien geconfigureerd en ingeschakeld).`, + label: `Status` + }, + label: `weergaven`, + home: { + label: `Thuis` } - } + }, + label: `instellingen` }; diff --git a/js/src/i18n/nl/shapeshift.js b/js/src/i18n/nl/shapeshift.js new file mode 100755 index 0000000000000000000000000000000000000000..caa28adafeb85506c8b516e8870b73b1694cd776 --- /dev/null +++ b/js/src/i18n/nl/shapeshift.js @@ -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 . + +export default { + awaitingDepositStep: { + awaitingConfirmation: `Wachten tot bevestigd is dat je {typeSymbol} storting op het account van het wisselkantoor is aangekomen.`, + awaitingDeposit: `{shapeshiftLink} is aan het wachten op {typeSymbol} storting. Verzend de valuta vanuit je {typeSymbol} netwerk client naar -`, + minimumMaximum: `{minimum} minimum, {maximum} maximum` + }, + awaitingExchangeStep: { + awaitingCompletion: `Wachten op de voltooiing van het omwisselen van de valuta en op de overschrijving van de valuta naar je Parity account.`, + receivedInfo: `{shapeshiftLink} heeft een storting ontvangen van -` + }, + button: { + cancel: `Annuleer`, + done: `Sluit`, + shift: `Wissel valuta om` + }, + completedStep: { + completed: `{shapeshiftLink} heeft het omwisselen van de valuta voltooid.`, + parityFunds: `De saldo wijziging zal spoedig in je Parity client worden weergegeven.` + }, + errorStep: { + info: `Het omwisselen van de valuta via {shapeshiftLink} is mislukt door een fout bij het wisselkantoor. De ontvangen foutmelding van het wisselkantoor is als volgt:` + }, + optionsStep: { + noPairs: `Er is momenteel geen wisselkoers voor het valuta-paar beschikbaar om de transactie mee uit te voeren.`, + returnAddr: { + hint: `het retouradres voor wanneer het verzenden mislukt`, + label: `(optioneel) {coinSymbol} retouradres` + }, + terms: { + label: `Ik begrijp dat ShapeShift.io een dienst is van een derde partij en dat bij gebruik van deze service de overdracht van informatie en/of financiele middelen volledig buiten het beheer van Parity vallen` + }, + typeSelect: { + hint: `het type crypto valuta om te wisselen`, + label: `verzend naar account vanuit` + } + }, + price: { + minMax: `({minimum} minimum, {maximum} maximum)` + }, + title: { + completed: `voltooid`, + deposit: `wachten op storting`, + details: `details`, + error: `omwisselen mislukt`, + exchange: `wachten op omwisselen` + }, + warning: { + noPrice: `Geen prijs gevonden voor het gekozen type` + } +}; diff --git a/js/src/i18n/nl/tabBar.js b/js/src/i18n/nl/tabBar.js new file mode 100644 index 0000000000000000000000000000000000000000..ae1cb1c52c6de7b9e22a97a7245c33d4662a22ca --- /dev/null +++ b/js/src/i18n/nl/tabBar.js @@ -0,0 +1,21 @@ +// 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 . + +export default { + tooltip: { + overview: `navigeer tussen de verschillende onderdelen en weergaven in de applicatie, wissel tussen de account weergave, token weergave en gedistribueerde applicatie weergave` + } +}; diff --git a/js/src/i18n/nl/transfer.js b/js/src/i18n/nl/transfer.js new file mode 100644 index 0000000000000000000000000000000000000000..1370711b4327d0fb7c106facfb8be6c10784792d --- /dev/null +++ b/js/src/i18n/nl/transfer.js @@ -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 . + +export default { + advanced: { + data: { + hint: `de data om door te geven met de transactie`, + label: `transactie data` + } + }, + warning: { + wallet_spent_limit: `Deze transactie waarde is boven de toegestane dag limiet en zal moeten worden bevestigd door andere eigenaren.` + } +}; diff --git a/js/src/i18n/nl/txEditor.js b/js/src/i18n/nl/txEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..2cf0b2bbd45f086e9ab45f6c0beaced01aa37413 --- /dev/null +++ b/js/src/i18n/nl/txEditor.js @@ -0,0 +1,39 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + condition: { + block: { + hint: `Het minimum blok voor het verzenden`, + label: `Transactie verzend blok` + }, + blocknumber: `Verzend na bloknummer`, + date: { + hint: `De minimale datum voor het verzenden`, + label: `Transactie verzend datum` + }, + datetime: `Verzend na datum & tijdstip`, + label: `Conditie waarbij transactie activeert`, + none: `Geen condities`, + time: { + hint: `Het minimale tijdstip voor het verzenden`, + label: `Transactie verzend tijdstip` + } + }, + gas: { + info: `Je kunt de gas prijs kiezen op basis van de gas prijs van de transacties die recentelijk in de blokken werden opgenomen. Een lagere gas prijs betekend een goedkopere transactie. Een hogere gas prijs betekend dat je transactie sneller in een blok wordt opgenomen.` + } +}; diff --git a/js/src/i18n/nl/ui.js b/js/src/i18n/nl/ui.js new file mode 100644 index 0000000000000000000000000000000000000000..c0454e9f67202553f12f2c84b21e86d63cd92d7d --- /dev/null +++ b/js/src/i18n/nl/ui.js @@ -0,0 +1,84 @@ +// 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 . + +export default { + balance: { + none: `Er zijn geen tegoeden gekoppeld aan dit account` + }, + blockStatus: { + bestBlock: `{blockNumber} beste blok`, + syncStatus: `{currentBlock}/{highestBlock} synchroniseren`, + warpRestore: `{percentage}% warp restore`, + warpStatus: `, {percentage}% historic` + }, + confirmDialog: { + no: `nee`, + yes: `ja` + }, + identityName: { + null: `NUL`, + unnamed: `NAAMLOOS` + }, + passwordStrength: { + label: `wachtwoord sterkte` + }, + tooltips: { + button: { + done: `Klaar`, + next: `Volgende`, + skip: `Overslaan` + } + }, + txHash: { + confirmations: `{count} {value, plural, one {confirmation} other {confirmations}}`, + oog: `De transactie heeft misschien al zijn gas verbruikt. Probeer het opnieuw met meer gas.`, + posted: `De transactie is op het netwerk geplaatst met hash {hashLink}`, + waiting: `wachten op bevestigingen` + }, + verification: { + gatherData: { + accountHasRequested: { + false: `Je hebt nog geen verificatie aangevraagd voor dit account.`, + pending: `Aan het controleren of je verificatie hebt aangevraagd…`, + true: `Je hebt al verificatie aangevraagd voor dit account.` + }, + accountIsVerified: { + false: `Je account is nog niet geverifieerd`, + pending: `Aan het controleren of je account is geverifieerd…`, + true: `Je account is al geverifieerd.` + }, + email: { + hint: `de code zal naar dit adres worden verzonden`, + label: `e-mail adres` + }, + fee: `De extra vergoeding is {amount} ETH.`, + isAbleToRequest: { + pending: `Valideren van je invoer…` + }, + isServerRunning: { + false: `De verificatie server is niet actief.`, + pending: `Controleren of de verificatie server actief is…`, + true: `De verificatie server is actief.` + }, + nofee: `Er zijn geen extra kosten.`, + phoneNumber: { + hint: `De SMS zal naar dit nummer worden verstuurd`, + label: `telefoonnummer in internationaal formaat` + }, + termsOfService: `Ik ga akkoord met de voorwaarden en condities hieronder.` + } + } +}; diff --git a/js/src/i18n/nl/upgradeParity.js b/js/src/i18n/nl/upgradeParity.js new file mode 100644 index 0000000000000000000000000000000000000000..c057c9e7d92400c48407af91a91a4f28689ba67a --- /dev/null +++ b/js/src/i18n/nl/upgradeParity.js @@ -0,0 +1,44 @@ +// 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 . + +export default { + busy: `Parity wordt momenteel bijgewerkt naar versie {newversion}`, + button: { + close: `sluiten`, + done: `klaar`, + upgrade: `werk nu bij` + }, + completed: `Het bijwerken naar Parity {newversion} is succesvol voltooid.`, + consensus: { + capable: `Je huidige versie van Parity voldoet aan de netwerk vereisten.`, + capableUntil: `Je huidige versie van Parity voldoet aan de netwerk vereisten tot aan blok {blockNumber}`, + incapableSince: `Je huidige versie van Parity voldoet aan de netwerk vereisten vanaf blok {blockNumber}`, + unknown: `Je huidige versie van Parity voldoet aan de netwerk vereisten.` + }, + failed: `Het bijwerken naar Parity {newversion} gaf een fout en is mislukt.`, + info: { + upgrade: `Een nieuwe versie van Parity, version {newversion} is beschikbaar als upgrade vanaf je huidige versie {currentversion}` + }, + step: { + completed: `bijwerken voltooid`, + error: `fout`, + info: `nieuwe versie beschikbaar`, + updating: `Parity bijwerken` + }, + version: { + unknown: `onbekend` + } +}; diff --git a/js/src/i18n/nl/vaults.js b/js/src/i18n/nl/vaults.js new file mode 100644 index 0000000000000000000000000000000000000000..b16c6807eba631313e27f6bb193a997f3b042e29 --- /dev/null +++ b/js/src/i18n/nl/vaults.js @@ -0,0 +1,75 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + accounts: { + button: { + cancel: `Annuleer`, + execute: `Stel in` + }, + empty: `Er zitten geen accounts in deze kluis`, + title: `Beheer Kluis Accounts` + }, + button: { + accounts: `accounts`, + add: `Maak kluis`, + close: `sluit kluis`, + open: `open kluis` + }, + confirmClose: { + info: `Je staat op het punt op een kluis te sluiten. Alle aan deze kluis verbonden accounts zullen niet meer zichtbaar zijn na het voltooien van deze actie. Om deze accounts weer zichtbaar te maken dien je de kluis weer te openen.`, + title: `Sluit Kluis` + }, + confirmOpen: { + info: `Je staat op het punt om een kluis te openen. Na de bevestiging met je wachtwoord zullen alle aan deze kluis verbonden account zichtbaar worden. Wanneer je de kluis weer sluit zullen deze accounts weer onzichtbaar worden, tot je de kluis weer opent.`, + password: { + hint: `het wachtwoord wat je hebt gekozen bij het aanmaken van de kluis`, + label: `kluis wachtwoord` + }, + title: `Open Kluis` + }, + create: { + button: { + close: `sluit`, + vault: `maak kluis` + }, + description: { + hint: `een uitgebereide omschrijving voor de kluis` + }, + descriptions: { + label: `(optioneel) omschrijving` + }, + hint: { + hint: `(optioneel) een hint om je het wachtwoord te helpen herinneren`, + label: `wachtwoord hint` + }, + name: { + hint: `een naam voor de kluis`, + label: `kluis naam` + }, + password: { + hint: `een sterk en uniek wachtwoord`, + label: `wachtwoord` + }, + password2: { + hint: `verifieer je wachtwoord`, + label: `wachtwoord (herhaal)` + }, + title: `Maak een nieuwe kluis aan` + }, + empty: `Er zijn momenteel geen kluizen om weer tegeven.`, + title: `Kluis Beheer` +}; diff --git a/js/src/i18n/nl/walletSettings.js b/js/src/i18n/nl/walletSettings.js new file mode 100644 index 0000000000000000000000000000000000000000..ca0842944692f520a4338630b0d53714f3ac1013 --- /dev/null +++ b/js/src/i18n/nl/walletSettings.js @@ -0,0 +1,57 @@ +// 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 . + +export default { + changes: { + modificationString: `Om je wijzigingen door te voeren zullen + andere eigenaren deze zelfde wijzigingen moeten verzenden. Om het + makkelijk te maken kunnen ze deze string kopieren-plakken:`, + none: `Er zijn van deze Wallet geen instellingen gewijzigd.`, + overview: `Je staat op het punt om de volgende wijzignen te maken` + }, + edit: { + message: `Om de instellingen van dit contract de wijzigen zullen + minimaal {owners, number} {owners, plural, one {owner } other {owners }} precies dezelfde + wijzigingen moeten verzenden. Je kunt hier de wijzigingen in string-vorm plakken.` + }, + modifications: { + daylimit: { + hint: `hoeveelheid uit te geven ETH zonder bevestiging met wachtwoord`, + label: `wallet dag limiet` + }, + fromString: { + label: `wijzigingen` + }, + owners: { + label: `andere wallet eigenaren` + }, + required: { + hint: `vereiste aantal eigenaren om een transactie goed te keuren`, + label: `vereiste eigenaren` + }, + sender: { + hint: `verzend wijzigingen als deze eigenaar`, + label: `van account (wallet eigenaar)` + } + }, + rejected: { + busyStep: { + state: `De wallet instellingen zullen niet worden gewijzigd. Je kunt dit venster veilig sluiten.`, + title: `De wijzigingen zijn afgewezen.` + }, + title: `afgewezen` + } +}; diff --git a/js/src/i18n/nl/web.js b/js/src/i18n/nl/web.js new file mode 100644 index 0000000000000000000000000000000000000000..359d956157d8c44a2e33dcb22494fcd6b07121ad --- /dev/null +++ b/js/src/i18n/nl/web.js @@ -0,0 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default { + requestToken: `Aanvragen toegangs token...` +}; diff --git a/js/src/index.ejs b/js/src/index.ejs index 590070e8b0bfbd87d1b5d2fe91d67afbed81bf4f..ec8592b881d28c1b5583a0384c0e0284e34ecdeb 100644 --- a/js/src/index.ejs +++ b/js/src/index.ejs @@ -6,12 +6,15 @@ <%= htmlWebpackPlugin.options.title %>