diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6662caee6..2460678d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ linux-stable: - stable - triggers script: - - cargo build --release $CARGOFLAGS + - cargo build -j $(nproc) --release $CARGOFLAGS - strip target/release/parity - md5sum target/release/parity > parity.md5 - sh scripts/deb-build.sh amd64 @@ -52,7 +52,7 @@ linux-beta: - stable - triggers script: - - cargo build --release $CARGOFLAGS + - cargo build -j $(nproc) --release $CARGOFLAGS - strip target/release/parity tags: - rust @@ -71,7 +71,7 @@ linux-nightly: - stable - triggers script: - - cargo build --release $CARGOFLAGS + - cargo build -j $(nproc) --release $CARGOFLAGS - strip target/release/parity tags: - rust @@ -92,7 +92,7 @@ linux-centos: script: - export CXX="g++" - export CC="gcc" - - cargo build --release $CARGOFLAGS + - cargo build -j $(nproc) --release $CARGOFLAGS - strip target/release/parity - md5sum target/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key @@ -119,7 +119,7 @@ linux-i686: script: - export HOST_CC=gcc - export HOST_CXX=g++ - - cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS + - cargo build -j $(nproc) --target i686-unknown-linux-gnu --release $CARGOFLAGS - strip target/i686-unknown-linux-gnu/release/parity - md5sum target/i686-unknown-linux-gnu/release/parity > parity.md5 - sh scripts/deb-build.sh i386 @@ -161,7 +161,7 @@ linux-armv7: - echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config - echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config - cat .cargo/config - - cargo build --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS + - cargo build -j $(nproc) --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity - md5sum target/armv7-unknown-linux-gnueabihf/release/parity > parity.md5 - sh scripts/deb-build.sh armhf @@ -203,7 +203,7 @@ linux-arm: - echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config - echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config - cat .cargo/config - - cargo build --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS + - cargo build -j $(nproc) --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity - md5sum target/arm-unknown-linux-gnueabihf/release/parity > parity.md5 - sh scripts/deb-build.sh armhf @@ -245,7 +245,7 @@ linux-armv6: - echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config - echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config - cat .cargo/config - - cargo build --target arm-unknown-linux-gnueabi --release $CARGOFLAGS + - cargo build -j $(nproc) --target arm-unknown-linux-gnueabi --release $CARGOFLAGS - arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity - md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5 - aws configure set aws_access_key_id $s3_key @@ -280,7 +280,7 @@ linux-aarch64: - echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config - echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config - cat .cargo/config - - cargo build --target aarch64-unknown-linux-gnu --release $CARGOFLAGS + - cargo build -j $(nproc) --target aarch64-unknown-linux-gnu --release $CARGOFLAGS - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity - md5sum target/aarch64-unknown-linux-gnu/release/parity > parity.md5 - sh scripts/deb-build.sh arm64 @@ -312,8 +312,8 @@ darwin: - stable - triggers script: - - cargo build --release -p ethstore $CARGOFLAGS - - cargo build --release $CARGOFLAGS + - cargo build -j 8 --release -p ethstore #$CARGOFLAGS + - cargo build -j 8 --release #$CARGOFLAGS - rm -rf parity.md5 - md5sum target/release/parity > parity.md5 - packagesbuild -v mac/Parity.pkgproj @@ -350,7 +350,7 @@ windows: - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - - cargo build --release %CARGOFLAGS% + - cargo build -j 8 --release #%CARGOFLAGS% - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe @@ -401,7 +401,7 @@ test-darwin: - git submodule update --init --recursive script: - export RUST_BACKTRACE=1 - - ./test.sh $CARGOFLAGS --no-release + - ./test.sh $CARGOFLAGS tags: - osx allow_failure: true @@ -413,7 +413,7 @@ test-windows: - git submodule update --init --recursive script: - set RUST_BACKTRACE=1 - - cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release + - cargo -j 8 test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release tags: - rust-windows allow_failure: true @@ -428,7 +428,7 @@ test-rust-stable: script: - export RUST_BACKTRACE=1 - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS --no-release; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi tags: - rust - rust-stable @@ -457,7 +457,7 @@ test-rust-beta: script: - export RUST_BACKTRACE=1 - echo $JS_FILES_MODIFIED - - ./test.sh $CARGOFLAGS --no-release + - ./test.sh $CARGOFLAGS tags: - rust - rust-beta @@ -471,7 +471,7 @@ test-rust-nightly: - git submodule update --init --recursive script: - export RUST_BACKTRACE=1 - - ./test.sh $CARGOFLAGS --no-release + - ./test.sh $CARGOFLAGS tags: - rust - rust-nightly diff --git a/.travis.yml b/.travis.yml index 6f3fd9933..f1e02396b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ git: matrix: include: - rust: stable - env: RUN_TESTS="true" TEST_OPTIONS="--no-release" + env: RUN_TESTS="true" TEST_OPTIONS="" - rust: stable env: RUN_COVERAGE="true" - rust: stable @@ -71,8 +71,7 @@ install: script: - if [ "$RUN_TESTS" = "true" ]; then ./js/scripts/lint.sh && - ./js/scripts/test.sh && - ./test.sh $TEST_OPTIONS --verbose; + travis_wait 40 ./test.sh $TEST_OPTIONS; fi - if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi diff --git a/Cargo.lock b/Cargo.lock index 58f130b35..5795de427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,24 +10,23 @@ dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.5.0", "ethcore-dapps 1.5.0", - "ethcore-devtools 1.4.0", + "ethcore-devtools 1.5.0", "ethcore-hash-fetch 1.5.0", "ethcore-io 1.5.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", "ethcore-ipc-hypervisor 1.2.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-ipc-nano 1.5.0", "ethcore-ipc-tests 0.1.0", "ethcore-logger 1.5.0", "ethcore-rpc 1.5.0", "ethcore-signer 1.5.0", - "ethcore-stratum 1.4.0", + "ethcore-stratum 1.5.0", "ethcore-util 1.5.0", "ethsync 1.5.0", - "fdlimit 0.1.0", + "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.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)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -175,6 +174,15 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cookie" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam" version = "0.2.9" @@ -265,7 +273,7 @@ dependencies = [ [[package]] name = "ethash" -version = "1.4.0" +version = "1.5.0" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -283,20 +291,20 @@ dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.4.0", + "ethash 1.5.0", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.4.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-ipc-nano 1.5.0", "ethcore-util 1.5.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "evmjit 1.4.0", + "evmjit 1.5.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", + "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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -335,20 +343,20 @@ version = "1.5.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.4.0", + "ethcore-devtools 1.5.0", "ethcore-hash-fetch 1.5.0", "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", "fetch 0.1.0", - "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-http-server 6.1.1 (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-ui 1.4.0", + "parity-ui 1.5.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.8.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -362,7 +370,7 @@ dependencies = [ [[package]] name = "ethcore-devtools" -version = "1.4.0" +version = "1.5.0" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -392,9 +400,9 @@ dependencies = [ [[package]] name = "ethcore-ipc" -version = "1.4.0" +version = "1.5.0" dependencies = [ - "ethcore-devtools 1.4.0", + "ethcore-devtools 1.5.0", "ethcore-util 1.5.0", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -402,7 +410,7 @@ dependencies = [ [[package]] name = "ethcore-ipc-codegen" -version = "1.4.0" +version = "1.5.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)", @@ -415,9 +423,9 @@ dependencies = [ name = "ethcore-ipc-hypervisor" version = "1.2.0" dependencies = [ - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-ipc-nano 1.5.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.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -426,9 +434,9 @@ dependencies = [ [[package]] name = "ethcore-ipc-nano" -version = "1.4.0" +version = "1.5.0" dependencies = [ - "ethcore-ipc 1.4.0", + "ethcore-ipc 1.5.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)", @@ -438,10 +446,10 @@ dependencies = [ name = "ethcore-ipc-tests" version = "0.1.0" dependencies = [ - "ethcore-devtools 1.4.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-devtools 1.5.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-ipc-nano 1.5.0", "ethcore-util 1.5.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)", @@ -467,7 +475,7 @@ version = "1.5.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.4.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", "ethcore-util 1.5.0", "ethcrypto 0.1.0", @@ -491,11 +499,11 @@ name = "ethcore-rpc" version = "1.5.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.4.0", + "ethash 1.5.0", "ethcore 1.5.0", - "ethcore-devtools 1.4.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", - "ethcore-ipc 1.4.0", + "ethcore-ipc 1.5.0", "ethcore-util 1.5.0", "ethcrypto 0.1.0", "ethjson 0.1.0", @@ -503,9 +511,9 @@ dependencies = [ "ethstore 0.1.0", "ethsync 1.5.0", "fetch 0.1.0", - "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)", + "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -522,14 +530,14 @@ version = "1.5.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.4.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", "ethcore-rpc 1.5.0", "ethcore-util 1.5.0", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 4.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.4.0", + "parity-ui 1.5.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)", @@ -537,16 +545,16 @@ dependencies = [ [[package]] name = "ethcore-stratum" -version = "1.4.0" +version = "1.5.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.4.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-devtools 1.5.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-ipc-nano 1.5.0", "ethcore-util 1.5.0", - "json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-tcp-server 0.1.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)", @@ -565,7 +573,7 @@ dependencies = [ "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.4.0", + "ethcore-devtools 1.5.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -653,9 +661,9 @@ dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.5.0", "ethcore-io 1.5.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", - "ethcore-ipc-nano 1.4.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-ipc-nano 1.5.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -669,14 +677,15 @@ dependencies = [ [[package]] name = "evmjit" -version = "1.4.0" +version = "1.5.0" dependencies = [ "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "fdlimit" -version = "0.1.0" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -686,7 +695,7 @@ name = "fetch" version = "0.1.0" dependencies = [ "https-fetch 0.1.0", - "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "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)", ] @@ -748,27 +757,6 @@ dependencies = [ "rustls 0.1.2 (git+https://github.com/ctz/rustls)", ] -[[package]] -name = "hyper" -version = "0.9.4" -source = "git+https://github.com/ethcore/hyper#9e346c1d4bc30cd4142dea9d8a0b117d30858ca4" -dependencies = [ - "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "language-tags 0.2.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)", - "rotor 0.6.3 (git+https://github.com/ethcore/rotor)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "typeable 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)", - "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "hyper" version = "0.9.10" @@ -789,6 +777,25 @@ dependencies = [ "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hyper" +version = "0.10.0-a.0" +source = "git+https://github.com/ethcore/hyper#7d4f7fa0baddcb2b0c523f7c05855d67de94fe88" +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)", + "language-tags 0.2.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)", + "rotor 0.6.3 (git+https://github.com/ethcore/rotor)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.0" @@ -831,39 +838,10 @@ name = "itoa" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "json-ipc-server" -version = "0.2.4" -source = "git+https://github.com/ethcore/json-ipc-server.git#4642cd03ec1d23db89df80d22d5a88e7364ab885" -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 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "json-tcp-server" -version = "0.1.0" -source = "git+https://github.com/ethcore/json-tcp-server#c2858522274ae56042472bb5d22845a1b85e5338" -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 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "jsonrpc-core" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "4.0.0" +source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -875,14 +853,44 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "6.1.1" -source = "git+https://github.com/ethcore/jsonrpc-http-server.git#cd6d4cb37d672cc3057aecd0692876f9e85f3ba5" +source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" dependencies = [ - "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-ipc-server" +version = "0.2.4" +source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" +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 4.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)", + "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jsonrpc-tcp-server" +version = "0.1.0" +source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" +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 4.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)", + "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1246,7 +1254,7 @@ dependencies = [ [[package]] name = "parity-ui" -version = "1.4.0" +version = "1.5.0" dependencies = [ "parity-ui-dev 1.4.0", "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", @@ -1263,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#cb6836dddf8c9951e056283dcd9e105e97923d07" +source = "git+https://github.com/ethcore/js-precompiled.git#1bf7160f6c8f25353d790dbd0935560d3d395727" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1503,11 +1511,12 @@ dependencies = [ [[package]] name = "rotor" version = "0.6.3" -source = "git+https://github.com/ethcore/rotor#e63d45137b2eb66d1e085a7c6321a5db8b187576" +source = "git+https://github.com/ethcore/rotor#c1a2dd0046c5ea2517a5b637fca8ee2e77021e82" dependencies = [ "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)", + "mio 0.6.1 (git+https://github.com/ethcore/mio)", "quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2008,6 +2017,7 @@ dependencies = [ "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" +"checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" "checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" "checksum ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)" = "" "checksum daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "271ec51b7e0bee92f0d04601422c73eb76ececf197026711c97ad25038a010cf" @@ -2018,6 +2028,7 @@ dependencies = [ "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" +"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" @@ -2025,17 +2036,17 @@ dependencies = [ "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "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.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "eb27e8a3e8f17ac43ffa41bbda9cf5ad3f9f13ef66fa4873409d4902310275f7" -"checksum hyper 0.9.4 (git+https://github.com/ethcore/hyper)" = "" "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 isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" -"checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "" -"checksum json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)" = "" -"checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414" -"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)" = "" +"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-tcp-server 0.1.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" diff --git a/Cargo.toml b/Cargo.toml index 3808cce95..65bb0dbc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,7 @@ serde = "0.8.0" serde_json = "0.8.0" hyper = { version = "0.9", default-features = false } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } -json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } -fdlimit = { path = "util/fdlimit" } +fdlimit = "0.1" ethcore = { path = "ethcore" } ethcore-util = { path = "util" } ethsync = { path = "sync" } @@ -89,4 +88,4 @@ name = "parity" [profile.release] debug = false lto = false - +panic = "abort" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 6be30884a..bbab8420e 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -12,8 +12,8 @@ build = "build.rs" rand = "0.3.14" log = "0.3" env_logger = "0.3" -jsonrpc-core = "3.0" -jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } +jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } +jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } unicase = "1.3" url = "1.0" diff --git a/dapps/js-glue/Cargo.toml b/dapps/js-glue/Cargo.toml index b85122e90..827c67ef5 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.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore ) -> Option { _ => None, } }, - uri::RequestUri::AbsolutePath(ref path) => { + uri::RequestUri::AbsolutePath { ref path, .. } => { // Attempt to prepend the Host header (mandatory in HTTP/1.1) let url_string = match req.headers().get::() { Some(ref host) => { diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 7c7ea0a86..9bb3be4a7 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -266,7 +266,11 @@ impl Server { #[cfg(test)] /// Returns address that this server is bound to. pub fn addr(&self) -> &SocketAddr { - self.server.as_ref().expect("server is always Some at the start; it's consumed only when object is dropped; qed").addr() + self.server.as_ref() + .expect("server is always Some at the start; it's consumed only when object is dropped; qed") + .addrs() + .first() + .expect("You cannot start the server without binding to at least one address; qed") } } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index 74eabf917..1494a04c7 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::io::Write; use time::{self, Duration}; use hyper::header; @@ -126,7 +125,7 @@ impl PageHandler { impl server::Handler for PageHandler { fn on_request(&mut self, req: server::Request) -> Next { self.file = match *req.uri() { - RequestUri::AbsolutePath(ref path) => { + RequestUri::AbsolutePath { ref path, .. } => { self.app.file(&self.extract_path(path)) }, RequestUri::AbsoluteUri(ref url) => { diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index 625bfc269..c47777bee 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -16,13 +16,14 @@ use std::sync::{Arc, Mutex}; use hyper; -use jsonrpc_core::IoHandler; -use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin}; + +use jsonrpc_core::{IoHandler, ResponseHandler, Request, Response}; +use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin, RpcHandler}; use endpoint::{Endpoint, EndpointPath, Handler}; pub fn rpc(handler: Arc, panic_handler: Arc () + Send>>>>) -> Box { Box::new(RpcEndpoint { - handler: handler, + handler: Arc::new(RpcMiddleware::new(handler)), panic_handler: panic_handler, cors_domain: None, // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. @@ -31,7 +32,7 @@ pub fn rpc(handler: Arc, panic_handler: Arc } struct RpcEndpoint { - handler: Arc, + handler: Arc, panic_handler: Arc () + Send>>>>, cors_domain: Option>, allowed_hosts: Option>, @@ -49,3 +50,86 @@ impl Endpoint for RpcEndpoint { )) } } + +const MIDDLEWARE_METHOD: &'static str = "eth_accounts"; + +struct RpcMiddleware { + handler: Arc, +} + +impl RpcMiddleware { + fn new(handler: Arc) -> Self { + RpcMiddleware { + handler: handler, + } + } + + /// Appends additional parameter for specific calls. + fn augment_request(&self, request: &mut Request, meta: Option) { + use jsonrpc_core::{Call, Params, to_value}; + + fn augment_call(call: &mut Call, meta: Option<&Meta>) { + match (call, meta) { + (&mut Call::MethodCall(ref mut method_call), Some(meta)) if &method_call.method == MIDDLEWARE_METHOD => { + let session = to_value(&meta.app_id); + + let params = match method_call.params { + Some(Params::Array(ref vec)) if vec.len() == 0 => Some(Params::Array(vec![session])), + // invalid params otherwise + _ => None, + }; + + method_call.params = params; + }, + _ => {} + } + } + + match *request { + Request::Single(ref mut call) => augment_call(call, meta.as_ref()), + Request::Batch(ref mut vec) => { + for mut call in vec { + augment_call(call, meta.as_ref()) + } + }, + } + } +} + +#[derive(Debug)] +struct Meta { + app_id: String, +} + +impl RpcHandler for RpcMiddleware { + type Metadata = Meta; + + fn read_metadata(&self, request: &hyper::server::Request) -> Option { + request.headers().get::() + .and_then(|referer| hyper::Url::parse(referer).ok()) + .and_then(|url| { + url.path_segments() + .and_then(|mut split| split.next()) + .map(|app_id| Meta { + app_id: app_id.to_owned(), + }) + }) + } + + fn handle_request(&self, request_str: &str, response_handler: H, meta: Option) where + H: ResponseHandler, Option> + 'static + { + let handler = IoHandler::convert_handler(response_handler); + let request = IoHandler::read_request(request_str); + trace!(target: "rpc", "Request metadata: {:?}", meta); + + match request { + Ok(mut request) => { + self.augment_request(&mut request, meta); + self.handler.request_handler().handle_request(request, handler, None) + }, + Err(error) => handler.send(Some(Response::from(error))), + } + } +} + diff --git a/dapps/ui/Cargo.toml b/dapps/ui/Cargo.toml index e71782dfa..de8207b88 100644 --- a/dapps/ui/Cargo.toml +++ b/dapps/ui/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Parity UI" homepage = "http://ethcore.io" license = "GPL-3.0" name = "parity-ui" -version = "1.4.0" +version = "1.5.0" authors = ["Ethcore "] [build-dependencies] diff --git a/db/Cargo.toml b/db/Cargo.toml index 2b4a19892..9642ed882 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Database" homepage = "http://ethcore.io" license = "GPL-3.0" name = "ethcore-db" -version = "1.4.0" +version = "1.5.0" authors = ["Ethcore "] build = "build.rs" diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 77b05b4cc..3b648c450 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools" homepage = "http://ethcore.io" license = "GPL-3.0" name = "ethcore-devtools" -version = "1.4.0" +version = "1.5.0" authors = ["Ethcore "] [dependencies] diff --git a/ethash/Cargo.toml b/ethash/Cargo.toml index d2fb37d94..bf1ba990e 100644 --- a/ethash/Cargo.toml +++ b/ethash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethash" -version = "1.4.0" +version = "1.5.0" authors = ["arkpar "] +build = "build.rs" + +[build-dependencies] +"ethcore-ipc-codegen" = { path = "../../ipc/codegen" } [dependencies] log = "0.3" @@ -12,5 +16,6 @@ ethcore = { path = ".." } ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } +ethcore-ipc = { path = "../../ipc/rpc" } rlp = { path = "../../util/rlp" } time = "0.1" \ No newline at end of file diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs new file mode 100644 index 000000000..cff92a011 --- /dev/null +++ b/ethcore/light/build.rs @@ -0,0 +1,21 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); +} diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index e3b5745b2..8a5c43c48 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -101,7 +101,7 @@ impl Provider for Client { Vec::new() } - fn code(&self, _req: request::ContractCodes) -> Vec { + fn contract_code(&self, _req: request::ContractCodes) -> Vec { Vec::new() } diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 07e6833a7..e150f4ee5 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -28,20 +28,25 @@ //! It starts by performing a header-only sync, verifying random samples //! of members of the chain to varying degrees. -// TODO: remove when integrating with parity. +// TODO: remove when integrating with the rest of parity. #![allow(dead_code)] pub mod client; pub mod net; pub mod provider; -pub mod request; +mod types; + +pub use self::provider::Provider; +pub use types::les_request as request; + +#[macro_use] +extern crate log; + +extern crate ethcore; extern crate ethcore_util as util; extern crate ethcore_network as network; extern crate ethcore_io as io; -extern crate ethcore; +extern crate ethcore_ipc as ipc; extern crate rlp; -extern crate time; - -#[macro_use] -extern crate log; \ No newline at end of file +extern crate time; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index b7bd30f82..6730c71a7 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -206,6 +206,39 @@ impl FlowParams { cost.0 + (amount * cost.1) } + /// Compute the maximum number of costs of a specific kind which can be made + /// with the given buffer. + /// 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 { + use util::Uint; + use std::usize; + + let cost = match kind { + request::Kind::Headers => &self.costs.headers, + request::Kind::Bodies => &self.costs.bodies, + request::Kind::Receipts => &self.costs.receipts, + request::Kind::StateProofs => &self.costs.state_proofs, + request::Kind::Codes => &self.costs.contract_codes, + request::Kind::HeaderProofs => &self.costs.header_proofs, + }; + + let start = buffer.current(); + + if start <= cost.0 { + return 0; + } else if cost.1 == U256::zero() { + return usize::MAX; + } + + let max = (start - cost.0) / cost.1; + if max >= usize::MAX.into() { + usize::MAX + } else { + max.as_u64() as usize + } + } + /// Create initial buffer parameter. pub fn create_buffer(&self) -> Buffer { Buffer { @@ -228,6 +261,16 @@ impl FlowParams { buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); } + + /// Refund some buffer which was previously deducted. + /// Does not update the recharge timestamp. + pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) { + buf.estimate = buf.estimate + refund_amount; + + if buf.estimate > self.limit { + buf.estimate = self.limit + } + } } #[cfg(test)] diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index e15bd50d3..0855cdeb8 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -52,6 +52,8 @@ pub enum Error { UnexpectedHandshake, /// Peer on wrong network (wrong NetworkId or genesis hash) WrongNetwork, + /// Unknown peer. + UnknownPeer, } impl Error { @@ -64,6 +66,7 @@ impl Error { Error::UnrecognizedPacket(_) => Punishment::Disconnect, Error::UnexpectedHandshake => Punishment::Disconnect, Error::WrongNetwork => Punishment::Disable, + Error::UnknownPeer => Punishment::Disconnect, } } } @@ -89,6 +92,7 @@ impl fmt::Display for Error { Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), Error::WrongNetwork => write!(f, "Wrong network"), + Error::UnknownPeer => write!(f, "unknown peer"), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index e72ce4bb2..a1b3b30b0 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -19,27 +19,28 @@ //! This uses a "Provider" to answer requests. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +use ethcore::transaction::SignedTransaction; use io::TimerToken; use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::RwLock; +use util::{Mutex, RwLock, U256}; +use time::SteadyTime; use std::collections::{HashMap, HashSet}; -use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; use self::error::{Error, Punishment}; -use self::status::{Status, Capabilities}; mod buffer_flow; mod error; mod status; -pub use self::status::Announcement; +pub use self::status::{Status, Capabilities, Announcement, NetworkId}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -86,6 +87,10 @@ mod packet { pub const HEADER_PROOFS: u8 = 0x0e; } +/// A request id. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ReqId(usize); + // A pending peer: one we've sent our status to but // may not have received one for. struct PendingPeer { @@ -103,32 +108,162 @@ struct Peer { sent_head: H256, // last head we've given them. } +impl Peer { + // check the maximum cost of a request, returning an error if there's + // not enough buffer 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); + + let max_cost = flow_params.compute_cost(kind, max); + try!(self.local_buffer.deduct_cost(max_cost)); + Ok(max_cost) + } + + // refund buffer for a request. returns new buffer amount. + fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 { + flow_params.refund(&mut self.local_buffer, amount); + + self.local_buffer.current() + } + + // recharge remote buffer with remote flow params. + fn recharge_remote(&mut self) { + let flow = &mut self.remote_flow; + flow.recharge(&mut self.remote_buffer); + } +} + +/// An LES event handler. +pub trait Handler: Send + Sync { + /// Called when a peer connects. + fn on_connect(&self, _id: PeerId, _status: &Status, _capabilities: &Capabilities) { } + /// Called when a peer disconnects + fn on_disconnect(&self, _id: PeerId) { } + /// Called when a peer makes an announcement. + fn on_announcement(&self, _id: PeerId, _announcement: &Announcement) { } + /// Called when a peer requests relay of some transactions. + fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } +} + +// a request and the time it was made. +struct Requested { + request: Request, + timestamp: SteadyTime, +} + +/// Protocol parameters. +pub struct Params { + /// Genesis hash. + pub genesis_hash: H256, + /// Network id. + pub network_id: NetworkId, + /// Buffer flow parameters. + pub flow_params: FlowParams, + /// Initial capabilities. + pub capabilities: Capabilities, +} + /// This is an implementation of the light ethereum network protocol, abstracted /// over a `Provider` of data and a p2p network. /// /// This is simply designed for request-response purposes. Higher level uses /// of the protocol, such as synchronization, will function as wrappers around /// this system. +// +// LOCK ORDER: +// Locks must be acquired in the order declared, and when holding a read lock +// on the peers, only one peer may be held at a time. pub struct LightProtocol { provider: Box, genesis_hash: H256, - network_id: status::NetworkId, + network_id: NetworkId, pending_peers: RwLock>, - peers: RwLock>, - pending_requests: RwLock>, + peers: RwLock>>, + pending_requests: RwLock>, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. + handlers: Vec>, req_id: AtomicUsize, } impl LightProtocol { + /// Create a new instance of the protocol manager. + pub fn new(provider: Box, params: Params) -> Self { + LightProtocol { + provider: provider, + genesis_hash: params.genesis_hash, + network_id: params.network_id, + pending_peers: RwLock::new(HashMap::new()), + peers: RwLock::new(HashMap::new()), + pending_requests: RwLock::new(HashMap::new()), + capabilities: RwLock::new(params.capabilities), + flow_params: params.flow_params, + handlers: Vec::new(), + req_id: AtomicUsize::new(0), + } + } + + /// Check the maximum amount of requests of a specific type + /// which a peer would be able to serve. + pub fn max_requests(&self, peer: PeerId, kind: request::Kind) -> Option { + self.peers.read().get(&peer).map(|peer| { + let mut peer = peer.lock(); + peer.recharge_remote(); + peer.remote_flow.max_amount(&peer.remote_buffer, kind) + }) + } + + /// Make a request to a peer. + /// + /// Fails on: nonexistent peer, network error, + /// insufficient buffer. 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: &NetworkContext, peer_id: &PeerId, request: Request) -> Result { + let peers = self.peers.read(); + let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); + let mut peer = peer.lock(); + + peer.recharge_remote(); + + let max = peer.remote_flow.compute_cost(request.kind(), request.amount()); + try!(peer.remote_buffer.deduct_cost(max)); + + let req_id = self.req_id.fetch_add(1, Ordering::SeqCst); + let packet_data = encode_request(&request, req_id); + + let packet_id = match request.kind() { + request::Kind::Headers => packet::GET_BLOCK_HEADERS, + request::Kind::Bodies => packet::GET_BLOCK_BODIES, + request::Kind::Receipts => packet::GET_RECEIPTS, + request::Kind::StateProofs => packet::GET_PROOFS, + request::Kind::Codes => packet::GET_CONTRACT_CODES, + request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, + }; + + try!(io.send(*peer_id, packet_id, packet_data)); + + peer.current_asking.insert(req_id); + self.pending_requests.write().insert(req_id, Requested { + request: request, + timestamp: SteadyTime::now(), + }); + + Ok(ReqId(req_id)) + } + /// Make an announcement of new chain head and capabilities to all peers. /// The announcement is expected to be valid. - pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { + pub fn make_announcement(&self, io: &NetworkContext, mut announcement: Announcement) { let mut reorgs_map = HashMap::new(); + // update stored capabilities + self.capabilities.write().update_from(&announcement); + // calculate reorg info and send packets - for (peer_id, peer_info) in self.peers.write().iter_mut() { + for (peer_id, peer_info) in self.peers.read().iter() { + let mut peer_info = peer_info.lock(); let reorg_depth = reorgs_map.entry(peer_info.sent_head) .or_insert_with(|| { match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { @@ -151,6 +286,14 @@ impl LightProtocol { } } } + + /// Add an event handler. + /// Ownership will be transferred to the protocol structure, + /// and the handler will be kept alive as long as it is. + /// These are intended to be added at the beginning of the + pub fn add_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } } impl LightProtocol { @@ -173,7 +316,11 @@ impl LightProtocol { fn on_disconnect(&self, peer: PeerId) { // TODO: reassign all requests assigned to this peer. self.pending_peers.write().remove(&peer); - self.peers.write().remove(&peer); + if self.peers.write().remove(&peer).is_some() { + for handler in &self.handlers { + handler.on_disconnect(peer) + } + } } // send status to a peer. @@ -219,15 +366,19 @@ impl LightProtocol { return Err(Error::WrongNetwork); } - self.peers.write().insert(*peer, Peer { + self.peers.write().insert(*peer, Mutex::new(Peer { local_buffer: self.flow_params.create_buffer(), remote_buffer: flow_params.create_buffer(), current_asking: HashSet::new(), - status: status, - capabilities: capabilities, + status: status.clone(), + capabilities: capabilities.clone(), remote_flow: flow_params, sent_head: pending.sent_head, - }); + })); + + for handler in &self.handlers { + handler.on_connect(*peer, &status, &capabilities) + } Ok(()) } @@ -240,13 +391,15 @@ impl LightProtocol { } let announcement = try!(status::parse_announcement(data)); - let mut peers = self.peers.write(); + let peers = self.peers.read(); - let peer_info = match peers.get_mut(peer) { + let peer_info = match peers.get(peer) { Some(info) => info, None => return Ok(()), }; + let mut peer_info = peer_info.lock(); + // update status. { // TODO: punish peer if they've moved backwards. @@ -259,15 +412,11 @@ impl LightProtocol { } // update capabilities. - { - let caps = &mut peer_info.capabilities; - caps.serve_headers = caps.serve_headers || announcement.serve_headers; - caps.serve_state_since = caps.serve_state_since.or(announcement.serve_state_since); - caps.serve_chain_since = caps.serve_chain_since.or(announcement.serve_chain_since); - caps.tx_relay = caps.tx_relay || announcement.tx_relay; - } + peer_info.capabilities.update_from(&announcement); - // TODO: notify listeners if new best block. + for handler in &self.handlers { + handler.on_announcement(*peer, &announcement); + } Ok(()) } @@ -276,45 +425,39 @@ impl LightProtocol { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: usize = 512; - let mut present_buffer = match self.peers.read().get(peer) { - Some(peer) => peer.local_buffer.clone(), + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, None => { - debug!(target: "les", "Ignoring announcement from unknown peer"); + debug!(target: "les", "Ignoring request from unknown peer"); return Ok(()) } }; - self.flow_params.recharge(&mut present_buffer); + let mut peer = peer.lock(); + let req_id: u64 = try!(data.val_at(0)); + let block = { + let rlp = try!(data.at(1)); + (try!(rlp.val_at(0)), try!(rlp.val_at(1))) + }; + let req = request::Headers { - block: { - let rlp = try!(data.at(1)); - (try!(rlp.val_at(0)), try!(rlp.val_at(1))) - }, + block_num: block.0, + block_hash: block.1, max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), skip: try!(data.val_at(3)), reverse: try!(data.val_at(4)), }; - let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); - try!(present_buffer.deduct_cost(max_cost)); + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max)); let response = self.provider.block_headers(req); 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 = match self.peers.write().get_mut(peer) { - Some(peer) => { - self.flow_params.recharge(&mut peer.local_buffer); - try!(peer.local_buffer.deduct_cost(actual_cost)); - peer.local_buffer.current() - } - None => { - debug!(target: "les", "peer disconnected during serving of request."); - return Ok(()) - } - }; - + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_HEADERS, { let mut stream = RlpStream::new_list(response.len() + 2); stream.append(&req_id).append(&cur_buffer); @@ -336,39 +479,30 @@ impl LightProtocol { fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 256; - let mut present_buffer = match self.peers.read().get(peer) { - Some(peer) => peer.local_buffer.clone(), + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, None => { - debug!(target: "les", "Ignoring announcement from unknown peer"); + debug!(target: "les", "Ignoring request from unknown peer"); return Ok(()) } }; + let mut peer = peer.lock(); - self.flow_params.recharge(&mut present_buffer); let req_id: u64 = try!(data.val_at(0)); let req = request::Bodies { block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) }; - let max_cost = self.flow_params.compute_cost(request::Kind::Bodies, req.block_hashes.len()); - try!(present_buffer.deduct_cost(max_cost)); + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len())); let response = self.provider.block_bodies(req); let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); 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 = match self.peers.write().get_mut(peer) { - Some(peer) => { - self.flow_params.recharge(&mut peer.local_buffer); - try!(peer.local_buffer.deduct_cost(actual_cost)); - peer.local_buffer.current() - } - None => { - debug!(target: "les", "peer disconnected during serving of request."); - return Ok(()) - } - }; + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_BODIES, { let mut stream = RlpStream::new_list(response.len() + 2); @@ -388,8 +522,44 @@ impl LightProtocol { } // Handle a request for receipts. - fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_RECEIPTS: usize = 256; + + 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 = try!(data.val_at(0)); + + let req = request::Receipts { + block_hashes: try!(data.iter().skip(1).take(MAX_RECEIPTS).map(|x| x.as_val()).collect()) + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len())); + + let response = self.provider.receipts(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + 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); + + io.respond(packet::RECEIPTS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for receipts in response { + stream.append_raw(&receipts, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for receipts. @@ -398,8 +568,55 @@ impl LightProtocol { } // Handle a request for proofs. - fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_PROOFS: usize = 128; + + 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 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { + Ok(request::StateProof { + block: try!(x.val_at(0)), + key1: try!(x.val_at(1)), + key2: if try!(x.at(2)).is_empty() { None } else { Some(try!(x.val_at(2))) }, + from_level: try!(x.val_at(3)), + }) + }).collect(); + + request::StateProofs { + requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::StateProofs, req.requests.len())); + + let response = self.provider.proofs(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + 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); + + io.respond(packet::PROOFS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for proof in response { + stream.append_raw(&proof, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for proofs. @@ -408,8 +625,53 @@ impl LightProtocol { } // Handle a request for contract code. - fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_CODES: usize = 256; + + 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 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_CODES).map(|x| { + Ok(request::ContractCode { + block_hash: try!(x.val_at(0)), + account_key: try!(x.val_at(1)), + }) + }).collect(); + + request::ContractCodes { + code_requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Codes, req.code_requests.len())); + + let response = self.provider.contract_code(req); + let response_len = response.iter().filter(|x| !x.is_empty()).count(); + 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); + + io.respond(packet::CONTRACT_CODES, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for code in response { + stream.append_raw(&code, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for contract code. @@ -418,8 +680,54 @@ impl LightProtocol { } // Handle a request for header proofs - fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_PROOFS: usize = 256; + + 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 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { + Ok(request::HeaderProof { + cht_number: try!(x.val_at(0)), + block_number: try!(x.val_at(1)), + from_level: try!(x.val_at(2)), + }) + }).collect(); + + request::HeaderProofs { + requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::HeaderProofs, req.requests.len())); + + let response = self.provider.header_proofs(req); + let response_len = response.iter().filter(|x| &x[..] != ::rlp::EMPTY_LIST_RLP).count(); + 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); + + io.respond(packet::HEADER_PROOFS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for proof in response { + stream.append_raw(&proof, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for header proofs @@ -428,8 +736,18 @@ impl LightProtocol { } // Receive a set of transactions to relay. - fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn relay_transactions(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + const MAX_TRANSACTIONS: usize = 256; + + let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); + + debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); + + for handler in &self.handlers { + handler.on_transactions(*peer, &txs); + } + + Ok(()) } } @@ -464,7 +782,7 @@ impl NetworkProtocolHandler for LightProtocol { packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, rlp), other => { Err(Error::UnrecognizedPacket(other)) @@ -503,4 +821,86 @@ impl NetworkProtocolHandler for LightProtocol { _ => warn!(target: "les", "received timeout on unknown token {}", timer), } } +} + +// Helper for encoding the request to RLP with the given ID. +fn encode_request(req: &Request, req_id: usize) -> Vec { + match *req { + Request::Headers(ref headers) => { + let mut stream = RlpStream::new_list(5); + stream + .append(&req_id) + .begin_list(2) + .append(&headers.block_num) + .append(&headers.block_hash) + .append(&headers.max) + .append(&headers.skip) + .append(&headers.reverse); + stream.out() + } + Request::Bodies(ref request) => { + let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); + stream.append(&req_id); + + for hash in &request.block_hashes { + stream.append(hash); + } + + stream.out() + } + Request::Receipts(ref request) => { + let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); + stream.append(&req_id); + + for hash in &request.block_hashes { + stream.append(hash); + } + + stream.out() + } + Request::StateProofs(ref request) => { + let mut stream = RlpStream::new_list(request.requests.len() + 1); + stream.append(&req_id); + + for proof_req in &request.requests { + stream.begin_list(4) + .append(&proof_req.block) + .append(&proof_req.key1); + + match proof_req.key2 { + Some(ref key2) => stream.append(key2), + None => stream.append_empty_data(), + }; + + stream.append(&proof_req.from_level); + } + + stream.out() + } + Request::Codes(ref request) => { + let mut stream = RlpStream::new_list(request.code_requests.len() + 1); + stream.append(&req_id); + + for code_req in &request.code_requests { + stream.begin_list(2) + .append(&code_req.block_hash) + .append(&code_req.account_key); + } + + stream.out() + } + Request::HeaderProofs(ref request) => { + let mut stream = RlpStream::new_list(request.requests.len() + 1); + stream.append(&req_id); + + for proof_req in &request.requests { + stream.begin_list(3) + .append(&proof_req.cht_number) + .append(&proof_req.block_number) + .append(&proof_req.from_level); + } + + stream.out() + } + } } \ No newline at end of file diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 5aaea9f3a..2c0c5f79a 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -183,8 +183,10 @@ pub struct Capabilities { /// Whether this peer can serve headers pub serve_headers: bool, /// Earliest block number it can serve block/receipt requests for. + /// `None` means no requests will be servable. pub serve_chain_since: Option, /// Earliest block number it can serve state requests for. + /// `None` means no requests will be servable. pub serve_state_since: Option, /// Whether it can relay transactions to the eth network. pub tx_relay: bool, @@ -201,6 +203,16 @@ impl Default for Capabilities { } } +impl Capabilities { + /// Update the capabilities from an announcement. + pub fn update_from(&mut self, announcement: &Announcement) { + self.serve_headers = self.serve_headers || announcement.serve_headers; + self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since); + self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since); + self.tx_relay = self.tx_relay || announcement.tx_relay; + } +} + /// Attempt to parse a handshake message into its three parts: /// - chain status /// - serving capabilities diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index b1625f95f..264df0397 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -17,8 +17,11 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. -use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; +use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; +use ethcore::transaction::SignedTransaction; +use ethcore::ids::BlockID; + use util::{Bytes, H256}; use request; @@ -26,7 +29,8 @@ use request; /// Defines the operations that a provider for `LES` must fulfill. /// /// These are defined at [1], but may be subject to change. -/// Requests which can't be fulfilled should return an empty RLP list. +/// Requests which can't be fulfilled should return either an empty RLP list +/// or empty vector where appropriate. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) pub trait Provider: Send + Sync { @@ -34,9 +38,12 @@ pub trait Provider: Send + Sync { fn chain_info(&self) -> BlockChainInfo; /// Find the depth of a common ancestor between two blocks. + /// If either block is unknown or an ancestor can't be found + /// then return `None`. fn reorg_depth(&self, a: &H256, b: &H256) -> Option; - /// Earliest state. + /// Earliest block where state queries are available. + /// If `None`, no state queries are servable. fn earliest_state(&self) -> Option; /// Provide a list of headers starting at the requested block, @@ -57,15 +64,105 @@ pub trait Provider: Send + Sync { /// Provide a set of merkle proofs, as requested. Each request is a /// block hash and request parameters. /// - /// Returns a vector to RLP-encoded lists satisfying the requests. + /// Returns a vector of RLP-encoded lists satisfying the requests. fn proofs(&self, req: request::StateProofs) -> Vec; /// Provide contract code for the specified (block_hash, account_hash) pairs. - fn code(&self, req: request::ContractCodes) -> Vec; + /// Each item in the resulting vector is either the raw bytecode or empty. + fn contract_code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. fn pending_transactions(&self) -> Vec; +} + +// Implementation of a light client data provider for a client. +impl Provider for T { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route(a, b).map(|route| route.index as u64) + } + + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) + } + + fn block_headers(&self, req: request::Headers) -> Vec { + let best_num = self.chain_info().best_block_number; + let start_num = req.block_num; + + match self.block_hash(BlockID::Number(req.block_num)) { + Some(hash) if hash == req.block_hash => {} + _=> { + trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); + return vec![] + } + } + + (0u64..req.max as u64) + .map(|x: u64| x.saturating_mul(req.skip)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) + .map(|x| if req.reverse { start_num - x } else { start_num + x }) + .map(|x| self.block_header(BlockID::Number(x))) + .take_while(|x| x.is_some()) + .flat_map(|x| x) + .collect() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + use rlp::{RlpStream, Stream}; + + let mut results = Vec::with_capacity(req.requests.len()); + + for request in req.requests { + let proof = match request.key2 { + Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockID::Hash(request.block)), + None => self.prove_account(request.key1, request.from_level, BlockID::Hash(request.block)), + }; + + let mut stream = RlpStream::new_list(proof.len()); + for node in proof { + stream.append_raw(&node, 1); + } + + results.push(stream.out()); + } + + results + } + + fn contract_code(&self, req: request::ContractCodes) -> Vec { + req.code_requests.into_iter() + .map(|req| { + self.code_by_hash(req.account_key, BlockID::Hash(req.block_hash)) + }) + .collect() + } + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + BlockChainClient::pending_transactions(self) + } } \ No newline at end of file diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/types/les_request.rs similarity index 72% rename from ethcore/light/src/request.rs rename to ethcore/light/src/types/les_request.rs index f043f0f25..d0de080ee 100644 --- a/ethcore/light/src/request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -16,25 +16,26 @@ //! LES request types. -// TODO: make IPC compatible. - use util::H256; /// A request for block headers. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Headers { - /// Block information for the request being made. - pub block: (u64, H256), + /// Starting block number + pub block_num: u64, + /// Starting block hash. This and number could be combined but IPC codegen is + /// not robust enough to support it. + pub block_hash: H256, /// The maximum amount of headers which can be returned. pub max: usize, /// The amount of headers to skip between each response entry. - pub skip: usize, + pub skip: u64, /// Whether the headers should proceed in falling number from the initial block. pub reverse: bool, } /// A request for specific block bodies. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Bodies { /// Hashes which bodies are being requested for. pub block_hashes: Vec @@ -44,14 +45,14 @@ pub struct Bodies { /// /// This request is answered with a list of transaction receipts for each block /// requested. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Receipts { /// Block hashes to return receipts for. pub block_hashes: Vec, } /// A request for a state proof -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct StateProof { /// Block hash to query state from. pub block: H256, @@ -65,21 +66,30 @@ pub struct StateProof { } /// A request for state proofs. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct StateProofs { /// All the proof requests. pub requests: Vec, } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct ContractCode { + /// Block hash + pub block_hash: H256, + /// Account key (== sha3(address)) + pub account_key: H256, +} + +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct ContractCodes { /// Block hash and account key (== sha3(address)) pairs to fetch code for. - pub code_requests: Vec<(H256, H256)>, + pub code_requests: Vec, } /// A request for a header proof from the Canonical Hash Trie. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, @@ -90,14 +100,14 @@ pub struct HeaderProof { } /// A request for header proofs from the CHT. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct HeaderProofs { /// All the proof requests. - pub requests: Vec, + pub requests: Vec, } /// Kinds of requests. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] pub enum Kind { /// Requesting headers. Headers, @@ -114,7 +124,7 @@ pub enum Kind { } /// Encompasses all possible types of requests in a single structure. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub enum Request { /// Requesting headers. Headers(Headers), @@ -142,4 +152,16 @@ impl Request { Request::HeaderProofs(_) => Kind::HeaderProofs, } } + + /// Get the amount of requests being made. + pub fn amount(&self) -> usize { + match *self { + Request::Headers(ref req) => req.max, + Request::Bodies(ref req) => req.block_hashes.len(), + Request::Receipts(ref req) => req.block_hashes.len(), + Request::StateProofs(ref req) => req.requests.len(), + Request::Codes(ref req) => req.code_requests.len(), + Request::HeaderProofs(ref req) => req.requests.len(), + } + } } \ No newline at end of file diff --git a/js/src/util/nullable-proptype.js b/ethcore/light/src/types/mod.rs similarity index 77% rename from js/src/util/nullable-proptype.js rename to ethcore/light/src/types/mod.rs index 331be6c18..2625358a3 100644 --- a/js/src/util/nullable-proptype.js +++ b/ethcore/light/src/types/mod.rs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { PropTypes } from 'react'; +//! Types used in the public (IPC) api which require custom code generation. -export default function (type) { - return PropTypes.oneOfType([ PropTypes.oneOf([ null ]), type ]); -} +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); diff --git a/ethcore/light/src/types/mod.rs.in b/ethcore/light/src/types/mod.rs.in new file mode 100644 index 000000000..2b7386b5b --- /dev/null +++ b/ethcore/light/src/types/mod.rs.in @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +pub mod les_request; \ No newline at end of file diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index 9ab782395..a0f88b85b 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -21,8 +21,7 @@ "genesis": { "seal": { "generic": { - "fields": 2, - "rlp": "0x200" + "rlp": "0xc28080" } }, "difficulty": "0x20000", diff --git a/ethcore/res/instant_seal.json b/ethcore/res/instant_seal.json index a6b24faf9..fbb650102 100644 --- a/ethcore/res/instant_seal.json +++ b/ethcore/res/instant_seal.json @@ -12,7 +12,6 @@ "genesis": { "seal": { "generic": { - "fields": 0, "rlp": "0x0" } }, diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider/mod.rs similarity index 84% rename from ethcore/src/account_provider.rs rename to ethcore/src/account_provider/mod.rs index da5992f0c..7e0005c7a 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider/mod.rs @@ -16,9 +16,12 @@ //! Account management. -use std::{fs, fmt}; +mod stores; + +use self::stores::{AddressBook, DappsSettingsStore}; + +use std::fmt; use std::collections::HashMap; -use std::path::PathBuf; use std::time::{Instant, Duration}; use util::{Mutex, RwLock, Itertools}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; @@ -106,77 +109,8 @@ impl KeyDirectory for NullDir { } } -/// Disk-backed map from Address to String. Uses JSON. -struct AddressBook { - path: PathBuf, - cache: HashMap, - transient: bool, -} - -impl AddressBook { - pub fn new(path: String) -> Self { - trace!(target: "addressbook", "new({})", path); - let mut path: PathBuf = path.into(); - path.push("address_book.json"); - trace!(target: "addressbook", "path={:?}", path); - let mut r = AddressBook { - path: path, - cache: HashMap::new(), - transient: false, - }; - r.revert(); - r - } - - pub fn transient() -> Self { - let mut book = AddressBook::new(Default::default()); - book.transient = true; - book - } - - pub fn get(&self) -> HashMap { - self.cache.clone() - } - - pub fn set_name(&mut self, a: Address, name: String) { - let mut x = self.cache.get(&a) - .cloned() - .unwrap_or_else(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None}); - x.name = name; - self.cache.insert(a, x); - self.save(); - } - - pub fn set_meta(&mut self, a: Address, meta: String) { - let mut x = self.cache.get(&a) - .cloned() - .unwrap_or_else(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None}); - x.meta = meta; - self.cache.insert(a, x); - self.save(); - } - - fn revert(&mut self) { - if self.transient { return; } - trace!(target: "addressbook", "revert"); - let _ = fs::File::open(self.path.clone()) - .map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) - .and_then(|f| AccountMeta::read_address_map(&f) - .map_err(|e| warn!(target: "addressbook", "Couldn't read address book: {}", e)) - .and_then(|m| { self.cache = m; Ok(()) }) - ); - } - - fn save(&mut self) { - if self.transient { return; } - trace!(target: "addressbook", "save"); - let _ = fs::File::create(self.path.clone()) - .map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) - .and_then(|mut f| AccountMeta::write_address_map(&self.cache, &mut f) - .map_err(|e| warn!(target: "addressbook", "Couldn't write to address book: {}", e)) - ); - } -} +/// Dapp identifier +pub type DappId = String; fn transient_sstore() -> EthMultiStore { EthMultiStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed") @@ -189,6 +123,7 @@ type AccountToken = String; pub struct AccountProvider { address_book: Mutex, unlocked: Mutex>, + dapps_settings: RwLock, /// Accounts on disk sstore: Box, /// Accounts unlocked with rolling tokens @@ -200,7 +135,8 @@ impl AccountProvider { pub fn new(sstore: Box) -> Self { AccountProvider { unlocked: Mutex::new(HashMap::new()), - address_book: Mutex::new(AddressBook::new(sstore.local_path().into())), + address_book: RwLock::new(AddressBook::new(sstore.local_path().into())), + dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())), sstore: sstore, transient_sstore: transient_sstore(), } @@ -209,8 +145,9 @@ impl AccountProvider { /// Creates not disk backed provider. pub fn transient_provider() -> Self { AccountProvider { - unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), + unlocked: Mutex::new(HashMap::new()), + dapps_settings: RwLock::new(DappsSettingsStore::transient()), sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), transient_sstore: transient_sstore(), } @@ -255,19 +192,36 @@ impl AccountProvider { Ok(accounts) } + /// Gets addresses visile for dapp. + pub fn dapps_addresses(&self, dapp: DappId) -> Result, Error> { + let accounts = self.dapps_settings.read().get(); + Ok(accounts.get(&dapp).map(|settings| settings.accounts.clone()).unwrap_or_else(Vec::new)) + } + + /// Sets addresses visile for dapp. + pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec
) -> Result<(), Error> { + self.dapps_settings.write().set_accounts(dapp, addresses); + Ok(()) + } + /// Returns each address along with metadata. pub fn addresses_info(&self) -> Result, Error> { - Ok(self.address_book.lock().get()) + Ok(self.address_book.read().get()) } /// Returns each address along with metadata. pub fn set_address_name(&self, account: Address, name: String) -> Result<(), Error> { - Ok(self.address_book.lock().set_name(account, name)) + Ok(self.address_book.write().set_name(account, name)) } /// Returns each address along with metadata. pub fn set_address_meta(&self, account: Address, meta: String) -> Result<(), Error> { - Ok(self.address_book.lock().set_meta(account, meta)) + Ok(self.address_book.write().set_meta(account, meta)) + } + + /// Removes and address from the addressbook + pub fn remove_address(&self, addr: Address) -> Result<(), Error> { + Ok(self.address_book.write().remove(addr)) } /// Returns each account along with name and meta. @@ -443,23 +397,9 @@ impl AccountProvider { #[cfg(test)] mod tests { - use super::{AccountProvider, AddressBook, Unlock}; - use std::collections::HashMap; + use super::{AccountProvider, Unlock}; use std::time::Instant; - use ethjson::misc::AccountMeta; use ethstore::ethkey::{Generator, Random}; - use devtools::RandomTempPath; - - #[test] - fn should_save_and_reload_address_book() { - let temp = RandomTempPath::create_dir(); - let path = temp.as_str().to_owned(); - let mut b = AddressBook::new(path.clone()); - b.set_name(1.into(), "One".to_owned()); - b.set_meta(1.into(), "{1:1}".to_owned()); - let b = AddressBook::new(path); - assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); - } #[test] fn unlock_account_temp() { @@ -513,4 +453,16 @@ mod tests { .expect("First usage of token should be correct."); assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail."); } + + fn should_set_dapps_addresses() { + // given + let ap = AccountProvider::transient_provider(); + let app = "app1".to_owned(); + + // when + ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap(); + + // then + assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); + } } diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs new file mode 100644 index 000000000..8bf555d68 --- /dev/null +++ b/ethcore/src/account_provider/stores.rs @@ -0,0 +1,271 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Address Book and Dapps Settings Store + +use std::{fs, fmt, hash, ops}; +use std::collections::HashMap; +use std::path::PathBuf; + +use ethstore::ethkey::Address; +use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings}; +use account_provider::DappId; + +/// Disk-backed map from Address to String. Uses JSON. +pub struct AddressBook { + cache: DiskMap, +} + +impl AddressBook { + /// Creates new address book at given directory. + pub fn new(path: String) -> Self { + let mut r = AddressBook { + cache: DiskMap::new(path, "address_book.json".into()) + }; + r.cache.revert(AccountMeta::read_address_map); + r + } + + /// Creates transient address book (no changes are saved to disk). + pub fn transient() -> Self { + AddressBook { + cache: DiskMap::transient() + } + } + + /// Get the address book. + pub fn get(&self) -> HashMap { + self.cache.clone() + } + + fn save(&self) { + self.cache.save(AccountMeta::write_address_map) + } + + /// Sets new name for given address. + pub fn set_name(&mut self, a: Address, name: String) { + { + let mut x = self.cache.entry(a) + .or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None}); + x.name = name; + } + self.save(); + } + + /// Sets new meta for given address. + pub fn set_meta(&mut self, a: Address, meta: String) { + { + let mut x = self.cache.entry(a) + .or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None}); + x.meta = meta; + } + self.save(); + } + + /// Removes an entry + pub fn remove(&mut self, a: Address) { + self.cache.remove(&a); + self.save(); + } +} + +/// Dapps user settings +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct DappsSettings { + /// A list of visible accounts + pub accounts: Vec
, +} + +impl From for DappsSettings { + fn from(s: JsonSettings) -> Self { + DappsSettings { + accounts: s.accounts.into_iter().map(Into::into).collect(), + } + } +} + +impl From for JsonSettings { + fn from(s: DappsSettings) -> Self { + JsonSettings { + accounts: s.accounts.into_iter().map(Into::into).collect(), + } + } +} + +/// Disk-backed map from DappId to Settings. Uses JSON. +pub struct DappsSettingsStore { + cache: DiskMap, +} + +impl DappsSettingsStore { + /// Creates new store at given directory path. + pub fn new(path: String) -> Self { + let mut r = DappsSettingsStore { + cache: DiskMap::new(path, "dapps_accounts.json".into()) + }; + r.cache.revert(JsonSettings::read_dapps_settings); + r + } + + /// Creates transient store (no changes are saved to disk). + pub fn transient() -> Self { + DappsSettingsStore { + cache: DiskMap::transient() + } + } + + /// Get copy of the dapps settings + pub fn get(&self) -> HashMap { + self.cache.clone() + } + + fn save(&self) { + self.cache.save(JsonSettings::write_dapps_settings) + } + + pub fn set_accounts(&mut self, id: DappId, accounts: Vec
) { + { + let mut settings = self.cache.entry(id).or_insert_with(DappsSettings::default); + settings.accounts = accounts; + } + self.save(); + } +} + +/// Disk-serializable HashMap +#[derive(Debug)] +struct DiskMap { + path: PathBuf, + cache: HashMap, + transient: bool, +} + +impl ops::Deref for DiskMap { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.cache + } +} + +impl ops::DerefMut for DiskMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.cache + } +} + +impl DiskMap { + pub fn new(path: String, file_name: String) -> Self { + trace!(target: "diskmap", "new({})", path); + let mut path: PathBuf = path.into(); + path.push(file_name); + trace!(target: "diskmap", "path={:?}", path); + DiskMap { + path: path, + cache: HashMap::new(), + transient: false, + } + } + + pub fn transient() -> Self { + let mut map = DiskMap::new(Default::default(), "diskmap.json".into()); + map.transient = true; + map + } + + fn revert(&mut self, read: F) where + F: Fn(fs::File) -> Result, E>, + E: fmt::Display, + { + if self.transient { return; } + trace!(target: "diskmap", "revert {:?}", self.path); + let _ = fs::File::open(self.path.clone()) + .map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e)) + .and_then(|f| read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e))) + .and_then(|m| { + self.cache = m; + Ok(()) + }); + } + + fn save(&self, write: F) where + F: Fn(&HashMap, &mut fs::File) -> Result<(), E>, + E: fmt::Display, + { + if self.transient { return; } + trace!(target: "diskmap", "save {:?}", self.path); + let _ = fs::File::create(self.path.clone()) + .map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e)) + .and_then(|mut f| { + write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e)) + }); + } +} + +#[cfg(test)] +mod tests { + use super::{AddressBook, DappsSettingsStore, DappsSettings}; + use std::collections::HashMap; + use ethjson::misc::AccountMeta; + use devtools::RandomTempPath; + + #[test] + fn should_save_and_reload_address_book() { + let temp = RandomTempPath::create_dir(); + let path = temp.as_str().to_owned(); + let mut b = AddressBook::new(path.clone()); + b.set_name(1.into(), "One".to_owned()); + b.set_meta(1.into(), "{1:1}".to_owned()); + let b = AddressBook::new(path); + assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); + } + + #[test] + fn should_save_and_reload_dapps_settings() { + // given + let temp = RandomTempPath::create_dir(); + let path = temp.as_str().to_owned(); + let mut b = DappsSettingsStore::new(path.clone()); + + // when + b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); + + // then + let b = DappsSettingsStore::new(path); + assert_eq!(b.get(), hash_map![ + "dappOne".into() => DappsSettings { + accounts: vec![1.into(), 2.into()], + } + ]); + } + + #[test] + fn should_remove_address() { + let temp = RandomTempPath::create_dir(); + let path = temp.as_str().to_owned(); + let mut b = AddressBook::new(path.clone()); + + b.set_name(1.into(), "One".to_owned()); + b.set_name(2.into(), "Two".to_owned()); + b.set_name(3.into(), "Three".to_owned()); + b.remove(2.into()); + + let b = AddressBook::new(path); + assert_eq!(b.get(), hash_map![ + 1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}, + 3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None} + ]); + } +} diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 68fb40483..0e8e0a271 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -34,6 +34,7 @@ use blockchain::update::ExtrasUpdate; use blockchain::{CacheSize, ImportRoute, Config}; use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; +use engines::Engine; const LOG_BLOOMS_LEVELS: usize = 3; const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; @@ -198,6 +199,9 @@ pub struct BlockChain { pending_block_hashes: RwLock>, pending_block_details: RwLock>, pending_transaction_addresses: RwLock>>, + + // Used for block ordering. + engine: Arc, } impl BlockProvider for BlockChain { @@ -415,9 +419,8 @@ impl<'a> Iterator for AncestryIter<'a> { } impl BlockChain { - #[cfg_attr(feature="dev", allow(useless_let_if_seq))] - /// Create new instance of blockchain from given Genesis - pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { + /// Create new instance of blockchain from given Genesis and block picking rules of Engine. + pub fn new(config: Config, genesis: &[u8], db: Arc, engine: Arc) -> BlockChain { // 400 is the avarage size of the key let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400); @@ -442,6 +445,7 @@ impl BlockChain { pending_block_hashes: RwLock::new(HashMap::new()), pending_block_details: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), + engine: engine, }; // load best block @@ -858,13 +862,12 @@ impl BlockChain { let number = header.number(); let parent_hash = header.parent_hash(); let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash)); - let total_difficulty = parent_details.total_difficulty + header.difficulty(); - let is_new_best = total_difficulty > self.best_block_total_difficulty(); + let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), HeaderView::new(&self.best_block_header()), &parent_details, header); BlockInfo { hash: hash, number: number, - total_difficulty: total_difficulty, + total_difficulty: parent_details.total_difficulty + header.difficulty(), location: if is_new_best { // on new best block we need to make sure that all ancestors // are moved to "canon chain" @@ -1319,11 +1322,16 @@ mod tests { use views::BlockView; use transaction::{Transaction, Action}; use log_entry::{LogEntry, LocalizedLogEntry}; + use spec::Spec; fn new_db(path: &str) -> Arc { Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap()) } + fn new_chain(genesis: &[u8], db: Arc) -> BlockChain { + BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) + } + #[test] fn should_cache_best_block() { // given @@ -1334,7 +1342,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 0); // when @@ -1360,7 +1368,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.genesis_hash(), genesis_hash.clone()); assert_eq!(bc.best_block_hash(), genesis_hash.clone()); @@ -1391,7 +1399,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut block_hashes = vec![genesis_hash.clone()]; let mut batch = db.transaction(); @@ -1427,7 +1435,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); for b in &[&b1a, &b1b, &b2a, &b2b, &b3a, &b3b, &b4a, &b4b, &b5a, &b5b] { @@ -1489,7 +1497,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1577,7 +1585,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1639,7 +1647,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let ir1 = bc.insert_block(&mut batch, &b1, vec![]); @@ -1755,7 +1763,7 @@ mod tests { let temp = RandomTempPath::new(); { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), genesis_hash); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); @@ -1766,7 +1774,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), first_hash); } @@ -1821,7 +1829,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &b1, vec![]); db.write(batch).unwrap(); @@ -1881,7 +1889,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); insert_block(&db, &bc, &b1, vec![Receipt { state_root: H256::default(), gas_used: 10_000.into(), @@ -1985,7 +1993,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5); let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 5); @@ -2042,7 +2050,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap(); let mut batch =db.transaction(); @@ -2061,7 +2069,7 @@ mod tests { // re-loading the blockchain should load the correct best block. let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 5); } @@ -2078,7 +2086,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 85ec05b03..21c5a2366 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, + ChainNotify, PruningInfo, ProvingBlockChainClient, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -164,7 +164,7 @@ impl Client { let gb = spec.genesis_block(); let db = Arc::new(try!(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database))); - let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); + let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); let trie_spec = match config.fat_db { @@ -787,7 +787,7 @@ impl snapshot::DatabaseRestore for Client { let cache_size = state_db.cache_size(); *state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size); - *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); + *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone(), self.engine.clone())); *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); Ok(()) } @@ -1272,7 +1272,7 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } - fn signing_network_id(&self) -> Option { + fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } @@ -1286,6 +1286,13 @@ impl BlockChainClient for Client { self.uncle(id) .map(|header| self.engine.extra_info(&decode(&header))) } + + fn pruning_info(&self) -> PruningInfo { + PruningInfo { + earliest_chain: self.chain.read().first_block_number().unwrap_or(1), + earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0), + } + } } impl MiningBlockChainClient for Client { @@ -1370,32 +1377,60 @@ impl MayPanic for Client { } } +impl ProvingBlockChainClient for Client { + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { + self.state_at(id) + .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) + .unwrap_or_else(Vec::new) + } -#[test] -fn should_not_cache_details_before_commit() { - use tests::helpers::*; - use std::thread; - use std::time::Duration; - use std::sync::atomic::{AtomicBool, Ordering}; + fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec { + self.state_at(id) + .and_then(move |state| state.prove_account(key1, from_level).ok()) + .unwrap_or_else(Vec::new) + } - let client = generate_dummy_client(0); - let genesis = client.chain_info().best_block_hash; - let (new_hash, new_block) = get_good_dummy_block_hash(); - - let go = { - // Separate thread uncommited transaction - let go = Arc::new(AtomicBool::new(false)); - 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()); - another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); - go_thread.store(true, Ordering::SeqCst); - }); - go - }; - - while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } - - assert!(client.tree_route(&genesis, &new_hash).is_none()); + fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes { + self.state_at(id) + .and_then(move |state| state.code_by_address_hash(account_key).ok()) + .and_then(|x| x) + .unwrap_or_else(Vec::new) + } } + +#[cfg(test)] +mod tests { + + #[test] + fn should_not_cache_details_before_commit() { + use client::BlockChainClient; + use tests::helpers::*; + + use std::thread; + use std::time::Duration; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use util::kvdb::DBTransaction; + + let client = generate_dummy_client(0); + let genesis = client.chain_info().best_block_hash; + let (new_hash, new_block) = get_good_dummy_block_hash(); + + let go = { + // Separate thread uncommited transaction + let go = Arc::new(AtomicBool::new(false)); + 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()); + another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); + go_thread.store(true, Ordering::SeqCst); + }); + go + }; + + while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + + assert!(client.tree_route(&genesis, &new_hash).is_none()); + } +} \ No newline at end of file diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3898ab6cd..af4daece6 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -25,18 +25,21 @@ mod client; pub use self::client::*; pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::error::Error; -pub use types::ids::*; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; +pub use self::chain_notify::ChainNotify; +pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient}; + +pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; +pub use types::pruning_info::PruningInfo; +pub use types::call_analytics::CallAnalytics; + pub use executive::{Executed, Executive, TransactOptions}; pub use env_info::{LastHashes, EnvInfo}; -pub use self::chain_notify::ChainNotify; -pub use types::call_analytics::CallAnalytics; pub use block_import_error::BlockImportError; pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportError; -pub use self::traits::{BlockChainClient, MiningBlockChainClient}; pub use verification::VerifierType; /// IPC interfaces diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index c03b5920b..dd00db7ec 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; use types::mode::Mode; +use types::pruning_info::PruningInfo; use views::BlockView; use verification::queue::QueueInfo; @@ -662,9 +663,16 @@ impl BlockChainClient for TestBlockChainClient { self.miner.pending_transactions(self.chain_info().best_block_number) } - fn signing_network_id(&self) -> Option { None } + fn signing_network_id(&self) -> Option { None } fn mode(&self) -> Mode { Mode::Active } fn set_mode(&self, _: Mode) { unimplemented!(); } + + fn pruning_info(&self) -> PruningInfo { + PruningInfo { + earliest_chain: 1, + earliest_state: 1, + } + } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 6d774e250..7bf17279c 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -39,6 +39,7 @@ use types::call_analytics::CallAnalytics; use types::blockchain_info::BlockChainInfo; use types::block_status::BlockStatus; use types::mode::Mode; +use types::pruning_info::PruningInfo; #[ipc(client_ident="RemoteClient")] /// Blockchain database client. Owns and manages a blockchain and a block queue. @@ -237,7 +238,7 @@ pub trait BlockChainClient : Sync + Send { } /// Get the preferred network ID to sign on - fn signing_network_id(&self) -> Option; + fn signing_network_id(&self) -> Option; /// Get the mode. fn mode(&self) -> Mode; @@ -250,10 +251,15 @@ pub trait BlockChainClient : Sync + Send { /// Returns engine-related extra info for `UncleID`. fn uncle_extra_info(&self, id: UncleID) -> Option>; + + /// Returns information about pruning/data availability. + fn pruning_info(&self) -> PruningInfo; } +impl IpcConfig for BlockChainClient { } + /// Extended client interface used for mining -pub trait MiningBlockChainClient : BlockChainClient { +pub trait MiningBlockChainClient: BlockChainClient { /// Returns OpenBlock prepared for closing. fn prepare_open_block(&self, author: Address, @@ -271,4 +277,23 @@ pub trait MiningBlockChainClient : BlockChainClient { fn latest_schedule(&self) -> Schedule; } -impl IpcConfig for BlockChainClient { } +/// Extended client interface for providing proofs of the state. +pub trait ProvingBlockChainClient: BlockChainClient { + /// Prove account storage at a specific block id. + /// + /// Both provided keys assume a secure trie. + /// Returns a vector of raw trie nodes (in order from the root) proving the storage query. + /// Nodes after `from_level` may be omitted. + /// An empty vector indicates unservable query. + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec; + + /// Prove account existence at a specific block id. + /// The key is the keccak hash of the account's address. + /// Returns a vector of raw trie nodes (in order from the root) proving the query. + /// Nodes after `from_level` may be omitted. + /// An empty vector indicates unservable query. + fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec; + + /// Get code by address hash. + fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; +} \ No newline at end of file diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 6c1d0a409..f632c9382 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -21,13 +21,15 @@ use std::sync::Weak; use std::time::{UNIX_EPOCH, Duration}; use util::*; use ethkey::{verify_address, Signature}; -use rlp::{UntrustedRlp, View, encode}; +use rlp::{UntrustedRlp, Rlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::Engine; use header::Header; use error::{Error, BlockError}; +use blockchain::extras::BlockDetails; +use views::HeaderView; use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; @@ -66,18 +68,20 @@ pub struct AuthorityRound { params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap, - transition_service: IoService, + transition_service: IoService<()>, message_channel: Mutex>>, step: AtomicUsize, proposed: AtomicBool, + account_provider: Mutex>>, + password: RwLock>, } fn header_step(header: &Header) -> Result { - UntrustedRlp::new(&header.seal()[0]).as_val() + UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val() } fn header_signature(header: &Header) -> Result { - UntrustedRlp::new(&header.seal()[1]).as_val::().map(Into::into) + UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::().map(Into::into) } trait AsMillis { @@ -99,10 +103,12 @@ impl AuthorityRound { params: params, our_params: our_params, builtins: builtins, - transition_service: try!(IoService::::start()), + transition_service: try!(IoService::<()>::start()), message_channel: Mutex::new(None), step: AtomicUsize::new(initial_step), - proposed: AtomicBool::new(false) + proposed: AtomicBool::new(false), + account_provider: Mutex::new(None), + password: RwLock::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.transition_service.register_handler(Arc::new(handler))); @@ -141,20 +147,17 @@ struct TransitionHandler { engine: Weak, } -#[derive(Clone)] -struct BlockArrived; - const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; -impl IoHandler for TransitionHandler { - fn initialize(&self, io: &IoContext) { +impl IoHandler<()> for TransitionHandler { + fn initialize(&self, io: &IoContext<()>) { if let Some(engine) = self.engine.upgrade() { io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, io: &IoContext<()>, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { engine.step.fetch_add(1, AtomicOrdering::SeqCst); @@ -206,10 +209,6 @@ impl Engine for AuthorityRound { }); } - /// Apply the block reward on finalisation of the block. - /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). - fn on_close_block(&self, _block: &mut ExecutedBlock) {} - fn is_sealer(&self, author: &Address) -> Option { let p = &self.our_params; Some(p.authorities.contains(author)) @@ -219,14 +218,14 @@ impl Engine for AuthorityRound { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { if self.proposed.load(AtomicOrdering::SeqCst) { return None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { - if let Some(ap) = accounts { + if let Some(ref ap) = *self.account_provider.lock() { // Account should be permanently unlocked, otherwise sealing will fail. - if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { + if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); @@ -272,7 +271,6 @@ impl Engine for AuthorityRound { } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - // Don't calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } @@ -284,10 +282,6 @@ impl Engine for AuthorityRound { try!(Err(BlockError::DoubleVote(header.author().clone()))); } - // Check difficulty is correct given the two timestamps. - if header.difficulty() != parent.difficulty() { - return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() }))) - } let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; @@ -306,9 +300,29 @@ impl Engine for AuthorityRound { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + let new_number = new_header.number(); + let best_number = best_header.number(); + if new_number != best_number { + new_number > best_number + } else { + // Take the oldest step at given height. + let new_step: usize = Rlp::new(&new_header.seal()[0]).as_val(); + let best_step: usize = Rlp::new(&best_header.seal()[0]).as_val(); + new_step < best_step + } + } + fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + fn set_signer(&self, _address: Address, password: String) { + *self.password.write() = Some(password); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } @@ -377,12 +391,11 @@ mod tests { fn generates_seal_and_does_not_double_propose() { let tap = AccountProvider::transient_provider(); let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); - tap.unlock_account_permanently(addr1, "1".into()).unwrap(); let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); - tap.unlock_account_permanently(addr2, "2".into()).unwrap(); let spec = Spec::new_test_round(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db1 = get_temp_state_db().take(); spec.ensure_db_good(&mut db1, &TrieFactory::new(TrieSpec::Secure)).unwrap(); @@ -394,16 +407,18 @@ mod tests { let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = b2.close_and_lock(); - if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { + engine.set_signer(addr1, "1".into()); + if let Some(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b1.block()).is_none()); } - if let Some(seal) = engine.generate_seal(b2.block(), Some(&tap)) { + engine.set_signer(addr2, "2".into()); + if let Some(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b2.block()).is_none()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index fb2f9bde6..1070d3a3d 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,6 +58,8 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, + account_provider: Mutex>>, + password: RwLock>, } impl BasicAuthority { @@ -67,6 +69,8 @@ impl BasicAuthority { params: params, our_params: our_params, builtins: builtins, + account_provider: Mutex::new(None), + password: RwLock::new(None), } } } @@ -98,13 +102,8 @@ impl Engine for BasicAuthority { max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) } }); -// info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number, header.difficulty, header.gas_limit); } - /// Apply the block reward on finalisation of the block. - /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). - fn on_close_block(&self, _block: &mut ExecutedBlock) {} - fn is_sealer(&self, author: &Address) -> Option { Some(self.our_params.authorities.contains(author)) } @@ -113,12 +112,12 @@ impl Engine for BasicAuthority { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail - if let Ok(signature) = ap.sign(*block.header().author(), None, message) { + if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) { return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); @@ -179,6 +178,14 @@ impl Engine for BasicAuthority { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn set_signer(&self, _address: Address, password: String) { + *self.password.write() = Some(password); + } + + fn register_account_provider(&self, ap: Arc) { + *self.account_provider.lock() = Some(ap); + } } #[cfg(test)] @@ -250,10 +257,11 @@ mod tests { fn can_generate_seal() { let tap = AccountProvider::transient_provider(); let addr = tap.insert_account("".sha3(), "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); let spec = new_test_authority(); let engine = &*spec.engine; + engine.set_signer(addr, "".into()); + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); @@ -261,7 +269,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, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index f50f7344b..83335fb03 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -23,7 +23,6 @@ use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; use util::Bytes; -use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -60,7 +59,7 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { Some(Vec::new()) } } @@ -70,16 +69,12 @@ mod tests { use util::*; use util::trie::TrieSpec; use tests::helpers::*; - use account_provider::AccountProvider; use spec::Spec; use header::Header; use block::*; #[test] fn instant_can_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); @@ -87,10 +82,9 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); - let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - // Seal with empty AccountProvider. - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c70a19de8..d7ff06248 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -38,6 +38,9 @@ use io::IoChannel; use service::ClientIoMessage; use header::Header; use transaction::SignedTransaction; +use ethereum::ethash; +use blockchain::extras::BlockDetails; +use views::HeaderView; /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// Provides hooks into each of the major parts of block import. @@ -91,7 +94,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. @@ -113,7 +116,7 @@ pub trait Engine : Sync + Send { fn verify_transaction(&self, _t: &SignedTransaction, _header: &Header) -> Result<(), Error> { Ok(()) } /// 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 { None } /// 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 @@ -144,7 +147,17 @@ pub trait Engine : Sync + Send { self.builtins().get(a).expect("attempted to execute nonexistent builtin").execute(input, output); } + /// Check if new block should be chosen as the one in chain. + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) + } + + /// Register an account which signs consensus messages. + fn set_signer(&self, _address: Address, _password: String) {} + /// Add a channel for communication with Client which can be used for sealing. fn register_message_channel(&self, _message_channel: IoChannel) {} - // TODO: sealing stuff - though might want to leave this for later. + + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/engines/null_engine.rs b/ethcore/src/engines/null_engine.rs index e0906ce22..bd5a4474a 100644 --- a/ethcore/src/engines/null_engine.rs +++ b/ethcore/src/engines/null_engine.rs @@ -38,6 +38,12 @@ impl NullEngine { } } +impl Default for NullEngine { + fn default() -> Self { + Self::new(Default::default(), Default::default()) + } +} + impl Engine for NullEngine { fn name(&self) -> &str { "NullEngine" diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 38a1df525..ae768e5d3 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -21,6 +21,7 @@ use builtin::Builtin; use env_info::EnvInfo; use error::{BlockError, TransactionError, Error}; use header::Header; +use views::HeaderView; use state::CleanupMode; use spec::CommonParams; use transaction::SignedTransaction; @@ -28,6 +29,7 @@ use engines::Engine; use evm::Schedule; use ethjson; use rlp::{self, UntrustedRlp, View}; +use blockchain::extras::BlockDetails; /// Ethash params. #[derive(Debug, PartialEq)] @@ -163,9 +165,9 @@ impl Engine for Ethash { } } - fn signing_network_id(&self, env_info: &EnvInfo) -> Option { - if env_info.number >= self.ethash_params.eip155_transition && self.params().network_id < 127 { - Some(self.params().network_id as u8) + fn signing_network_id(&self, env_info: &EnvInfo) -> Option { + if env_info.number >= self.ethash_params.eip155_transition { + Some(self.params().network_id) } else { None } @@ -314,7 +316,7 @@ impl Engine for Ethash { } if let Some(n) = t.network_id() { - if header.number() < self.ethash_params.eip155_transition || n as usize != self.params().network_id { + if header.number() < self.ethash_params.eip155_transition || n != self.params().network_id { return Err(TransactionError::InvalidNetworkId.into()) } } @@ -325,6 +327,15 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + is_new_best_block(best_total_difficulty, parent_details, new_header) + } +} + +/// Check if a new block should replace the best blockchain block. +pub fn is_new_best_block(best_total_difficulty: U256, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + parent_details.total_difficulty + new_header.difficulty() > best_total_difficulty } #[cfg_attr(feature="dev", allow(wrong_self_convention))] diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index a543e608d..8d1f55567 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -464,15 +464,12 @@ impl Miner { /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { - trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); + trace!(target: "miner", "seal_block_internally: attempting internal seal."); + let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); - block.lock().try_seal(&*self.engine, seal).or_else(|_| { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); Err(None) }) } else { @@ -485,7 +482,7 @@ impl Miner { fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_block(sealed.rlp_bytes()).is_ok() { + if chain.import_sealed_block(sealed).is_ok() { trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } @@ -740,6 +737,19 @@ impl MinerService for Miner { *self.author.write() = author; } + fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + if self.seals_internally { + if let Some(ref ap) = self.accounts { + try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); + } + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + self.engine.set_signer(address, password); + } + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } @@ -1042,7 +1052,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1050,22 +1060,22 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| &b.hash() == &block_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) }) } else { - warn!(target: "miner", "Mined solution rejected: Block unknown or out of date."); + warn!(target: "miner", "Submitted solution rejected: Block unknown or out of date."); Err(Error::PowHashInvalid) }; result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); try!(chain.import_sealed_block(sealed)); - info!(target: "miner", "Mined block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); + info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); Ok(()) }) } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 1fb2244fd..8c466581b 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -76,6 +76,9 @@ pub trait MinerService : Send + Sync { /// Set the author that we will seal blocks as. fn set_author(&self, author: Address); + /// Set info necessary to sign consensus messages. + fn set_engine_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index c0d34a6a9..89ee68de0 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -81,6 +81,7 @@ struct Restoration { struct RestorationParams<'a> { manifest: ManifestData, // manifest to base restoration on. pruning: Algorithm, // pruning algorithm for the database. + engine: Arc, // consensus engine of the chain. db_path: PathBuf, // database path db_config: &'a DatabaseConfig, // configuration for the database. writer: Option, // writer for recovered snapshot. @@ -99,7 +100,7 @@ impl Restoration { let raw_db = Arc::new(try!(Database::open(params.db_config, &*params.db_path.to_string_lossy()) .map_err(UtilError::SimpleString))); - let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); + let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone(), params.engine); let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), &manifest)); let root = manifest.state_root.clone(); @@ -420,6 +421,7 @@ impl Service { let params = RestorationParams { manifest: manifest, pruning: self.pruning, + engine: self.engine.clone(), db_path: self.restoration_db(), db_config: &self.db_config, writer: writer, diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 18637bad1..3d9390d2e 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -37,13 +37,14 @@ fn chunk_and_restore(amount: u64) { let genesis = canon_chain.generate(&mut finalizer).unwrap(); let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let engine = Arc::new(::engines::NullEngine::default()); let orig_path = RandomTempPath::create_dir(); let new_path = RandomTempPath::create_dir(); let mut snapshot_path = new_path.as_path().to_owned(); snapshot_path.push("SNAP"); let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); - let bc = BlockChain::new(Default::default(), &genesis, old_db.clone()); + let bc = BlockChain::new(Default::default(), &genesis, old_db.clone(), engine.clone()); // build the blockchain. let mut batch = old_db.transaction(); @@ -73,21 +74,20 @@ fn chunk_and_restore(amount: u64) { // restore it. let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); - let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), engine.clone()); let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); let flag = AtomicBool::new(true); for chunk_hash in &reader.manifest().block_hashes { let compressed = reader.chunk(*chunk_hash).unwrap(); let chunk = snappy::decompress(&compressed).unwrap(); - rebuilder.feed(&chunk, &engine, &flag).unwrap(); + rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap(); } rebuilder.finalize(HashMap::new()).unwrap(); // and test it. - let new_chain = BlockChain::new(Default::default(), &genesis, new_db); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db, engine); assert_eq!(new_chain.best_block_hash(), best_hash); } @@ -121,8 +121,8 @@ fn checks_flag() { let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap()); - let chain = BlockChain::new(Default::default(), &genesis, db.clone()); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); + let engine = Arc::new(::engines::NullEngine::default()); + let chain = BlockChain::new(Default::default(), &genesis, db.clone(), engine.clone()); let manifest = ::snapshot::ManifestData { state_hashes: Vec::new(), @@ -134,8 +134,8 @@ fn checks_flag() { let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap(); - match rebuilder.feed(&chunk, &engine, &AtomicBool::new(false)) { + match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) { Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {} _ => panic!("Wrong result on abort flag set") } -} \ No newline at end of file +} diff --git a/ethcore/src/spec/seal.rs b/ethcore/src/spec/seal.rs index e25f174d2..f3e5dca95 100644 --- a/ethcore/src/spec/seal.rs +++ b/ethcore/src/spec/seal.rs @@ -30,11 +30,9 @@ pub struct Ethereum { impl Into for Ethereum { fn into(self) -> Generic { - let mut s = RlpStream::new(); - s.append(&self.mix_hash); - s.append(&self.nonce); + let mut s = RlpStream::new_list(2); + s.append(&self.mix_hash).append(&self.nonce); Generic { - fields: 2, rlp: s.out() } } @@ -42,8 +40,6 @@ impl Into for Ethereum { /// Generic seal. pub struct Generic { - /// Number of seal fields. - pub fields: usize, /// Seal rlp. pub rlp: Vec, } @@ -64,7 +60,6 @@ impl From for Seal { mix_hash: eth.mix_hash.into() }), ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic { - fields: g.fields, rlp: g.rlp.into() }) } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 71c15bca2..c9ae087c0 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -30,15 +30,14 @@ use ethjson; use rlp::{Rlp, RlpStream, View, Stream}; /// Parameters common to all engines. -#[derive(Debug, PartialEq, Clone)] -#[cfg_attr(test, derive(Default))] +#[derive(Debug, PartialEq, Clone, Default)] pub struct CommonParams { /// Account start nonce. pub account_start_nonce: U256, /// Maximum size of extra data. pub maximum_extra_data_size: usize, /// Network id. - pub network_id: usize, + pub network_id: u64, /// Main subprotocol name. pub subprotocol_name: String, /// Minimum gas limit. @@ -94,8 +93,6 @@ pub struct Spec { pub receipts_root: H256, /// The genesis block's extra data field. pub extra_data: Bytes, - /// The number of seal fields in the genesis block. - pub seal_fields: usize, /// Each seal field, expressed as RLP, concatenated. pub seal_rlp: Bytes, @@ -127,7 +124,6 @@ impl From for Spec { gas_used: g.gas_used, timestamp: g.timestamp, extra_data: g.extra_data, - seal_fields: seal.fields, seal_rlp: seal.rlp, state_root_memo: RwLock::new(g.state_root), genesis_state: From::from(s.accounts), @@ -167,7 +163,7 @@ impl Spec { pub fn nodes(&self) -> &[String] { &self.nodes } /// Get the configured Network ID. - pub fn network_id(&self) -> usize { self.params.network_id } + pub fn network_id(&self) -> u64 { self.params.network_id } /// Get the configured subprotocol name. pub fn subprotocol_name(&self) -> String { self.params.subprotocol_name.clone() } @@ -192,13 +188,8 @@ impl Spec { header.set_gas_limit(self.gas_limit.clone()); header.set_difficulty(self.difficulty.clone()); header.set_seal({ - let seal = { - let mut s = RlpStream::new_list(self.seal_fields); - s.append_raw(&self.seal_rlp, self.seal_fields); - s.out() - }; - let r = Rlp::new(&seal); - (0..self.seal_fields).map(|i| r.at(i).as_raw().to_vec()).collect() + let r = Rlp::new(&self.seal_rlp); + r.iter().map(|f| f.as_raw().to_vec()).collect() }); trace!(target: "spec", "Header hash is {}", header.hash()); header @@ -227,7 +218,6 @@ impl Spec { self.gas_used = g.gas_used; self.timestamp = g.timestamp; self.extra_data = g.extra_data; - self.seal_fields = seal.fields; self.seal_rlp = seal.rlp; self.state_root_memo = RwLock::new(g.state_root); } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index bdcc92bd0..c3d812c46 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -436,6 +436,27 @@ impl Account { } } +// light client storage proof. +impl Account { + /// Prove a storage key's existence or nonexistence in the account's storage + /// trie. + /// `storage_key` is the hash of the desired storage key, meaning + /// this will only work correctly under a secure trie. + /// Returns a merkle proof of the storage trie node with all nodes before `from_level` + /// omitted. + pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result, Box> { + use util::trie::{Trie, TrieDB}; + use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + + let mut recorder = TrieRecorder::with_depth(from_level); + + let trie = try!(TrieDB::new(db, &self.storage_root)); + let _ = try!(trie.get_recorded(&storage_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } +} + impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", PodAccount::from_account(self)) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index db7c318c6..b3d63d0ae 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -16,7 +16,7 @@ use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry; -use util::*; + use receipt::Receipt; use engines::Engine; use env_info::EnvInfo; @@ -30,6 +30,9 @@ use types::state_diff::StateDiff; use transaction::SignedTransaction; use state_db::StateDB; +use util::*; +use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + mod account; mod substate; @@ -758,6 +761,53 @@ impl State { } } +// LES state proof implementations. +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> { + let mut recorder = TrieRecorder::with_depth(from_level); + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let _ = try!(trie.get_recorded(&account_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } + + /// Prove an account's storage key's existence or nonexistence in the state. + /// Returns a merkle proof of the account's storage trie with all nodes before + /// `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> { + // TODO: probably could look into cache somehow but it's keyed by + // address, not sha3(address). + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(Vec::new()), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + acc.prove_storage(account_db.as_hashdb(), storage_key, from_level) + } + + /// 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> { + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let mut acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(None), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + Ok(acc.cache_code(account_db.as_hashdb()).map(|c| (&*c).clone())) + } +} + impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.cache.borrow()) @@ -790,1107 +840,1107 @@ impl Clone for State { #[cfg(test)] mod tests { -use std::sync::Arc; -use std::str::FromStr; -use rustc_serialize::hex::FromHex; -use super::*; -use util::{U256, H256, FixedHash, Address, Hashable}; -use tests::helpers::*; -use devtools::*; -use env_info::EnvInfo; -use spec::*; -use transaction::*; -use util::log::init_log; -use trace::{FlatTrace, TraceError, trace}; -use types::executed::CallType; + use std::sync::Arc; + use std::str::FromStr; + use rustc_serialize::hex::FromHex; + use super::*; + use util::{U256, H256, FixedHash, Address, Hashable}; + use tests::helpers::*; + use devtools::*; + use env_info::EnvInfo; + use spec::*; + use transaction::*; + use util::log::init_log; + use trace::{FlatTrace, TraceError, trace}; + use types::executed::CallType; -#[test] -fn should_apply_create_transaction() { - init_log(); + #[test] + fn should_apply_create_transaction() { + init_log(); - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Create, - value: 100.into(), - data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(), - }.sign(&"".sha3(), None); - - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 0, - action: trace::Action::Create(trace::Create { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - value: 100.into(), - gas: 77412.into(), - init: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], - }), - result: trace::Res::Create(trace::CreateResult { - gas_used: U256::from(3224), - address: Address::from_str("8988167e088c87cd314df6d3c2b83da5acb93ace").unwrap(), - code: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_work_when_cloned() { - init_log(); - - let a = Address::zero(); - - let temp = RandomTempPath::new(); - let mut state = { + let temp = RandomTempPath::new(); let mut state = get_temp_state_in(temp.as_path()); - assert_eq!(state.exists(&a), false); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Create, + value: 100.into(), + data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(), + }.sign(&"".sha3(), None); + + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 0, + action: trace::Action::Create(trace::Create { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + value: 100.into(), + gas: 77412.into(), + init: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], + }), + result: trace::Res::Create(trace::CreateResult { + gas_used: U256::from(3224), + address: Address::from_str("8988167e088c87cd314df6d3c2b83da5acb93ace").unwrap(), + code: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_work_when_cloned() { + init_log(); + + let a = Address::zero(); + + 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); + state.commit().unwrap(); + state.clone() + }; + state.inc_nonce(&a); state.commit().unwrap(); - state.clone() - }; + } - state.inc_nonce(&a); - state.commit().unwrap(); -} + #[test] + fn should_trace_failed_create_transaction() { + init_log(); -#[test] -fn should_trace_failed_create_transaction() { - init_log(); + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Create, - value: 100.into(), - data: FromHex::from_hex("5b600056").unwrap(), - }.sign(&"".sha3(), None); - - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Create(trace::Create { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Create, value: 100.into(), - gas: 78792.into(), - init: vec![91, 96, 0, 86], - }), - result: trace::Res::FailedCreate(TraceError::OutOfGas), - subtraces: 0 - }]; + data: FromHex::from_hex("5b600056").unwrap(), + }.sign(&"".sha3(), None); - assert_eq!(result.trace, expected_trace); -} + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + action: trace::Action::Create(trace::Create { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + value: 100.into(), + gas: 78792.into(), + init: vec![91, 96, 0, 86], + }), + result: trace::Res::FailedCreate(TraceError::OutOfGas), + subtraces: 0 + }]; -#[test] -fn should_trace_call_transaction() { - init_log(); + assert_eq!(result.trace, expected_trace); + } - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); + #[test] + fn should_trace_call_transaction() { + init_log(); - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), None); + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); - state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(3), - output: vec![] - }), - subtraces: 0, - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_basic_call_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(0), - output: vec![] - }), - subtraces: 0, - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_call_transaction_to_builtin() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = &*Spec::new_test().engine; - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0x1.into()), - value: 0.into(), - data: vec![], - }.sign(&"".sha3(), None); - - let result = state.apply(&info, engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: "0000000000000000000000000000000000000001".into(), - value: 0.into(), - gas: 79_000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(3000), - output: vec![] - }), - subtraces: 0, - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_not_trace_subcall_transaction_to_builtin() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = &*Spec::new_test().engine; - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 0.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap()); - let result = state.apply(&info, engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 0.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(28_061), - output: vec![] - }), - subtraces: 0, - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_not_trace_callcode() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = &*Spec::new_test().engine; - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 0.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); - let result = state.apply(&info, engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 0.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: 64.into(), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xa.into(), - value: 0.into(), - gas: 4096.into(), - input: vec![], - call_type: CallType::CallCode, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: 3.into(), - output: vec![], - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_not_trace_delegatecall() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - info.number = 0x789b0; - let engine = &*Spec::new_test().engine; - - println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 0.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap()); - state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); - let result = state.apply(&info, engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 0.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(61), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 0.into(), - gas: 32768.into(), - input: vec![], - call_type: CallType::DelegateCall, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: 3.into(), - output: vec![], - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_failed_call_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::FailedCall(TraceError::OutOfGas), - subtraces: 0, - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_call_with_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(69), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xb.into(), - value: 0.into(), - gas: 78934.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(3), - output: vec![] - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_call_with_basic_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(31761), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xb.into(), - value: 69.into(), - gas: 2300.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult::default()), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_not_trace_call_with_invalid_basic_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), None); - - state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds. - state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(31761), - output: vec![] - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_failed_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![],//600480600b6000396000f35b600056 - }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(79_000), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xb.into(), - value: 0.into(), - gas: 78934.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::FailedCall(TraceError::OutOfGas), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_call_with_subcall_with_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(135), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xb.into(), - value: 0.into(), - gas: 78934.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(69), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0, 0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xb.into(), - to: 0xc.into(), - value: 0.into(), - gas: 78868.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(3), - output: vec![] - }), - }]; - - assert_eq!(result.trace, expected_trace); -} - -#[test] -fn should_trace_failed_subcall_with_subcall_transaction() { - init_log(); - - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); - - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); - - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![],//600480600b6000396000f35b600056 - }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), - value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(79_000), - output: vec![] - }) - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 1, + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap()); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), action: trace::Action::Call(trace::Call { - from: 0xa.into(), - to: 0xb.into(), - value: 0.into(), - gas: 78934.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::FailedCall(TraceError::OutOfGas), - }, FlatTrace { - trace_address: vec![0, 0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Call(trace::Call { - from: 0xb.into(), - to: 0xc.into(), - value: 0.into(), - gas: 78868.into(), - call_type: CallType::Call, - input: vec![], - }), - result: trace::Res::Call(trace::CallResult { - gas_used: U256::from(3), - output: vec![] - }), - }]; + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3), + output: vec![] + }), + subtraces: 0, + }]; - assert_eq!(result.trace, expected_trace); -} + assert_eq!(result.trace, expected_trace); + } -#[test] -fn should_trace_suicide() { - init_log(); + #[test] + fn should_trace_basic_call_transaction() { + init_log(); - let temp = RandomTempPath::new(); - let mut state = get_temp_state_in(temp.as_path()); + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); - let mut info = EnvInfo::default(); - info.gas_limit = 1_000_000.into(); - let engine = TestEngine::new(5); + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); - let t = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 100_000.into(), - action: Action::Call(0xa.into()), - value: 100.into(), - data: vec![], - }.sign(&"".sha3(), 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().as_ref().unwrap(), &100.into(), CleanupMode::NoEmpty); - let result = state.apply(&info, &engine, &t, true).unwrap(); - let expected_trace = vec![FlatTrace { - trace_address: Default::default(), - subtraces: 1, - action: trace::Action::Call(trace::Call { - from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), - to: 0xa.into(), + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), value: 100.into(), - gas: 79000.into(), - input: vec![], - call_type: CallType::Call, - }), - result: trace::Res::Call(trace::CallResult { - gas_used: 3.into(), - output: vec![] - }), - }, FlatTrace { - trace_address: vec![0].into_iter().collect(), - subtraces: 0, - action: trace::Action::Suicide(trace::Suicide { - address: 0xa.into(), - refund_address: 0xb.into(), - balance: 150.into(), - }), - result: trace::Res::None, - }]; + data: vec![], + }.sign(&"".sha3(), None); - assert_eq!(result.trace, expected_trace); -} + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(0), + output: vec![] + }), + subtraces: 0, + }]; -#[test] -fn code_from_database() { - let a = Address::zero(); - let temp = RandomTempPath::new(); - let (root, db) = { + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_call_transaction_to_builtin() { + init_log(); + + let temp = RandomTempPath::new(); 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]); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = &*Spec::new_test().engine; + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0x1.into()), + value: 0.into(), + data: vec![], + }.sign(&"".sha3(), None); + + let result = state.apply(&info, engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: "0000000000000000000000000000000000000001".into(), + value: 0.into(), + gas: 79_000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3000), + output: vec![] + }), + subtraces: 0, + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_not_trace_subcall_transaction_to_builtin() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = &*Spec::new_test().engine; + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 0.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap()); + let result = state.apply(&info, engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 0.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(28_061), + output: vec![] + }), + subtraces: 0, + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_not_trace_callcode() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = &*Spec::new_test().engine; + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 0.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()); + state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); + let result = state.apply(&info, engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 0.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: 64.into(), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xa.into(), + value: 0.into(), + gas: 4096.into(), + input: vec![], + call_type: CallType::CallCode, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: 3.into(), + output: vec![], + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_not_trace_delegatecall() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + info.number = 0x789b0; + let engine = &*Spec::new_test().engine; + + println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 0.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap()); + state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); + let result = state.apply(&info, engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 0.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(61), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 0.into(), + gas: 32768.into(), + input: vec![], + call_type: CallType::DelegateCall, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: 3.into(), + output: vec![], + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_failed_call_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap()); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::FailedCall(TraceError::OutOfGas), + subtraces: 0, + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_call_with_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(69), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xb.into(), + value: 0.into(), + gas: 78934.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3), + output: vec![] + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_call_with_basic_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(31761), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xb.into(), + value: 69.into(), + gas: 2300.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult::default()), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_not_trace_call_with_invalid_basic_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), None); + + state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds. + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(31761), + output: vec![] + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_failed_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![],//600480600b6000396000f35b600056 + }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(79_000), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xb.into(), + value: 0.into(), + gas: 78934.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::FailedCall(TraceError::OutOfGas), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_call_with_subcall_with_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(135), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xb.into(), + value: 0.into(), + gas: 78934.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(69), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0, 0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xb.into(), + to: 0xc.into(), + value: 0.into(), + gas: 78868.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3), + output: vec![] + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_failed_subcall_with_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![],//600480600b6000396000f35b600056 + }.sign(&"".sha3(), 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().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(79_000), + output: vec![] + }) + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: 0xa.into(), + to: 0xb.into(), + value: 0.into(), + gas: 78934.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::FailedCall(TraceError::OutOfGas), + }, FlatTrace { + trace_address: vec![0, 0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Call(trace::Call { + from: 0xb.into(), + to: 0xc.into(), + value: 0.into(), + gas: 78868.into(), + call_type: CallType::Call, + input: vec![], + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3), + output: vec![] + }), + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn should_trace_suicide() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3(), 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().as_ref().unwrap(), &100.into(), CleanupMode::NoEmpty); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: 3.into(), + output: vec![] + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Suicide(trace::Suicide { + address: 0xa.into(), + refund_address: 0xb.into(), + balance: 150.into(), + }), + result: trace::Res::None, + }]; + + assert_eq!(result.trace, expected_trace); + } + + #[test] + fn code_from_database() { + let a = Address::zero(); + 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.commit().unwrap(); + assert_eq!(state.code(&a), 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()))); - state.commit().unwrap(); - assert_eq!(state.code(&a), 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()))); -} + #[test] + fn storage_at_from_database() { + let a = Address::zero(); + 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.commit().unwrap(); + state.drop() + }; -#[test] -fn storage_at_from_database() { - let a = Address::zero(); - 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.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))); + } - 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))); -} + #[test] + fn get_from_database() { + let a = Address::zero(); + 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.commit().unwrap(); + assert_eq!(state.balance(&a), U256::from(69u64)); + state.drop() + }; -#[test] -fn get_from_database() { - let a = Address::zero(); - 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.commit().unwrap(); + let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); assert_eq!(state.balance(&a), 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)); -} - -#[test] -fn remove() { - 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)); - 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)); -} - -#[test] -fn empty_account_is_not_created() { - let a = Address::zero(); - let path = RandomTempPath::new(); - 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.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)); -} - -#[test] -fn empty_account_exists_when_creation_forced() { - let a = Address::zero(); - let path = RandomTempPath::new(); - 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.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)); -} - -#[test] -fn remove_from_database() { - let a = Address::zero(); - let temp = RandomTempPath::new(); - let (root, db) = { - let mut state = get_temp_state_in(temp.as_path()); - state.inc_nonce(&a); - state.commit().unwrap(); - assert_eq!(state.exists(&a), true); assert_eq!(state.nonce(&a), U256::from(1u64)); - state.drop() - }; + } - let (root, db) = { - let mut state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); + #[test] + fn remove() { + 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)); state.kill_account(&a); - state.commit().unwrap(); + assert_eq!(state.exists(&a), false); + assert_eq!(state.exists_and_not_null(&a), false); + assert_eq!(state.nonce(&a), U256::from(0u64)); + } + + #[test] + fn empty_account_is_not_created() { + let a = Address::zero(); + let path = RandomTempPath::new(); + 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.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)); + } + + #[test] + fn empty_account_exists_when_creation_forced() { + let a = Address::zero(); + let path = RandomTempPath::new(); + 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.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)); + } + + #[test] + fn remove_from_database() { + let a = Address::zero(); + let temp = RandomTempPath::new(); + let (root, db) = { + let mut state = get_temp_state_in(temp.as_path()); + state.inc_nonce(&a); + state.commit().unwrap(); + assert_eq!(state.exists(&a), true); + assert_eq!(state.nonce(&a), 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)); + state.kill_account(&a); + state.commit().unwrap(); + assert_eq!(state.exists(&a), false); + assert_eq!(state.nonce(&a), 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)); - 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)); -} + #[test] + fn alter_balance() { + let mut state_result = get_temp_state(); + 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.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)); + 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)); + state.commit().unwrap(); + assert_eq!(state.balance(&a), U256::from(9u64)); + assert_eq!(state.balance(&b), U256::from(18u64)); + } -#[test] -fn alter_balance() { - let mut state_result = get_temp_state(); - 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.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)); - 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)); - state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(9u64)); - assert_eq!(state.balance(&b), U256::from(18u64)); -} + #[test] + fn alter_nonce() { + 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.commit().unwrap(); + assert_eq!(state.nonce(&a), U256::from(2u64)); + state.inc_nonce(&a); + assert_eq!(state.nonce(&a), U256::from(3u64)); + state.commit().unwrap(); + assert_eq!(state.nonce(&a), U256::from(3u64)); + } -#[test] -fn alter_nonce() { - 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.commit().unwrap(); - assert_eq!(state.nonce(&a), U256::from(2u64)); - state.inc_nonce(&a); - assert_eq!(state.nonce(&a), U256::from(3u64)); - state.commit().unwrap(); - assert_eq!(state.nonce(&a), U256::from(3u64)); -} + #[test] + fn balance_nonce() { + 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)); + state.commit().unwrap(); + assert_eq!(state.balance(&a), U256::from(0u64)); + assert_eq!(state.nonce(&a), U256::from(0u64)); + } -#[test] -fn balance_nonce() { - 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)); - state.commit().unwrap(); - assert_eq!(state.balance(&a), U256::from(0u64)); - assert_eq!(state.nonce(&a), U256::from(0u64)); -} + #[test] + fn ensure_cached() { + let mut state_result = get_temp_state(); + let mut state = state_result.reference_mut(); + let a = Address::zero(); + state.require(&a, false); + state.commit().unwrap(); + assert_eq!(state.root().hex(), "0ce23f3c809de377b008a4a3ee94a0834aac8bec1f86e28ffe4fdb5a15b0c785"); + } -#[test] -fn ensure_cached() { - let mut state_result = get_temp_state(); - let mut state = state_result.reference_mut(); - let a = Address::zero(); - state.require(&a, false); - state.commit().unwrap(); - assert_eq!(state.root().hex(), "0ce23f3c809de377b008a4a3ee94a0834aac8bec1f86e28ffe4fdb5a15b0c785"); -} + #[test] + fn checkpoint_basic() { + let mut state_result = get_temp_state(); + 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.discard_checkpoint(); + assert_eq!(state.balance(&a), U256::from(69u64)); + state.checkpoint(); + state.add_balance(&a, &U256::from(1u64), CleanupMode::NoEmpty); + assert_eq!(state.balance(&a), U256::from(70u64)); + state.revert_to_checkpoint(); + assert_eq!(state.balance(&a), U256::from(69u64)); + } -#[test] -fn checkpoint_basic() { - let mut state_result = get_temp_state(); - 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.discard_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); - state.checkpoint(); - state.add_balance(&a, &U256::from(1u64), CleanupMode::NoEmpty); - assert_eq!(state.balance(&a), U256::from(70u64)); - state.revert_to_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); -} + #[test] + fn checkpoint_nested() { + let mut state_result = get_temp_state(); + let mut state = state_result.reference_mut(); + 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.discard_checkpoint(); + assert_eq!(state.balance(&a), U256::from(69u64)); + state.revert_to_checkpoint(); + assert_eq!(state.balance(&a), U256::from(0)); + } -#[test] -fn checkpoint_nested() { - let mut state_result = get_temp_state(); - let mut state = state_result.reference_mut(); - 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.discard_checkpoint(); - assert_eq!(state.balance(&a), U256::from(69u64)); - state.revert_to_checkpoint(); - assert_eq!(state.balance(&a), U256::from(0)); -} + #[test] + fn create_empty() { + let mut state_result = get_temp_state(); + let mut state = state_result.reference_mut(); + state.commit().unwrap(); + assert_eq!(state.root().hex(), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); + } -#[test] -fn create_empty() { - let mut state_result = get_temp_state(); - let mut state = state_result.reference_mut(); - state.commit().unwrap(); - assert_eq!(state.root().hex(), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); -} + #[test] + fn should_not_panic_on_state_diff_with_storage() { + let state = get_temp_state(); + let mut state = state.reference().clone(); -#[test] -fn should_not_panic_on_state_diff_with_storage() { - let state = get_temp_state(); - 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()); - 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()); + let mut new_state = state.clone(); + new_state.set_storage(&a, 0xb.into(), 0xd.into()); - let mut new_state = state.clone(); - new_state.set_storage(&a, 0xb.into(), 0xd.into()); - - new_state.diff_from(state); -} + new_state.diff_from(state); + } } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 77a6f117a..e952fe27a 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -286,7 +286,7 @@ fn new_db(path: &str) -> Arc { pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); for block_order in 1..block_number { @@ -304,7 +304,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); @@ -323,7 +323,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); GuardedTempResult:: { _temp: temp, diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 6ef67009a..d462d3cb9 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -34,3 +34,4 @@ pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; pub mod mode; +pub mod pruning_info; \ No newline at end of file diff --git a/ethcore/src/types/pruning_info.rs b/ethcore/src/types/pruning_info.rs new file mode 100644 index 000000000..40564f488 --- /dev/null +++ b/ethcore/src/types/pruning_info.rs @@ -0,0 +1,30 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Information about portions of the state and chain which the client may serve. +//! +//! Currently assumes that a client will store everything past a certain point +//! or everything. Will be extended in the future to support a definition +//! of which portions of the ancient chain and current state trie are stored as well. + +/// Client pruning info. See module-level docs for more details. +#[derive(Debug, Clone, Binary)] +pub struct PruningInfo { + /// The first block which everything can be served after. + pub earliest_chain: u64, + /// The first block where state requests may be served. + pub earliest_state: u64, +} \ No newline at end of file diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 7dba4da53..1c6ef92e3 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -72,7 +72,7 @@ pub struct Transaction { impl Transaction { /// Append object with a without signature into RLP stream - pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, network_id: Option) { + pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, network_id: Option) { s.begin_list(if network_id.is_none() { 6 } else { 9 }); s.append(&self.nonce); s.append(&self.gas_price); @@ -140,26 +140,26 @@ impl From for SignedTransaction { impl Transaction { /// The message hash of the transaction. - pub fn hash(&self, network_id: Option) -> H256 { + pub fn hash(&self, network_id: Option) -> H256 { let mut stream = RlpStream::new(); self.rlp_append_unsigned_transaction(&mut stream, network_id); stream.out().sha3() } /// Signs the transaction as coming from `sender`. - pub fn sign(self, secret: &Secret, network_id: Option) -> SignedTransaction { + pub fn sign(self, secret: &Secret, network_id: Option) -> SignedTransaction { let sig = ::ethkey::sign(secret, &self.hash(network_id)) .expect("data is valid and context has signing capabilities; qed"); self.with_signature(sig, network_id) } /// Signs the transaction with signature. - pub fn with_signature(self, sig: Signature, network_id: Option) -> SignedTransaction { + pub fn with_signature(self, sig: Signature, network_id: Option) -> SignedTransaction { SignedTransaction { unsigned: self, r: sig.r().into(), s: sig.s().into(), - v: sig.v() + if let Some(n) = network_id { 35 + n * 2 } else { 27 }, + v: sig.v() as u64 + if let Some(n) = network_id { 35 + n * 2 } else { 27 }, hash: Cell::new(None), sender: Cell::new(None), } @@ -211,7 +211,7 @@ pub struct SignedTransaction { unsigned: Transaction, /// The V field of the signature; the LS bit described which half of the curve our point falls /// in. The MS bits describe which network this transaction is for. If 27/28, its for all networks. - v: u8, + v: u64, /// The R field of the signature; helps describe the point on the curve. r: U256, /// The S field of the signature; helps describe the point on the curve. @@ -302,10 +302,13 @@ impl SignedTransaction { } /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid. - pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => (v - 1) % 2, _ => 4 } } + pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => ((v - 1) % 2) as u8, _ => 4 } } + + /// The `v` value that appears in the RLP. + pub fn original_v(&self) -> u64 { self.v } /// The network ID, or `None` if this is a global transaction. - pub fn network_id(&self) -> Option { + pub fn network_id(&self) -> Option { match self.v { v if v > 36 => Some((v - 35) / 2), _ => None, diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 686a1d093..adc93484c 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -17,7 +17,7 @@ //! A queue of blocks. Sits between network or other I/O and the `BlockChain`. //! Sorts them ready for blockchain insertion. -use std::thread::{JoinHandle, self}; +use std::thread::{self, JoinHandle}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering}; use std::sync::{Condvar as SCondvar, Mutex as SMutex}; use util::*; @@ -53,6 +53,8 @@ pub struct Config { /// Maximum heap memory to use. /// When the limit is reached, is_full returns true. pub max_mem_use: usize, + /// Settings for the number of verifiers and adaptation strategy. + pub verifier_settings: VerifierSettings, } impl Default for Config { @@ -60,39 +62,35 @@ impl Default for Config { Config { max_queue_size: 30000, max_mem_use: 50 * 1024 * 1024, + verifier_settings: VerifierSettings::default(), } } } -struct VerifierHandle { - deleting: Arc, - sleep: Arc, - thread: JoinHandle<()>, +/// Verifier settings. +#[derive(Debug, PartialEq, Clone)] +pub struct VerifierSettings { + /// Whether to scale amount of verifiers according to load. + // Todo: replace w/ strategy enum? + pub scale_verifiers: bool, + /// Beginning amount of verifiers. + pub num_verifiers: usize, } -impl VerifierHandle { - // signal to the verifier thread that it should sleep. - fn sleep(&self) { - self.sleep.store(true, AtomicOrdering::SeqCst); +impl Default for VerifierSettings { + fn default() -> Self { + VerifierSettings { + scale_verifiers: false, + num_verifiers: MAX_VERIFIERS, + } } +} - // signal to the verifier thread that it should wake up. - fn wake_up(&self) { - self.sleep.store(false, AtomicOrdering::SeqCst); - self.thread.thread().unpark(); - } - - // signal to the verifier thread that it should conclude its - // operations. - fn conclude(&self) { - self.wake_up(); - self.deleting.store(true, AtomicOrdering::Release); - } - - // join the verifier thread. - fn join(self) { - self.thread.join().expect("Verifier thread panicked"); - } +// pool states +enum State { + // all threads with id < inner value are to work. + Work(usize), + Exit, } /// An item which is in the process of being verified. @@ -131,7 +129,6 @@ pub struct VerificationQueue { engine: Arc, more_to_verify: Arc, verification: Arc>, - verifiers: Mutex<(Vec, usize)>, deleting: Arc, ready_signal: Arc, empty: Arc, @@ -139,6 +136,9 @@ pub struct VerificationQueue { ticks_since_adjustment: AtomicUsize, max_queue_size: usize, max_mem_use: usize, + scale_verifiers: bool, + verifier_handles: Vec>, + state: Arc<(Mutex, Condvar)>, } struct QueueSignal { @@ -221,43 +221,45 @@ impl VerificationQueue { }); let empty = Arc::new(SCondvar::new()); let panic_handler = PanicHandler::new_in_arc(); + let scale_verifiers = config.verifier_settings.scale_verifiers; - let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS); - let default_amount = max(::num_cpus::get(), 3) - 2; - let mut verifiers = Vec::with_capacity(max_verifiers); + let num_cpus = ::num_cpus::get(); + let max_verifiers = min(num_cpus, MAX_VERIFIERS); + let default_amount = max(1, min(max_verifiers, config.verifier_settings.num_verifiers)); + let state = Arc::new((Mutex::new(State::Work(default_amount)), Condvar::new())); + let mut verifier_handles = Vec::with_capacity(max_verifiers); debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount); + debug!(target: "verification", "Verifier auto-scaling {}", if scale_verifiers { "enabled" } else { "disabled" }); for i in 0..max_verifiers { debug!(target: "verification", "Adding verification thread #{}", i); - let deleting = deleting.clone(); let panic_handler = panic_handler.clone(); let verification = verification.clone(); let engine = engine.clone(); let wait = more_to_verify.clone(); let ready = ready_signal.clone(); let empty = empty.clone(); + let state = state.clone(); - // enable only the first few verifiers. - let sleep = if i < default_amount { - Arc::new(AtomicBool::new(false)) - } else { - Arc::new(AtomicBool::new(true)) - }; - - verifiers.push(VerifierHandle { - deleting: deleting.clone(), - sleep: sleep.clone(), - thread: thread::Builder::new() - .name(format!("Verifier #{}", i)) - .spawn(move || { - panic_handler.catch_panic(move || { - VerificationQueue::verify(verification, engine, wait, ready, deleting, empty, sleep) - }).unwrap() - }) - .expect("Failed to create verifier thread.") - }); + let handle = thread::Builder::new() + .name(format!("Verifier #{}", i)) + .spawn(move || { + panic_handler.catch_panic(move || { + VerificationQueue::verify( + verification, + engine, + wait, + ready, + empty, + state, + i, + ) + }).unwrap() + }) + .expect("Failed to create verifier thread."); + verifier_handles.push(handle); } VerificationQueue { @@ -266,13 +268,15 @@ impl VerificationQueue { ready_signal: ready_signal, more_to_verify: more_to_verify, verification: verification, - verifiers: Mutex::new((verifiers, default_amount)), deleting: deleting, processing: RwLock::new(HashSet::new()), empty: empty, ticks_since_adjustment: AtomicUsize::new(0), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), + scale_verifiers: scale_verifiers, + verifier_handles: verifier_handles, + state: state, } } @@ -281,23 +285,30 @@ impl VerificationQueue { engine: Arc, wait: Arc, ready: Arc, - deleting: Arc, empty: Arc, - sleep: Arc, + state: Arc<(Mutex, Condvar)>, + id: usize, ) { - while !deleting.load(AtomicOrdering::Acquire) { + loop { + // check current state. { - while sleep.load(AtomicOrdering::SeqCst) { - trace!(target: "verification", "Verifier sleeping"); - ::std::thread::park(); - trace!(target: "verification", "Verifier waking up"); + let mut cur_state = state.0.lock(); + while let State::Work(x) = *cur_state { + // sleep until this thread is required. + if id < x { break } - if deleting.load(AtomicOrdering::Acquire) { - return; - } + debug!(target: "verification", "verifier {} sleeping", id); + state.1.wait(&mut cur_state); + debug!(target: "verification", "verifier {} waking up", id); + } + + if let State::Exit = *cur_state { + debug!(target: "verification", "verifier {} exiting", id); + break; } } + // wait for work if empty. { let mut more_to_verify = verification.more_to_verify.lock().unwrap(); @@ -305,15 +316,22 @@ impl VerificationQueue { empty.notify_all(); } - while verification.unverified.lock().is_empty() && !deleting.load(AtomicOrdering::Acquire) { + while verification.unverified.lock().is_empty() { + if let State::Exit = *state.0.lock() { + debug!(target: "verification", "verifier {} exiting", id); + return; + } + more_to_verify = wait.wait(more_to_verify).unwrap(); } - if deleting.load(AtomicOrdering::Acquire) { + if let State::Exit = *state.0.lock() { + debug!(target: "verification", "verifier {} exiting", id); return; } } + // do work. let item = { // acquire these locks before getting the item to verify. let mut unverified = verification.unverified.lock(); @@ -568,6 +586,14 @@ impl VerificationQueue { } } + /// Get the current number of working verifiers. + pub fn num_verifiers(&self) -> usize { + match *self.state.0.lock() { + State::Work(x) => x, + State::Exit => panic!("state only set to exit on drop; queue live now; qed"), + } + } + /// Optimise memory footprint of the heap fields, and adjust the number of threads /// to better suit the workload. pub fn collect_garbage(&self) { @@ -598,13 +624,15 @@ impl VerificationQueue { self.processing.write().shrink_to_fit(); + if !self.scale_verifiers { return } + if self.ticks_since_adjustment.fetch_add(1, AtomicOrdering::SeqCst) + 1 >= READJUSTMENT_PERIOD { self.ticks_since_adjustment.store(0, AtomicOrdering::SeqCst); } else { return; } - let current = self.verifiers.lock().1; + let current = self.num_verifiers(); let diff = (v_len - u_len).abs(); let total = v_len + u_len; @@ -626,27 +654,14 @@ impl VerificationQueue { // possible, never going over the amount of initially allocated threads // or below 1. fn scale_verifiers(&self, target: usize) { - let mut verifiers = self.verifiers.lock(); - let &mut (ref mut verifiers, ref mut verifier_count) = &mut *verifiers; - - let target = min(verifiers.len(), target); + let current = self.num_verifiers(); + let target = min(self.verifier_handles.len(), target); let target = max(1, target); - debug!(target: "verification", "Scaling from {} to {} verifiers", verifier_count, target); + debug!(target: "verification", "Scaling from {} to {} verifiers", current, target); - // scaling up - for i in *verifier_count..target { - debug!(target: "verification", "Waking up verifier {}", i); - verifiers[i].wake_up(); - } - - // scaling down. - for i in target..*verifier_count { - debug!(target: "verification", "Putting verifier {} to sleep", i); - verifiers[i].sleep(); - } - - *verifier_count = target; + *self.state.0.lock() = State::Work(target); + self.state.1.notify_all(); } } @@ -660,22 +675,18 @@ impl Drop for VerificationQueue { fn drop(&mut self) { trace!(target: "shutdown", "[VerificationQueue] Closing..."); self.clear(); - self.deleting.store(true, AtomicOrdering::Release); + self.deleting.store(true, AtomicOrdering::SeqCst); - let mut verifiers = self.verifiers.get_mut(); - let mut verifiers = &mut verifiers.0; - - // first pass to signal conclusion. must be done before - // notify or deadlock possible. - for handle in verifiers.iter() { - handle.conclude(); - } + // set exit state; should be done before `more_to_verify` notification. + *self.state.0.lock() = State::Exit; + self.state.1.notify_all(); + // wake up all threads waiting for more work. self.more_to_verify.notify_all(); - // second pass to join. - for handle in verifiers.drain(..) { - handle.join(); + // wait for all verifier threads to join. + for thread in self.verifier_handles.drain(..) { + thread.join().expect("Propagating verifier thread panic on shutdown"); } trace!(target: "shutdown", "[VerificationQueue] Closed."); @@ -687,16 +698,21 @@ mod tests { use util::*; use io::*; use spec::*; - use super::{BlockQueue, Config}; + use super::{BlockQueue, Config, State}; use super::kind::blocks::Unverified; use tests::helpers::*; use error::*; use views::*; - fn get_test_queue() -> BlockQueue { + // create a test block queue. + // auto_scaling enables verifier adjustment. + fn get_test_queue(auto_scale: bool) -> BlockQueue { let spec = get_test_spec(); let engine = spec.engine; - BlockQueue::new(Config::default(), engine, IoChannel::disconnected(), true) + + let mut config = Config::default(); + config.verifier_settings.scale_verifiers = auto_scale; + BlockQueue::new(config, engine, IoChannel::disconnected(), true) } #[test] @@ -709,7 +725,7 @@ mod tests { #[test] fn can_import_blocks() { - let queue = get_test_queue(); + let queue = get_test_queue(false); if let Err(e) = queue.import(Unverified::new(get_good_dummy_block())) { panic!("error importing block that is valid by definition({:?})", e); } @@ -717,7 +733,7 @@ mod tests { #[test] fn returns_error_for_duplicates() { - let queue = get_test_queue(); + let queue = get_test_queue(false); if let Err(e) = queue.import(Unverified::new(get_good_dummy_block())) { panic!("error importing block that is valid by definition({:?})", e); } @@ -736,7 +752,7 @@ mod tests { #[test] fn returns_ok_for_drained_duplicates() { - let queue = get_test_queue(); + let queue = get_test_queue(false); let block = get_good_dummy_block(); let hash = BlockView::new(&block).header().hash().clone(); if let Err(e) = queue.import(Unverified::new(block)) { @@ -753,7 +769,7 @@ mod tests { #[test] fn returns_empty_once_finished() { - let queue = get_test_queue(); + let queue = get_test_queue(false); queue.import(Unverified::new(get_good_dummy_block())) .expect("error importing block that is valid by definition"); queue.flush(); @@ -781,30 +797,23 @@ mod tests { fn scaling_limits() { use super::MAX_VERIFIERS; - let queue = get_test_queue(); + let queue = get_test_queue(true); queue.scale_verifiers(MAX_VERIFIERS + 1); - assert!(queue.verifiers.lock().1 < MAX_VERIFIERS + 1); + assert!(queue.num_verifiers() < MAX_VERIFIERS + 1); queue.scale_verifiers(0); - assert!(queue.verifiers.lock().1 == 1); + assert!(queue.num_verifiers() == 1); } #[test] fn readjust_verifiers() { - let queue = get_test_queue(); + let queue = get_test_queue(true); // put all the verifiers to sleep to ensure // the test isn't timing sensitive. - let num_verifiers = { - let verifiers = queue.verifiers.lock(); - for i in 0..verifiers.1 { - verifiers.0[i].sleep(); - } - - verifiers.1 - }; + *queue.state.0.lock() = State::Work(0); for block in get_good_dummy_block_seq(5000) { queue.import(Unverified::new(block)).expect("Block good by definition; qed"); @@ -812,20 +821,12 @@ mod tests { // almost all unverified == bump verifier count. queue.collect_garbage(); - assert_eq!(queue.verifiers.lock().1, num_verifiers + 1); - - // wake them up again and verify everything. - { - let verifiers = queue.verifiers.lock(); - for i in 0..verifiers.1 { - verifiers.0[i].wake_up(); - } - } + assert_eq!(queue.num_verifiers(), 1); queue.flush(); // nothing to verify == use minimum number of verifiers. queue.collect_garbage(); - assert_eq!(queue.verifiers.lock().1, 1); + assert_eq!(queue.num_verifiers(), 1); } } diff --git a/evmjit/Cargo.toml b/evmjit/Cargo.toml index b7a4d1447..12c57a769 100644 --- a/evmjit/Cargo.toml +++ b/evmjit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evmjit" -version = "1.4.0" +version = "1.5.0" authors = ["debris "] [lib] diff --git a/ipc/codegen/Cargo.toml b/ipc/codegen/Cargo.toml index 1c61db49f..2867609d6 100644 --- a/ipc/codegen/Cargo.toml +++ b/ipc/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-codegen" -version = "1.4.0" +version = "1.5.0" authors = ["Nikolay Volf"] 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 b358eb23a..32171bbf4 100644 --- a/ipc/nano/Cargo.toml +++ b/ipc/nano/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-nano" -version = "1.4.0" +version = "1.5.0" authors = ["Nikolay Volf "] license = "GPL-3.0" diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml index 9e0dfd91b..1aecb3292 100644 --- a/ipc/rpc/Cargo.toml +++ b/ipc/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc" -version = "1.4.0" +version = "1.5.0" authors = ["Nikolay Volf "] license = "GPL-3.0" diff --git a/js/.babelrc b/js/.babelrc index 2298d98c0..8147da435 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -7,6 +7,7 @@ "transform-runtime", "transform-decorators-legacy", "transform-class-properties", + "transform-object-rest-spread", "lodash" ], "retainLines": true, diff --git a/js/.npmrc b/js/.npmrc index 3f7b4cf25..a00908d4f 100644 --- a/js/.npmrc +++ b/js/.npmrc @@ -1 +1 @@ -save-prefix='~' +save-prefix='' diff --git a/js/assets/images/certifications/unknown.svg b/js/assets/images/certifications/unknown.svg new file mode 100644 index 000000000..1554bcc25 --- /dev/null +++ b/js/assets/images/certifications/unknown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/js/package.json b/js/package.json index a6f705ec6..4be14d531 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.78", + "version": "0.2.102", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -47,132 +47,128 @@ "prepush": "npm run lint:cached" }, "devDependencies": { - "babel-cli": "~6.18.0", - "babel-core": "~6.18.2", - "babel-eslint": "~7.1.0", - "babel-loader": "~6.2.3", - "babel-plugin-lodash": "~3.2.2", - "babel-plugin-transform-class-properties": "~6.19.0", - "babel-plugin-transform-decorators-legacy": "~1.3.4", - "babel-plugin-transform-react-remove-prop-types": "~0.2.9", - "babel-plugin-transform-runtime": "~6.15.0", - "babel-polyfill": "~6.16.0", - "babel-preset-es2015": "~6.18.0", - "babel-preset-es2015-rollup": "~1.2.0", - "babel-preset-es2016": "~6.16.0", - "babel-preset-es2017": "~6.16.0", - "babel-preset-react": "~6.16.0", - "babel-preset-stage-0": "~6.16.0", + "babel-cli": "6.18.0", + "babel-core": "6.20.0", + "babel-eslint": "7.1.1", + "babel-loader": "6.2.8", + "babel-plugin-lodash": "3.2.10", + "babel-plugin-transform-class-properties": "6.18.0", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-plugin-transform-object-rest-spread": "6.20.2", + "babel-plugin-transform-react-remove-prop-types": "0.2.11", + "babel-plugin-transform-runtime": "6.15.0", + "babel-polyfill": "6.20.0", + "babel-preset-es2015": "6.18.0", + "babel-preset-es2016": "6.16.0", + "babel-preset-es2017": "6.16.0", + "babel-preset-react": "6.16.0", + "babel-preset-stage-0": "6.16.0", "babel-register": "6.18.0", - "babel-runtime": "~6.18.0", - "chai": "~3.5.0", - "chai-enzyme": "0.4.2", - "cheerio": "0.20.0", - "copy-webpack-plugin": "~4.0.0", - "core-js": "~2.4.1", - "coveralls": "~2.11.11", - "css-loader": "~0.26.0", - "enzyme": "2.3.0", - "eslint": "~3.10.2", - "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.7.1", - "eslint-plugin-standard": "~2.0.0", - "express": "~4.14.0", + "babel-runtime": "6.20.0", + "chai": "3.5.0", + "chai-enzyme": "0.6.1", + "circular-dependency-plugin": "2.0.0", + "copy-webpack-plugin": "4.0.1", + "core-js": "2.4.1", + "coveralls": "2.11.15", + "css-loader": "0.26.1", + "ejs-loader": "0.3.0", + "enzyme": "2.6.0", + "eslint": "3.11.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.7.1", + "eslint-plugin-standard": "2.0.1", + "express": "4.14.0", "extract-loader": "0.1.0", - "extract-text-webpack-plugin": "~2.0.0-beta.4", - "file-loader": "~0.9.0", - "fs-extra": "~0.30.0", - "happypack": "~3.0.0", - "history": "~2.0.0", - "html-loader": "~0.4.4", - "html-webpack-plugin": "~2.24.1", - "http-proxy-middleware": "~0.17.2", - "husky": "~0.11.9", - "ignore-styles": "2.0.0", - "image-webpack-loader": "~3.0.0", - "istanbul": "~1.0.0-alpha.2", - "jsdom": "9.2.1", - "json-loader": "~0.5.4", - "mocha": "~3.0.0-1", + "extract-text-webpack-plugin": "2.0.0-beta.4", + "file-loader": "0.9.0", + "happypack": "3.0.0", + "html-loader": "0.4.4", + "html-webpack-plugin": "2.24.1", + "http-proxy-middleware": "0.17.2", + "husky": "0.11.9", + "ignore-styles": "5.0.1", + "image-webpack-loader": "3.0.0", + "istanbul": "1.0.0-alpha.2", + "jsdom": "9.8.3", + "json-loader": "0.5.4", + "mocha": "3.2.0", "mock-local-storage": "1.0.2", - "mock-socket": "~3.0.1", - "nock": "~8.0.0", + "mock-socket": "6.0.3", + "nock": "9.0.2", "postcss-import": "8.1.0", - "postcss-loader": "~1.1.1", - "postcss-nested": "~1.0.0", - "postcss-simple-vars": "~3.0.0", - "progress": "~1.1.8", - "raw-loader": "~0.5.1", - "react-addons-perf": "~15.3.2", - "react-addons-test-utils": "~15.3.2", - "react-dom": "~15.3.2", - "react-hot-loader": "~3.0.0-beta.6", - "rucksack-css": "~0.8.6", - "sinon": "~1.17.4", - "sinon-as-promised": "~4.0.2", - "sinon-chai": "~2.8.0", - "style-loader": "~0.13.0", - "url-loader": "~0.5.7", - "webpack": "~2.1.0-beta.27", - "webpack-dev-middleware": "~1.8.4", + "postcss-loader": "1.1.1", + "postcss-nested": "1.0.0", + "postcss-simple-vars": "3.0.0", + "progress": "1.1.8", + "raw-loader": "0.5.1", + "react-addons-perf": "15.4.1", + "react-addons-test-utils": "15.4.1", + "react-hot-loader": "3.0.0-beta.6", + "rucksack-css": "0.9.1", + "sinon": "1.17.6", + "sinon-as-promised": "4.0.2", + "sinon-chai": "2.8.0", + "style-loader": "0.13.1", + "url-loader": "0.5.7", + "webpack": "2.1.0-beta.27", + "webpack-dev-middleware": "1.8.4", "webpack-error-notification": "0.1.6", - "webpack-hot-middleware": "~2.13.2", - "websocket": "~1.0.23" + "webpack-hot-middleware": "2.13.2", + "websocket": "1.0.23" }, "dependencies": { - "bignumber.js": "~2.3.0", + "bignumber.js": "3.0.1", "blockies": "0.0.2", - "brace": "~0.9.0", - "bytes": "~2.4.0", - "chart.js": "~2.3.0", - "es6-error": "~4.0.0", - "es6-promise": "~3.2.1", - "ethereumjs-tx": "~1.1.2", - "eventemitter3": "~2.0.2", - "file-saver": "~1.3.3", - "format-json": "~1.0.3", - "format-number": "~2.0.1", - "geopattern": "~1.2.3", - "isomorphic-fetch": "~2.2.1", - "js-sha3": "~0.5.2", - "lodash": "~4.11.1", - "marked": "~0.3.6", - "material-ui": "0.16.1", - "material-ui-chip-input": "~0.8.0", - "mobx": "~2.6.1", - "mobx-react": "~3.5.8", - "mobx-react-devtools": "~4.2.9", - "moment": "~2.14.1", - "phoneformat.js": "~1.0.3", - "qs": "~6.3.0", - "react": "~15.3.2", - "react-ace": "~4.0.0", - "react-addons-css-transition-group": "~15.3.2", - "react-chartjs-2": "~1.5.0", - "react-copy-to-clipboard": "~4.2.3", - "react-dom": "~15.3.2", - "react-dropzone": "~3.7.3", - "react-redux": "~4.4.5", - "react-router": "~2.6.1", - "react-router-redux": "~4.0.5", - "react-tap-event-plugin": "~1.0.0", - "react-tooltip": "~2.0.3", - "recharts": "~0.15.2", - "redux": "~3.5.2", - "redux-actions": "~0.10.1", - "redux-thunk": "~2.1.0", - "rlp": "~2.0.0", - "scryptsy": "~2.0.0", + "brace": "0.9.0", + "bytes": "2.4.0", + "es6-error": "4.0.0", + "es6-promise": "4.0.5", + "ethereumjs-tx": "1.1.4", + "eventemitter3": "2.0.2", + "file-saver": "1.3.3", + "format-json": "1.0.3", + "format-number": "2.0.1", + "geopattern": "1.2.3", + "isomorphic-fetch": "2.2.1", + "js-sha3": "0.5.5", + "lodash": "4.17.2", + "marked": "0.3.6", + "material-ui": "0.16.4", + "material-ui-chip-input": "0.11.1", + "mobx": "2.6.4", + "mobx-react": "4.0.3", + "mobx-react-devtools": "4.2.10", + "moment": "2.17.0", + "phoneformat.js": "1.0.3", + "qs": "6.3.0", + "react": "15.4.1", + "react-ace": "4.1.0", + "react-addons-css-transition-group": "15.4.1", + "react-copy-to-clipboard": "4.2.3", + "react-dom": "15.4.1", + "react-dropzone": "3.7.3", + "react-redux": "4.4.6", + "react-router": "3.0.0", + "react-router-redux": "4.0.7", + "react-tap-event-plugin": "2.0.1", + "react-tooltip": "3.2.2", + "recharts": "0.15.2", + "redux": "3.6.0", + "redux-actions": "1.1.0", + "redux-thunk": "2.1.0", + "rlp": "2.0.0", + "scryptsy": "2.0.0", "solc": "ngotchac/solc-js", - "store": "~1.3.20", - "utf8": "~2.1.1", - "valid-url": "~1.0.9", - "validator": "~5.7.0", - "web3": "~0.17.0-beta", - "whatwg-fetch": "~1.0.0", - "worker-loader": "~0.7.1" + "store": "1.3.20", + "utf8": "2.1.2", + "valid-url": "1.0.9", + "validator": "6.2.0", + "web3": "0.17.0-beta", + "whatwg-fetch": "2.0.1", + "worker-loader": "0.7.1" } } diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 3ff4a577c..1cf3095ef 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -34,11 +34,18 @@ git fetch origin 2>$GITLOG git checkout -b $BRANCH echo "*** Committing compiled files for $UTCDATE" +mv build ../build.new git add . -git commit -m "$UTCDATE" +git commit -m "$UTCDATE [update]" +git merge origin/$BRANCH -X ours --commit -m "$UTCDATE [merge]" +git rm -r build +rm -rf build +git commit -m "$UTCDATE [cleanup]" +mv ../build.new build +git add . +git commit -m "$UTCDATE [release]" echo "*** Merging remote" -git merge origin/$BRANCH -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$BRANCH 2>$GITLOG PRECOMPILED_HASH=`git rev-parse HEAD` diff --git a/js/scripts/test.js b/js/scripts/test.js new file mode 100644 index 000000000..78f2f99bd --- /dev/null +++ b/js/scripts/test.js @@ -0,0 +1 @@ +// test script 6 diff --git a/js/src/abi/util/slice.js b/js/src/abi/util/slice.js index 417efea54..f4bdf38e2 100644 --- a/js/src/abi/util/slice.js +++ b/js/src/abi/util/slice.js @@ -27,9 +27,5 @@ export function sliceData (_data) { data = padAddress(''); } - if (data.length % 64) { - throw new Error(`Invalid data length (not mod 64) passed to sliceData, ${data}, % 64 == ${data.length % 64}`); - } - return data.match(/.{1,64}/g); } diff --git a/js/src/abi/util/slice.spec.js b/js/src/abi/util/slice.spec.js index 92608c590..d653c6901 100644 --- a/js/src/abi/util/slice.spec.js +++ b/js/src/abi/util/slice.spec.js @@ -21,10 +21,6 @@ describe('abi/util/slice', () => { const slice1 = '131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'; const slice2 = '2124768576358735263578356373526387638357635873563586353756358763'; - it('throws an error on mod 64 != 0', () => { - expect(() => sliceData('123')).to.throw(/sliceData/); - }); - it('returns an empty array when length === 0', () => { expect(sliceData('')).to.deep.equal([]); }); diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index c1ba8498d..1185199e1 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -189,15 +189,21 @@ export default class Contract { }); } - _encodeOptions (func, options, values) { + getCallData = (func, options, values) => { + let data = options.data; + const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null; const call = tokens ? func.encodeCall(tokens) : null; - if (options.data && options.data.substr(0, 2) === '0x') { - options.data = options.data.substr(2); + if (data && data.substr(0, 2) === '0x') { + data = data.substr(2); } - options.data = `0x${options.data || ''}${call || ''}`; + return `0x${data || ''}${call || ''}`; + } + + _encodeOptions (func, options, values) { + options.data = this.getCallData(func, options, values); return options; } @@ -209,8 +215,10 @@ export default class Contract { _bindFunction = (func) => { func.call = (options, values = []) => { + const callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + return this._api.eth - .call(this._encodeOptions(func, this._addOptionsTo(options), values)) + .call(callParams) .then((encoded) => func.decodeOutput(encoded)) .then((tokens) => tokens.map((token) => token.value)) .then((returns) => returns.length === 1 ? returns[0] : returns); @@ -240,9 +248,28 @@ export default class Contract { return this.unsubscribe(subscriptionId); }; + event.getAllLogs = (options = {}) => { + return this.getAllLogs(event); + }; + return event; } + getAllLogs (event, _options) { + // Options as first parameter + if (!_options && event && event.topics) { + return this.getAllLogs(null, event); + } + + const options = this._getFilterOptions(event, _options); + options.fromBlock = 0; + options.toBlock = 'latest'; + + return this._api.eth + .getLogs(options) + .then((logs) => this.parseEventLogs(logs)); + } + _findEvent (eventName = null) { const event = eventName ? this._events.find((evt) => evt.name === eventName) @@ -256,7 +283,7 @@ export default class Contract { return event; } - _createEthFilter (event = null, _options) { + _getFilterOptions (event = null, _options = {}) { const optionTopics = _options.topics || []; const signature = event && event.signature || null; @@ -271,6 +298,11 @@ export default class Contract { topics }); + return options; + } + + _createEthFilter (event = null, _options) { + const options = this._getFilterOptions(event, _options); return this._api.eth.newFilter(options); } diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index 6d261c674..16fbd4d2e 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -112,11 +112,15 @@ export function inNumber10 (number) { } export function inNumber16 (number) { - if (isInstanceOf(number, BigNumber)) { - return inHex(number.toString(16)); + const bn = isInstanceOf(number, BigNumber) + ? number + : (new BigNumber(number || 0)); + + if (!bn.isInteger()) { + throw new Error(`[format/input::inNumber16] the given number is not an integer: ${bn.toFormat()}`); } - return inHex((new BigNumber(number || 0)).toString(16)); + return inHex(bn.toString(16)); } export function inOptions (options) { @@ -130,6 +134,9 @@ export function inOptions (options) { case 'gas': case 'gasPrice': + options[key] = inNumber16((new BigNumber(options[key])).round()); + break; + case 'value': case 'nonce': options[key] = inNumber16(options[key]); diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 262a275a0..1094cdb83 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -144,7 +144,8 @@ export function outSignerRequest (request) { break; case 'payload': - request[key].transaction = outTransaction(request[key].transaction); + request[key].signTransaction = outTransaction(request[key].signTransaction); + request[key].sendTransaction = outTransaction(request[key].sendTransaction); break; } }); diff --git a/js/src/api/rpc/eth/eth.js b/js/src/api/rpc/eth/eth.js index 43f8025e1..8148f9385 100644 --- a/js/src/api/rpc/eth/eth.js +++ b/js/src/api/rpc/eth/eth.js @@ -146,7 +146,8 @@ export default class Eth { getLogs (options) { return this._transport - .execute('eth_getLogs', inFilter(options)); + .execute('eth_getLogs', inFilter(options)) + .then((logs) => logs.map(outLog)); } getLogsEx (options) { diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index ac16eb9b0..7dd5b7eb3 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -128,6 +128,11 @@ export default class Parity { .execute('parity_killAccount', inAddress(account), password); } + removeAddress (address) { + return this._transport + .execute('parity_removeAddress', inAddress(address)); + } + listGethAccounts () { return this._transport .execute('parity_listGethAccounts') diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 2cb0c3ea0..82671184e 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -68,6 +68,7 @@ export default class Personal { this._accountsInfo(); return; + case 'parity_removeAddress': case 'parity_setAccountName': case 'parity_setAccountMeta': this._accountsInfo(); diff --git a/js/src/api/util/format.js b/js/src/api/util/format.js index 7f60357cd..d8cf74a8f 100644 --- a/js/src/api/util/format.js +++ b/js/src/api/util/format.js @@ -32,6 +32,10 @@ export function hex2Ascii (_hex) { return str; } +export function bytesToAscii (bytes) { + return bytes.map((b) => String.fromCharCode(b % 512)).join(''); +} + export function asciiToHex (string) { return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join(''); } diff --git a/js/src/contracts/abi/badgereg.json b/js/src/contracts/abi/badgereg.json new file mode 100644 index 000000000..3d18ba393 --- /dev/null +++ b/js/src/contracts/abi/badgereg.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"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"}] \ No newline at end of file diff --git a/js/src/contracts/abi/certifier.json b/js/src/contracts/abi/certifier.json new file mode 100644 index 000000000..905ddde6c --- /dev/null +++ b/js/src/contracts/abi/certifier.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index a6a7f0783..f15765b1a 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -14,6 +14,7 @@ // 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'; @@ -28,6 +29,7 @@ import tokenreg from './tokenreg.json'; import wallet from './wallet.json'; export { + badgereg, basiccoin, basiccoinmanager, dappreg, diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js new file mode 100644 index 000000000..45760b277 --- /dev/null +++ b/js/src/contracts/badgereg.js @@ -0,0 +1,66 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { bytesToHex, hex2Ascii } from '~/api/util/format'; + +import ABI from './abi/certifier.json'; + +const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export default class BadgeReg { + constructor (api, registry) { + this._api = api; + this._registry = registry; + + registry.getContract('badgereg'); + this.certifiers = {}; // by name + this.contracts = {}; // by name + } + + fetchCertifier (name) { + if (this.certifiers[name]) { + return Promise.resolve(this.certifiers[name]); + } + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return badgeReg.instance.fromName.call({}, [name]) + .then(([ id, address ]) => { + return Promise.all([ + badgeReg.instance.meta.call({}, [id, 'TITLE']), + badgeReg.instance.meta.call({}, [id, 'IMG']) + ]) + .then(([ title, img ]) => { + title = bytesToHex(title); + title = title === ZERO ? null : hex2Ascii(title); + if (bytesToHex(img) === ZERO) img = null; + + const data = { address, name, title, icon: img }; + this.certifiers[name] = data; + return data; + }); + }); + }); + } + + checkIfCertified (certifier, address) { + if (!this.contracts[certifier]) { + this.contracts[certifier] = this._api.newContract(ABI, certifier); + } + const contract = this.contracts[certifier]; + + return contract.instance.certified.call({}, [address]); + } +} diff --git a/js/src/views/Signer/components/index.js b/js/src/contracts/code/index.js similarity index 87% rename from js/src/views/Signer/components/index.js rename to js/src/contracts/code/index.js index 7c891f621..baa144979 100644 --- a/js/src/views/Signer/components/index.js +++ b/js/src/contracts/code/index.js @@ -14,5 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export RequestFinished from './RequestFinished'; -export RequestPending from './RequestPending'; +import wallet from './wallet'; + +export { + wallet +}; diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js new file mode 100644 index 000000000..94aa04b7b --- /dev/null +++ b/js/src/contracts/code/wallet.js @@ -0,0 +1,23 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +/** + * @version Solidity v0.4.6 + * @from https://github.com/ethereum/dapp-bin/blob/dd5c485359074d49f571693ae064ce78970f3d6d/wallet/wallet.sol + * @date 22-Nov-2016 @ 15h00 UTC + */ +export default '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; + diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index cefece7de..f61a63690 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -20,6 +20,7 @@ import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; import * as smsVerification from './sms-verification'; +import BadgeReg from './badgereg'; let instance = null; @@ -33,6 +34,7 @@ export default class Contracts { this._signaturereg = new SignatureReg(api, this._registry); this._tokenreg = new TokenReg(api, this._registry); this._githubhint = new GithubHint(api, this._registry); + this.badgeReg = new BadgeReg(api, this._registry); } get registry () { diff --git a/js/src/contracts/snippets/wallet.sol b/js/src/contracts/snippets/wallet.sol new file mode 100644 index 000000000..b369eea76 --- /dev/null +++ b/js/src/contracts/snippets/wallet.sol @@ -0,0 +1,388 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// 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; + } + + // 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]); + } + + function isOwner(address _addr) 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; + } + // 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 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; + } + } + } + + 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) +// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method +// 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; + } + // 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(); + } + // 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 + + 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 + + // 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); + + // 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); +} + +// usage: +// bytes32 h = Wallet(w).from(oneOwner).execute(to, value, data); +// 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; + } + + // 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 _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(); + } + + // FIELDS + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} diff --git a/js/src/dapps/basiccoin.js b/js/src/dapps/basiccoin.js index a64afa0b9..eb3213c79 100644 --- a/js/src/dapps/basiccoin.js +++ b/js/src/dapps/basiccoin.js @@ -16,8 +16,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; -import { createHashHistory } from 'history'; -import { Redirect, Router, Route, useRouterHistory } from 'react-router'; +import { Redirect, Router, Route, hashHistory } from 'react-router'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -27,14 +26,12 @@ import Application from './basiccoin/Application'; import Overview from './basiccoin/Overview'; import Transfer from './basiccoin/Transfer'; -const routerHistory = useRouterHistory(createHashHistory)({}); - import '../../assets/fonts/Roboto/font.css'; import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - + diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index abe0c90c5..a05ab6436 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -94,7 +94,6 @@ export default class Application extends Component { tokenregInstance, accounts: Object .keys(accountsInfo) - .filter((address) => !accountsInfo[address].meta.deleted) .sort((a, b) => { return (accountsInfo[b].uuid || '').localeCompare(accountsInfo[a].uuid || ''); }) diff --git a/js/src/dapps/basiccoin/Transfer/Send/send.js b/js/src/dapps/basiccoin/Transfer/Send/send.js index a9c05a228..f10807b2e 100644 --- a/js/src/dapps/basiccoin/Transfer/Send/send.js +++ b/js/src/dapps/basiccoin/Transfer/Send/send.js @@ -17,7 +17,7 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; -import { eip20 } from '../../../../contracts/abi'; +import { eip20 } from '~/contracts/abi'; import { api } from '../../parity'; import { loadBalances } from '../../services'; diff --git a/js/src/dapps/basiccoin/services.js b/js/src/dapps/basiccoin/services.js index 3dd5202a7..f76b7fb61 100644 --- a/js/src/dapps/basiccoin/services.js +++ b/js/src/dapps/basiccoin/services.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import * as abis from '../../contracts/abi'; +import * as abis from '~/contracts/abi'; import { api } from './parity'; let managerInstance; diff --git a/js/src/dapps/dappreg/dappsStore.js b/js/src/dapps/dappreg/dappsStore.js index 4baf7337c..382f5cdc4 100644 --- a/js/src/dapps/dappreg/dappsStore.js +++ b/js/src/dapps/dappreg/dappsStore.js @@ -17,8 +17,8 @@ import BigNumber from 'bignumber.js'; import { action, computed, observable, transaction } from 'mobx'; -import * as abis from '../../contracts/abi'; -import builtins from '../../views/Dapps/builtin.json'; +import * as abis from '~/contracts/abi'; +import builtins from '~/views/Dapps/builtin.json'; import { api } from './parity'; diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index d4f7fc6b2..fc889b7f6 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import * as abis from '../../contracts/abi'; +import * as abis from '~/contracts/abi'; import { api } from './parity'; export function attachInterface () { diff --git a/js/src/dapps/index.ejs b/js/src/dapps/index.ejs index fa65c78bf..aab652fa6 100644 --- a/js/src/dapps/index.ejs +++ b/js/src/dapps/index.ejs @@ -8,7 +8,7 @@ + + + +

This is your account <%= name %>:

+
+ symbol for the address of the account +
<%= address %>
+
+

This is the recovery phrase:

+
<%= phrase %>
+ + diff --git a/js/src/views/Signer/components/RequestFinished/index.js b/js/src/modals/CreateWallet/WalletDetails/index.js similarity index 94% rename from js/src/views/Signer/components/RequestFinished/index.js rename to js/src/modals/CreateWallet/WalletDetails/index.js index c5ed83b6b..266385511 100644 --- a/js/src/views/Signer/components/RequestFinished/index.js +++ b/js/src/modals/CreateWallet/WalletDetails/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './requestFinished'; +export default from './walletDetails'; diff --git a/js/src/modals/CreateWallet/WalletDetails/walletDetails.js b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js new file mode 100644 index 000000000..314425dcf --- /dev/null +++ b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js @@ -0,0 +1,164 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui'; +import { parseAbiType } from '~/util/abi'; + +import styles from '../createWallet.css'; + +export default class WalletDetails extends Component { + static propTypes = { + accounts: PropTypes.object.isRequired, + wallet: PropTypes.object.isRequired, + errors: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + walletType: PropTypes.string.isRequired + }; + + render () { + const { walletType } = this.props; + + if (walletType === 'WATCH') { + return this.renderWatchDetails(); + } + + return this.renderMultisigDetails(); + } + + renderWatchDetails () { + const { wallet, errors } = this.props; + + return ( +
+ + + + + + + ); + } + + renderMultisigDetails () { + const { accounts, wallet, errors } = this.props; + + return ( +
+ + + + + + + + +
+ + + +
+ + ); + } + + onAddressChange = (_, address) => { + this.props.onChange({ address }); + } + + onAccoutChange = (_, account) => { + this.props.onChange({ account }); + } + + onNameChange = (_, name) => { + this.props.onChange({ name }); + } + + onDescriptionChange = (_, description) => { + this.props.onChange({ description }); + } + + onOwnersChange = (owners) => { + this.props.onChange({ owners }); + } + + onRequiredChange = (required) => { + this.props.onChange({ required }); + } + + onDaylimitChange = (daylimit) => { + this.props.onChange({ daylimit }); + } +} diff --git a/js/src/views/Signer/components/TransactionFinished/index.js b/js/src/modals/CreateWallet/WalletInfo/index.js similarity index 93% rename from js/src/views/Signer/components/TransactionFinished/index.js rename to js/src/modals/CreateWallet/WalletInfo/index.js index fe606e1d4..975e7bd59 100644 --- a/js/src/views/Signer/components/TransactionFinished/index.js +++ b/js/src/modals/CreateWallet/WalletInfo/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './TransactionFinished'; +export default from './walletInfo'; diff --git a/js/src/modals/CreateWallet/WalletInfo/walletInfo.js b/js/src/modals/CreateWallet/WalletInfo/walletInfo.js new file mode 100644 index 000000000..bbbe5877f --- /dev/null +++ b/js/src/modals/CreateWallet/WalletInfo/walletInfo.js @@ -0,0 +1,92 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { CompletedStep, IdentityIcon, CopyToClipboard } from '~/ui'; +import { fromWei } from '~/api/util/wei'; + +import styles from '../createWallet.css'; + +export default class WalletInfo extends Component { + static propTypes = { + accounts: PropTypes.object.isRequired, + account: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + address: PropTypes.string.isRequired, + owners: PropTypes.array.isRequired, + required: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]).isRequired, + daylimit: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]).isRequired, + + deployed: PropTypes.bool + }; + + render () { + const { address, required, daylimit, name, deployed } = this.props; + + return ( + +
+ { name } + has been + { deployed ? 'deployed' : 'added' } at +
+
+ + +
{ address }
+
+
with the following owners
+
+ { this.renderOwners() } +
+

+ { required } owners are required to confirm a transaction. +

+

+ The daily limit is set to { fromWei(daylimit).toFormat() } ETH. +

+
+ ); + } + + renderOwners () { + const { account, owners, deployed } = this.props; + + return [].concat(deployed ? account : null, owners).filter((a) => a).map((address, id) => ( +
+ +
{ this.addressToString(address) }
+
+ )); + } + + addressToString (address) { + const { accounts } = this.props; + + if (accounts[address]) { + return accounts[address].name || address; + } + + return address; + } +} diff --git a/util/fdlimit/src/lib.rs b/js/src/modals/CreateWallet/WalletType/index.js similarity index 88% rename from util/fdlimit/src/lib.rs rename to js/src/modals/CreateWallet/WalletType/index.js index 92c403058..525e35495 100644 --- a/util/fdlimit/src/lib.rs +++ b/js/src/modals/CreateWallet/WalletType/index.js @@ -1,19 +1,17 @@ // Copyright 2015, 2016 Ethcore (UK) Ltd. // This file is part of Parity. -// + // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// + // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see .extern crate libc; -extern crate libc; -mod raise_fd_limit; -pub use raise_fd_limit::raise_fd_limit; +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './walletType.js'; diff --git a/js/src/modals/CreateWallet/WalletType/walletType.js b/js/src/modals/CreateWallet/WalletType/walletType.js new file mode 100644 index 000000000..868c6ad9b --- /dev/null +++ b/js/src/modals/CreateWallet/WalletType/walletType.js @@ -0,0 +1,64 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { RadioButtons } from '~/ui'; + +// import styles from '../createWallet.css'; + +export default class WalletType extends Component { + static propTypes = { + onChange: PropTypes.func.isRequired, + type: PropTypes.string.isRequired + }; + + render () { + const { type } = this.props; + + return ( + + ); + } + + getTypes () { + return [ + { + label: 'Multi-Sig wallet', key: 'MULTISIG', + description: ( + + Create/Deploy a + standard multi-signature + Wallet + + ) + }, + { + label: 'Watch a wallet', key: 'WATCH', + description: 'Add an existing wallet to your accounts' + } + ]; + } + + onTypeChange = (type) => { + this.props.onChange(type.key); + } +} diff --git a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.css b/js/src/modals/CreateWallet/createWallet.css similarity index 62% rename from js/src/views/Signer/components/TransactionFinished/TransactionFinished.css rename to js/src/modals/CreateWallet/createWallet.css index 3617f666a..01c079260 100644 --- a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.css +++ b/js/src/modals/CreateWallet/createWallet.css @@ -15,40 +15,44 @@ /* along with Parity. If not, see . */ -@import '../../_layout.css'; +.address { + vertical-align: top; + display: inline-block; +} + +.identityicon { + margin: -8px 0.5em; +} + +.owner { + height: 40px; + color: lightgrey; -.container { display: flex; - padding: 1.5em 0 1em; + align-items: center; + justify-content: center; - & > * { - vertical-align: middle; - min-height: $finishedHeight; + .identityicon { + width: 24px; + height: 24px; } } -.statusContainer { - box-sizing: border-box; - float: right; - padding: 0 1em; - flex: 0 0 $statusWidth; -} +.splitInput { + display: flex; + flex-direction: row; -.transactionDetails { - width: 100%; - box-sizing: border-box; -} + > * { + flex: 1; -.isConfirmed { - color: green; -} + margin: 0 0.25em; -.isRejected { - opacity: 0.5; - padding-top: 2em; -} + &:first-child { + margin-left: 0; + } -.txHash { - display: block; - word-break: break-all; + &:last-child { + margin-right: 0; + } + } } diff --git a/js/src/modals/CreateWallet/createWallet.js b/js/src/modals/CreateWallet/createWallet.js new file mode 100644 index 000000000..d99ef05b4 --- /dev/null +++ b/js/src/modals/CreateWallet/createWallet.js @@ -0,0 +1,214 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import { observer } from 'mobx-react'; + +import ActionDone from 'material-ui/svg-icons/action/done'; +import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; + +import { Button, Modal, TxHash, BusyStep } from '~/ui'; + +import WalletType from './WalletType'; +import WalletDetails from './WalletDetails'; +import WalletInfo from './WalletInfo'; +import CreateWalletStore from './createWalletStore'; +// import styles from './createWallet.css'; + +@observer +export default class CreateWallet extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + accounts: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired + }; + + store = new CreateWalletStore(this.context.api, this.props.accounts); + + render () { + const { stage, steps, waiting, rejected } = this.store; + + if (rejected) { + return ( + + + + ); + } + + return ( + s.title) } + waiting={ waiting } + > + { this.renderPage() } + + ); + } + + renderPage () { + const { step } = this.store; + const { accounts } = this.props; + + switch (step) { + case 'DEPLOYMENT': + return ( + + { this.store.txhash ? () : null } + + ); + + case 'INFO': + return ( + + ); + + case 'DETAILS': + return ( + + ); + + default: + case 'TYPE': + return ( + + ); + } + } + + renderDialogActions () { + const { step, hasErrors, rejected, onCreate, onNext, onAdd } = this.store; + + const cancelBtn = ( +