Merge branch 'master' into auth-bft

This commit is contained in:
keorn 2016-11-25 10:51:06 +00:00
commit 1692c07ba6
138 changed files with 5226 additions and 1070 deletions

View File

@ -31,10 +31,12 @@ linux-stable:
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5" - md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
tags: tags:
- rust - rust
- rust-stable - rust-stable
@ -96,6 +98,8 @@ linux-centos:
- md5sum target/release/parity > parity.md5 - md5sum target/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5 - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
tags: tags:
@ -126,10 +130,12 @@ linux-i686:
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5" - md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/i686-unknown-linux-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
tags: tags:
- rust - rust
- rust-i686 - rust-i686
@ -166,10 +172,12 @@ linux-armv7:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5" - md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
tags: tags:
- rust - rust
- rust-arm - rust-arm
@ -206,10 +214,12 @@ linux-arm:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5" - md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
tags: tags:
- rust - rust
- rust-arm - rust-arm
@ -241,8 +251,10 @@ linux-armv6:
- md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5 - md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity.md5 --body parity.md5
tags: tags:
- rust - rust
- rust-arm - rust-arm
@ -279,10 +291,12 @@ linux-aarch64:
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5" - md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
tags: tags:
- rust - rust
- rust-arm - rust-arm
@ -291,6 +305,41 @@ linux-aarch64:
- target/aarch64-unknown-linux-gnu/release/parity - target/aarch64-unknown-linux-gnu/release/parity
name: "aarch64-unknown-linux-gnu_parity" name: "aarch64-unknown-linux-gnu_parity"
allow_failure: true allow_failure: true
#linux-alpine:
# stage: build
# image: ethcore/rust-alpine:latest
# only:
# - beta
# - tags
# - stable
# - triggers
# script:
# - export HOST_CC=gcc
# - export HOST_CXX=g++
# - cargo build --release $CARGOFLAGS
# - strip target/release/parity
# - md5sum target/release/parity > parity.md5
# - sh scripts/deb-build.sh arm64
# - cp target/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity
# - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
# - dpkg-deb -b deb "parity_"$VER"_arm64.deb"
# - md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
# - aws configure set aws_access_key_id $s3_key
# - aws configure set aws_secret_access_key $s3_secret
# - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
# - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu
# - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity
# - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5
# - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
# - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
# tags:
# - rust
# - rust-alpine
# artifacts:
# paths:
# - target/aarch64-unknown-linux-gnu/release/parity
# name: "aarch64-unknown-linux-gnu_parity"
# allow_failure: true
darwin: darwin:
stage: build stage: build
only: only:
@ -299,13 +348,22 @@ darwin:
- stable - stable
- triggers - triggers
script: script:
- cargo build --release -p ethstore $CARGOFLAGS
- cargo build --release $CARGOFLAGS - cargo build --release $CARGOFLAGS
- rm -rf parity.md5 - rm -rf parity.md5
- md5sum target/release/parity > parity.md5 - md5sum target/release/parity > parity.md5
- packagesbuild -v mac/Parity.pkgproj
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- mv target/release/Parity\ Ethereum.pkg "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
- md5sum "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" >> "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
- aws configure set aws_access_key_id $s3_key - aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret - aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity - if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5 - aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-apple-darwin
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
tags: tags:
- osx - osx
artifacts: artifacts:
@ -349,14 +407,20 @@ windows:
- cd ..\.. - cd ..\..
- aws configure set aws_access_key_id %s3_key% - aws configure set aws_access_key_id %s3_key%
- aws configure set aws_secret_access_key %s3_secret% - aws configure set aws_secret_access_key %s3_secret%
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe - echo %CI_BUILD_REF_NAME%
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5 - echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip - echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5 - echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe - echo %S3_BUCKET%
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5 - aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5 - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
tags: tags:
- rust-windows - rust-windows
artifacts: artifacts:
@ -394,13 +458,13 @@ test-rust-stable:
image: ethcore/rust:stable image: ethcore/rust:stable
before_script: before_script:
- git submodule update --init --recursive - git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only CI_BUILD_REF CI_BUILD_REF@{1} | grep \.js | wc -l) - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l)
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi
script: script:
- export RUST_BACKTRACE=1 - export RUST_BACKTRACE=1
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi - 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
tags: tags:
- rust - rust
- rust-stable - rust-stable
@ -411,13 +475,13 @@ test-rust-beta:
image: ethcore/rust:beta image: ethcore/rust:beta
before_script: before_script:
- git submodule update --init --recursive - git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only CI_BUILD_REF CI_BUILD_REF@{1} | grep \.js | wc -l) - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l)
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi
script: script:
- export RUST_BACKTRACE=1 - export RUST_BACKTRACE=1
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi - 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
tags: tags:
- rust - rust
- rust-beta - rust-beta
@ -429,28 +493,17 @@ test-rust-nightly:
image: ethcore/rust:nightly image: ethcore/rust:nightly
before_script: before_script:
- git submodule update --init --recursive - git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only CI_BUILD_REF CI_BUILD_REF@{1} | grep \.js | wc -l) - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l)
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi
script: script:
- export RUST_BACKTRACE=1 - export RUST_BACKTRACE=1
- echo $JS_FILES_MODIFIED - echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi - 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
tags: tags:
- rust - rust
- rust-nightly - rust-nightly
allow_failure: true allow_failure: true
js-tests:
stage: test
image: ethcore/rust:stable
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/lint.sh
- ./js/scripts/test.sh
- ./js/scripts/build.sh
tags:
- javascript-test
js-release: js-release:
stage: js-build stage: js-build
only: only:
@ -459,9 +512,11 @@ js-release:
- stable - stable
image: ethcore/rust:stable image: ethcore/rust:stable
before_script: before_script:
- if [[ $NIGHTLY != "master" ]]; then ./js/scripts/install-deps.sh; fi - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l)
- echo $JS_FILES_MODIFIED
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/install-deps.sh;fi
script: script:
- if [[ $NIGHTLY != "master" ]]; then ./js/scripts/build.sh; fi - echo $JS_FILES_MODIFIED
- if [[ $NIGHTLY != "master" ]]; then ./js/scripts/release.sh; fi - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh&./js/scripts/release.sh; fi
tags: tags:
- javascript - javascript

10
Cargo.lock generated
View File

@ -385,7 +385,7 @@ version = "1.5.0"
dependencies = [ dependencies = [
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.1 (git+https://github.com/carllerche/mio)", "mio 0.6.1 (git+https://github.com/ethcore/mio)",
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -475,7 +475,7 @@ dependencies = [
"igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.1 (git+https://github.com/carllerche/mio)", "mio 0.6.1 (git+https://github.com/ethcore/mio)",
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
@ -1026,7 +1026,7 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/carllerche/mio#56f8663510196fdca04bdf7c5f4d60b24297826f" source = "git+https://github.com/ethcore/mio#ef182bae193a9c7457cd2cf661fcaffb226e3eef"
dependencies = [ dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1263,7 +1263,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#587684374a12bf715151dd987a552a3d61e42972" source = "git+https://github.com/ethcore/js-precompiled.git#f1de5e5612d8237143b37aebf237a49475c2c4e6"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -2053,7 +2053,7 @@ dependencies = [
"checksum mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)" = "<none>" "checksum mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)" = "<none>"
"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e"
"checksum mio 0.6.0-dev (git+https://github.com/ethcore/mio?branch=timer-fix)" = "<none>" "checksum mio 0.6.0-dev (git+https://github.com/ethcore/mio?branch=timer-fix)" = "<none>"
"checksum mio 0.6.1 (git+https://github.com/carllerche/mio)" = "<none>" "checksum mio 0.6.1 (git+https://github.com/ethcore/mio)" = "<none>"
"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" "checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a"
"checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8" "checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8"
"checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>" "checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"

View File

@ -21,8 +21,8 @@
"genesis": { "genesis": {
"seal": { "seal": {
"generic": { "generic": {
"fields": 1, "fields": 2,
"rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" "rlp": "0x200"
} }
}, },
"difficulty": "0x20000", "difficulty": "0x20000",

View File

@ -8,7 +8,7 @@
"difficultyBoundDivisor": "0x0800", "difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", "registrar": "0x81a4b044831c4f12ba601adb9274516939e9b8a2",
"homesteadTransition": 0, "homesteadTransition": 0,
"eip150Transition": 0, "eip150Transition": 0,
"eip155Transition": 10, "eip155Transition": 10,

View File

@ -11,9 +11,9 @@
}, },
"genesis": { "genesis": {
"seal": { "seal": {
"ethereum": { "generic": {
"nonce": "0x00006d6f7264656e", "fields": 0,
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" "rlp": "0x0"
} }
}, },
"difficulty": "0x20000", "difficulty": "0x20000",

View File

@ -276,14 +276,20 @@ impl AccountProvider {
} }
/// Returns `true` if the password for `account` is `password`. `false` if not. /// Returns `true` if the password for `account` is `password`. `false` if not.
pub fn test_password(&self, account: &Address, password: String) -> Result<bool, Error> { pub fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error> {
match self.sstore.sign(account, &password, &Default::default()) { match self.sstore.sign(account, password, &Default::default()) {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(SSError::InvalidPassword) => Ok(false), Err(SSError::InvalidPassword) => Ok(false),
Err(e) => Err(Error::SStore(e)), Err(e) => Err(Error::SStore(e)),
} }
} }
/// Permanently removes an account.
pub fn kill_account(&self, account: &Address, password: &str) -> Result<(), Error> {
try!(self.sstore.remove_account(account, &password));
Ok(())
}
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given. /// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> {
self.sstore.change_password(account, &password, &new_password).map_err(Error::SStore) self.sstore.change_password(account, &password, &new_password).map_err(Error::SStore)

View File

@ -141,7 +141,7 @@ pub trait BlockProvider {
fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockNumber, to_block: BlockNumber) -> Vec<BlockNumber>; fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockNumber, to_block: BlockNumber) -> Vec<BlockNumber>;
/// Returns logs matching given filter. /// Returns logs matching given filter.
fn logs<F>(&self, mut blocks: Vec<BlockNumber>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry> fn logs<F>(&self, blocks: Vec<BlockNumber>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry>
where F: Fn(&LogEntry) -> bool, Self: Sized; where F: Fn(&LogEntry) -> bool, Self: Sized;
} }

View File

@ -256,8 +256,8 @@ impl Engine for AuthorityRound {
/// Check if the signature belongs to the correct proposer. /// Check if the signature belongs to the correct proposer.
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let header_step = try!(header_step(header)); let header_step = try!(header_step(header));
// Give one step slack if step is lagging, double vote is still not possible. // Give one step slack if step is lagging, double vote is still not possible.
if header_step <= self.step() + 1 { if header_step <= self.step() + 1 {
let proposer_signature = try!(header_signature(header)); let proposer_signature = try!(header_signature(header));
let ok_sig = try!(verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())); let ok_sig = try!(verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash()));
@ -423,13 +423,13 @@ mod tests {
let engine = Spec::new_test_round().engine; let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
let mut step = UNIX_EPOCH.elapsed().unwrap().as_secs(); let time = UNIX_EPOCH.elapsed().unwrap().as_secs();
// Two authorities.
let mut step = time - time % 2;
header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
let first_ok = engine.verify_block_seal(&header).is_ok(); assert!(engine.verify_block_seal(&header).is_err());
step = step + 1; step = step + 1;
header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
let second_ok = engine.verify_block_seal(&header).is_ok(); assert!(engine.verify_block_seal(&header).is_ok());
assert!(first_ok ^ second_ok);
} }
} }

View File

@ -577,29 +577,29 @@ impl Miner {
let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into();
let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); let best_block_header: Header = ::rlp::decode(&chain.best_block_header());
transactions.into_iter() transactions.into_iter()
.filter(|tx| match self.engine.verify_transaction_basic(tx, &best_block_header) { .map(|tx| {
Ok(()) => true, match self.engine.verify_transaction_basic(&tx, &best_block_header) {
Err(e) => { Err(e) => {
debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e); debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e);
false Err(e)
} },
} Ok(()) => {
) let origin = accounts.as_ref().and_then(|accounts| {
.map(|tx| { tx.sender().ok().and_then(|sender| match accounts.contains(&sender) {
let origin = accounts.as_ref().and_then(|accounts| { true => Some(TransactionOrigin::Local),
tx.sender().ok().and_then(|sender| match accounts.contains(&sender) { false => None,
true => Some(TransactionOrigin::Local), })
false => None, }).unwrap_or(default_origin);
})
}).unwrap_or(default_origin); match origin {
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
match origin { transaction_queue.add(tx, origin, &fetch_account, &gas_required)
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { },
transaction_queue.add(tx, origin, &fetch_account, &gas_required) TransactionOrigin::External => {
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
}
}
}, },
TransactionOrigin::External => {
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
}
} }
}) })
.collect() .collect()

View File

@ -128,6 +128,9 @@ impl<T> TraceDB<T> where T: DatabaseExtras {
/// Creates new instance of `TraceDB`. /// Creates new instance of `TraceDB`.
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self { pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
let mut batch = DBTransaction::new(&tracesdb); let mut batch = DBTransaction::new(&tracesdb);
let genesis = extras.block_hash(0)
.expect("Genesis block is always inserted upon extras db creation qed");
batch.write(db::COL_TRACE, &genesis, &FlatBlockTraces::default());
batch.put(db::COL_TRACE, b"version", TRACE_DB_VER); batch.put(db::COL_TRACE, b"version", TRACE_DB_VER);
tracesdb.write(batch).expect("failed to update version"); tracesdb.write(batch).expect("failed to update version");
@ -413,8 +416,12 @@ mod tests {
struct NoopExtras; struct NoopExtras;
impl DatabaseExtras for NoopExtras { impl DatabaseExtras for NoopExtras {
fn block_hash(&self, _block_number: BlockNumber) -> Option<H256> { fn block_hash(&self, block_number: BlockNumber) -> Option<H256> {
unimplemented!(); if block_number == 0 {
Some(H256::default())
} else {
unimplemented!()
}
} }
fn transaction_hash(&self, _block_number: BlockNumber, _tx_position: usize) -> Option<H256> { fn transaction_hash(&self, _block_number: BlockNumber, _tx_position: usize) -> Option<H256> {
@ -581,35 +588,21 @@ mod tests {
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap()); let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
let mut config = Config::default(); let mut config = Config::default();
config.enabled = true; config.enabled = true;
let block_0 = H256::from(0xa1); let block_1 = H256::from(0xa1);
let block_1 = H256::from(0xa2); let block_2 = H256::from(0xa2);
let tx_0 = H256::from(0xff); let tx_1 = H256::from(0xff);
let tx_1 = H256::from(0xaf); let tx_2 = H256::from(0xaf);
let mut extras = Extras::default(); let mut extras = Extras::default();
extras.block_hashes.insert(0, block_0.clone()); extras.block_hashes.insert(0, H256::default());
extras.block_hashes.insert(1, block_1.clone()); extras.block_hashes.insert(1, block_1.clone());
extras.transaction_hashes.insert(0, vec![tx_0.clone()]); extras.block_hashes.insert(2, block_2.clone());
extras.transaction_hashes.insert(1, vec![tx_1.clone()]); extras.transaction_hashes.insert(1, vec![tx_1.clone()]);
extras.transaction_hashes.insert(2, vec![tx_2.clone()]);
let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras)); let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras));
// import block 0
let request = create_simple_import_request(0, block_0.clone());
let mut batch = DBTransaction::new(&db);
tracedb.import(&mut batch, request);
db.write(batch).unwrap();
let filter = Filter {
range: (0..0),
from_address: AddressesFilter::from(vec![Address::from(1)]),
to_address: AddressesFilter::from(vec![]),
};
let traces = tracedb.filter(&filter);
assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone()));
// import block 1 // import block 1
let request = create_simple_import_request(1, block_1.clone()); let request = create_simple_import_request(1, block_1.clone());
let mut batch = DBTransaction::new(&db); let mut batch = DBTransaction::new(&db);
@ -617,38 +610,56 @@ mod tests {
db.write(batch).unwrap(); db.write(batch).unwrap();
let filter = Filter { let filter = Filter {
range: (0..1), range: (1..1),
from_address: AddressesFilter::from(vec![Address::from(1)]),
to_address: AddressesFilter::from(vec![]),
};
let traces = tracedb.filter(&filter);
assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone()));
// import block 2
let request = create_simple_import_request(2, block_2.clone());
let mut batch = DBTransaction::new(&db);
tracedb.import(&mut batch, request);
db.write(batch).unwrap();
let filter = Filter {
range: (1..2),
from_address: AddressesFilter::from(vec![Address::from(1)]), from_address: AddressesFilter::from(vec![Address::from(1)]),
to_address: AddressesFilter::from(vec![]), to_address: AddressesFilter::from(vec![]),
}; };
let traces = tracedb.filter(&filter); let traces = tracedb.filter(&filter);
assert_eq!(traces.len(), 2); assert_eq!(traces.len(), 2);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone()));
assert_eq!(traces[1], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); assert_eq!(traces[1], create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
let traces = tracedb.block_traces(0).unwrap(); assert!(tracedb.block_traces(0).is_some(), "Genesis trace should be always present.");
assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone()));
let traces = tracedb.block_traces(1).unwrap(); let traces = tracedb.block_traces(1).unwrap();
assert_eq!(traces.len(), 1); assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone()));
assert_eq!(None, tracedb.block_traces(2)); let traces = tracedb.block_traces(2).unwrap();
let traces = tracedb.transaction_traces(0, 0).unwrap();
assert_eq!(traces.len(), 1); assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); assert_eq!(traces[0], create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
assert_eq!(None, tracedb.block_traces(3));
let traces = tracedb.transaction_traces(1, 0).unwrap(); let traces = tracedb.transaction_traces(1, 0).unwrap();
assert_eq!(traces.len(), 1); assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone()));
assert_eq!(None, tracedb.transaction_traces(1, 1)); let traces = tracedb.transaction_traces(2, 0).unwrap();
assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
assert_eq!(None, tracedb.transaction_traces(2, 1));
assert_eq!(tracedb.trace(0, 0, vec![]).unwrap(), create_simple_localized_trace(0, block_0.clone(), tx_0.clone()));
assert_eq!(tracedb.trace(1, 0, vec![]).unwrap(), create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); assert_eq!(tracedb.trace(1, 0, vec![]).unwrap(), create_simple_localized_trace(1, block_1.clone(), tx_1.clone()));
assert_eq!(tracedb.trace(2, 0, vec![]).unwrap(), create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
} }
#[test] #[test]
@ -660,8 +671,10 @@ mod tests {
let block_0 = H256::from(0xa1); let block_0 = H256::from(0xa1);
let tx_0 = H256::from(0xff); let tx_0 = H256::from(0xff);
extras.block_hashes.insert(0, block_0.clone()); extras.block_hashes.insert(0, H256::default());
extras.transaction_hashes.insert(0, vec![tx_0.clone()]); extras.transaction_hashes.insert(0, vec![]);
extras.block_hashes.insert(1, block_0.clone());
extras.transaction_hashes.insert(1, vec![tx_0.clone()]);
// set tracing on // set tracing on
config.enabled = true; config.enabled = true;
@ -669,8 +682,8 @@ mod tests {
{ {
let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone())); let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone()));
// import block 0 // import block 1
let request = create_simple_import_request(0, block_0.clone()); let request = create_simple_import_request(1, block_0.clone());
let mut batch = DBTransaction::new(&db); let mut batch = DBTransaction::new(&db);
tracedb.import(&mut batch, request); tracedb.import(&mut batch, request);
db.write(batch).unwrap(); db.write(batch).unwrap();
@ -678,8 +691,28 @@ mod tests {
{ {
let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras)); let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras));
let traces = tracedb.transaction_traces(0, 0); let traces = tracedb.transaction_traces(1, 0);
assert_eq!(traces.unwrap(), vec![create_simple_localized_trace(0, block_0, tx_0)]); assert_eq!(traces.unwrap(), vec![create_simple_localized_trace(1, block_0, tx_0)]);
} }
} }
#[test]
fn query_genesis() {
let temp = RandomTempPath::new();
let db = new_db(temp.as_str());
let mut config = Config::default();
let mut extras = Extras::default();
let block_0 = H256::from(0xa1);
extras.block_hashes.insert(0, block_0.clone());
extras.transaction_hashes.insert(0, vec![]);
// set tracing on
config.enabled = true;
let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone()));
let traces = tracedb.block_traces(0).unwrap();
assert_eq!(traces.len(), 0);
}
} }

View File

@ -119,7 +119,7 @@ impl Into<Vec<FlatTrace>> for FlatTransactionTraces {
} }
/// Represents all traces produced by transactions in a single block. /// Represents all traces produced by transactions in a single block.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone, Default)]
pub struct FlatBlockTraces(Vec<FlatTransactionTraces>); pub struct FlatBlockTraces(Vec<FlatTransactionTraces>);
impl HeapSizeOf for FlatBlockTraces { impl HeapSizeOf for FlatBlockTraces {

View File

@ -35,6 +35,9 @@ pub mod kind;
const MIN_MEM_LIMIT: usize = 16384; const MIN_MEM_LIMIT: usize = 16384;
const MIN_QUEUE_LIMIT: usize = 512; const MIN_QUEUE_LIMIT: usize = 512;
// maximum possible number of verification threads.
const MAX_VERIFIERS: usize = 8;
/// Type alias for block queue convenience. /// Type alias for block queue convenience.
pub type BlockQueue = VerificationQueue<self::kind::Blocks>; pub type BlockQueue = VerificationQueue<self::kind::Blocks>;
@ -61,6 +64,37 @@ impl Default for Config {
} }
} }
struct VerifierHandle {
deleting: Arc<AtomicBool>,
sleep: Arc<AtomicBool>,
thread: JoinHandle<()>,
}
impl VerifierHandle {
// signal to the verifier thread that it should sleep.
fn sleep(&self) {
self.sleep.store(true, AtomicOrdering::SeqCst);
}
// 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");
}
}
/// An item which is in the process of being verified. /// An item which is in the process of being verified.
pub struct Verifying<K: Kind> { pub struct Verifying<K: Kind> {
hash: H256, hash: H256,
@ -97,11 +131,12 @@ pub struct VerificationQueue<K: Kind> {
engine: Arc<Engine>, engine: Arc<Engine>,
more_to_verify: Arc<SCondvar>, more_to_verify: Arc<SCondvar>,
verification: Arc<Verification<K>>, verification: Arc<Verification<K>>,
verifiers: Vec<JoinHandle<()>>, verifiers: Mutex<(Vec<VerifierHandle>, usize)>,
deleting: Arc<AtomicBool>, deleting: Arc<AtomicBool>,
ready_signal: Arc<QueueSignal>, ready_signal: Arc<QueueSignal>,
empty: Arc<SCondvar>, empty: Arc<SCondvar>,
processing: RwLock<HashSet<H256>>, processing: RwLock<HashSet<H256>>,
ticks_since_adjustment: AtomicUsize,
max_queue_size: usize, max_queue_size: usize,
max_mem_use: usize, max_mem_use: usize,
} }
@ -157,6 +192,7 @@ struct Verification<K: Kind> {
more_to_verify: SMutex<()>, more_to_verify: SMutex<()>,
empty: SMutex<()>, empty: SMutex<()>,
sizes: Sizes, sizes: Sizes,
check_seal: bool,
} }
impl<K: Kind> VerificationQueue<K> { impl<K: Kind> VerificationQueue<K> {
@ -173,7 +209,8 @@ impl<K: Kind> VerificationQueue<K> {
unverified: AtomicUsize::new(0), unverified: AtomicUsize::new(0),
verifying: AtomicUsize::new(0), verifying: AtomicUsize::new(0),
verified: AtomicUsize::new(0), verified: AtomicUsize::new(0),
} },
check_seal: check_seal,
}); });
let more_to_verify = Arc::new(SCondvar::new()); let more_to_verify = Arc::new(SCondvar::new());
let deleting = Arc::new(AtomicBool::new(false)); let deleting = Arc::new(AtomicBool::new(false));
@ -185,44 +222,82 @@ impl<K: Kind> VerificationQueue<K> {
let empty = Arc::new(SCondvar::new()); let empty = Arc::new(SCondvar::new());
let panic_handler = PanicHandler::new_in_arc(); let panic_handler = PanicHandler::new_in_arc();
let mut verifiers: Vec<JoinHandle<()>> = Vec::new(); let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS);
let thread_count = max(::num_cpus::get(), 3) - 2; let default_amount = max(::num_cpus::get(), 3) - 2;
for i in 0..thread_count { let mut verifiers = Vec::with_capacity(max_verifiers);
let verification = verification.clone();
let engine = engine.clone(); debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount);
let more_to_verify = more_to_verify.clone();
let ready_signal = ready_signal.clone(); for i in 0..max_verifiers {
let empty = empty.clone(); debug!(target: "verification", "Adding verification thread #{}", i);
let deleting = deleting.clone(); let deleting = deleting.clone();
let panic_handler = panic_handler.clone(); let panic_handler = panic_handler.clone();
verifiers.push( let verification = verification.clone();
thread::Builder::new() let engine = engine.clone();
.name(format!("Verifier #{}", i)) let wait = more_to_verify.clone();
.spawn(move || { let ready = ready_signal.clone();
panic_handler.catch_panic(move || { let empty = empty.clone();
VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty, check_seal)
}).unwrap() // enable only the first few verifiers.
}) let sleep = if i < default_amount {
.expect("Error starting block verification thread") 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.")
});
} }
VerificationQueue { VerificationQueue {
engine: engine, engine: engine,
panic_handler: panic_handler, panic_handler: panic_handler,
ready_signal: ready_signal.clone(), ready_signal: ready_signal,
more_to_verify: more_to_verify.clone(), more_to_verify: more_to_verify,
verification: verification.clone(), verification: verification,
verifiers: verifiers, verifiers: Mutex::new((verifiers, default_amount)),
deleting: deleting.clone(), deleting: deleting,
processing: RwLock::new(HashSet::new()), processing: RwLock::new(HashSet::new()),
empty: empty.clone(), empty: empty,
ticks_since_adjustment: AtomicUsize::new(0),
max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT),
max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT),
} }
} }
fn verify(verification: Arc<Verification<K>>, engine: Arc<Engine>, wait: Arc<SCondvar>, ready: Arc<QueueSignal>, deleting: Arc<AtomicBool>, empty: Arc<SCondvar>, check_seal: bool) { fn verify(
verification: Arc<Verification<K>>,
engine: Arc<Engine>,
wait: Arc<SCondvar>,
ready: Arc<QueueSignal>,
deleting: Arc<AtomicBool>,
empty: Arc<SCondvar>,
sleep: Arc<AtomicBool>,
) {
while !deleting.load(AtomicOrdering::Acquire) { while !deleting.load(AtomicOrdering::Acquire) {
{
while sleep.load(AtomicOrdering::SeqCst) {
trace!(target: "verification", "Verifier sleeping");
::std::thread::park();
trace!(target: "verification", "Verifier waking up");
if deleting.load(AtomicOrdering::Acquire) {
return;
}
}
}
{ {
let mut more_to_verify = verification.more_to_verify.lock().unwrap(); let mut more_to_verify = verification.more_to_verify.lock().unwrap();
@ -255,7 +330,7 @@ impl<K: Kind> VerificationQueue<K> {
}; };
let hash = item.hash(); let hash = item.hash();
let is_ready = match K::verify(item, &*engine, check_seal) { let is_ready = match K::verify(item, &*engine, verification.check_seal) {
Ok(verified) => { Ok(verified) => {
let mut verifying = verification.verifying.lock(); let mut verifying = verification.verifying.lock();
let mut idx = None; let mut idx = None;
@ -302,9 +377,15 @@ impl<K: Kind> VerificationQueue<K> {
} }
} }
fn drain_verifying(verifying: &mut VecDeque<Verifying<K>>, verified: &mut VecDeque<K::Verified>, bad: &mut HashSet<H256>, sizes: &Sizes) { fn drain_verifying(
verifying: &mut VecDeque<Verifying<K>>,
verified: &mut VecDeque<K::Verified>,
bad: &mut HashSet<H256>,
sizes: &Sizes,
) {
let mut removed_size = 0; let mut removed_size = 0;
let mut inserted_size = 0; let mut inserted_size = 0;
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) { while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
assert!(verifying.pop_front().is_some()); assert!(verifying.pop_front().is_some());
let size = output.heap_size_of_children(); let size = output.heap_size_of_children();
@ -487,14 +568,85 @@ impl<K: Kind> VerificationQueue<K> {
} }
} }
/// Optimise memory footprint of the heap fields. /// Optimise memory footprint of the heap fields, and adjust the number of threads
/// to better suit the workload.
pub fn collect_garbage(&self) { pub fn collect_garbage(&self) {
{ // number of ticks to average queue stats over
self.verification.unverified.lock().shrink_to_fit(); // when deciding whether to change the number of verifiers.
#[cfg(not(test))]
const READJUSTMENT_PERIOD: usize = 12;
#[cfg(test)]
const READJUSTMENT_PERIOD: usize = 1;
let (u_len, v_len) = {
let u_len = {
let mut q = self.verification.unverified.lock();
q.shrink_to_fit();
q.len()
};
self.verification.verifying.lock().shrink_to_fit(); self.verification.verifying.lock().shrink_to_fit();
self.verification.verified.lock().shrink_to_fit();
} let v_len = {
let mut q = self.verification.verified.lock();
q.shrink_to_fit();
q.len()
};
(u_len as isize, v_len as isize)
};
self.processing.write().shrink_to_fit(); self.processing.write().shrink_to_fit();
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 diff = (v_len - u_len).abs();
let total = v_len + u_len;
self.scale_verifiers(
if u_len < 20 {
1
} else if diff <= total / 10 {
current
} else if v_len > u_len {
current - 1
} else {
current + 1
}
);
}
// wake up or sleep verifiers to get as close to the target as
// 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 target = max(1, target);
debug!(target: "verification", "Scaling from {} to {} verifiers", verifier_count, 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;
} }
} }
@ -509,10 +661,23 @@ impl<K: Kind> Drop for VerificationQueue<K> {
trace!(target: "shutdown", "[VerificationQueue] Closing..."); trace!(target: "shutdown", "[VerificationQueue] Closing...");
self.clear(); self.clear();
self.deleting.store(true, AtomicOrdering::Release); self.deleting.store(true, AtomicOrdering::Release);
self.more_to_verify.notify_all();
for t in self.verifiers.drain(..) { let mut verifiers = self.verifiers.get_mut();
t.join().unwrap(); 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();
} }
self.more_to_verify.notify_all();
// second pass to join.
for handle in verifiers.drain(..) {
handle.join();
}
trace!(target: "shutdown", "[VerificationQueue] Closed."); trace!(target: "shutdown", "[VerificationQueue] Closed.");
} }
} }
@ -611,4 +776,56 @@ mod tests {
} }
assert!(queue.queue_info().is_full()); assert!(queue.queue_info().is_full());
} }
#[test]
fn scaling_limits() {
use super::MAX_VERIFIERS;
let queue = get_test_queue();
queue.scale_verifiers(MAX_VERIFIERS + 1);
assert!(queue.verifiers.lock().1 < MAX_VERIFIERS + 1);
queue.scale_verifiers(0);
assert!(queue.verifiers.lock().1 == 1);
}
#[test]
fn readjust_verifiers() {
let queue = get_test_queue();
// 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
};
for block in get_good_dummy_block_seq(5000) {
queue.import(Unverified::new(block)).expect("Block good by definition; qed");
}
// 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();
}
}
queue.flush();
// nothing to verify == use minimum number of verifiers.
queue.collect_garbage();
assert_eq!(queue.verifiers.lock().1, 1);
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.58", "version": "0.2.73",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -102,6 +102,7 @@
"postcss-nested": "^1.0.0", "postcss-nested": "^1.0.0",
"postcss-simple-vars": "^3.0.0", "postcss-simple-vars": "^3.0.0",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react-addons-perf": "~15.3.2",
"react-addons-test-utils": "~15.3.2", "react-addons-test-utils": "~15.3.2",
"react-copy-to-clipboard": "^4.2.3", "react-copy-to-clipboard": "^4.2.3",
"react-dom": "~15.3.2", "react-dom": "~15.3.2",

View File

@ -15,7 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default function (rpc) { export default function (rpc) {
const subscriptions = []; let subscriptions = [];
let pollStatusIntervalId = null;
function getCoins () { function getCoins () {
return rpc.get('getcoins'); return rpc.get('getcoins');
@ -45,6 +46,24 @@ export default function (rpc) {
callback, callback,
idx idx
}); });
// Only poll if there are subscriptions...
if (!pollStatusIntervalId) {
pollStatusIntervalId = setInterval(_pollStatus, 2000);
}
}
function unsubscribe (depositAddress) {
const newSubscriptions = []
.concat(subscriptions)
.filter((sub) => sub.depositAddress !== depositAddress);
subscriptions = newSubscriptions;
if (subscriptions.length === 0) {
clearInterval(pollStatusIntervalId);
pollStatusIntervalId = null;
}
} }
function _getSubscriptionStatus (subscription) { function _getSubscriptionStatus (subscription) {
@ -81,13 +100,12 @@ export default function (rpc) {
subscriptions.forEach(_getSubscriptionStatus); subscriptions.forEach(_getSubscriptionStatus);
} }
setInterval(_pollStatus, 2000);
return { return {
getCoins, getCoins,
getMarketInfo, getMarketInfo,
getStatus, getStatus,
shift, shift,
subscribe subscribe,
unsubscribe
}; };
} }

View File

@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
import React from 'react'; import React from 'react';
export default ( export const termsOfService = (
<ul> <ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li> <li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li> <li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
@ -25,3 +26,18 @@ export default (
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li> <li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul> </ul>
); );
export const postToServer = (query) => {
query = stringify(query);
return fetch('https://sms-verification.parity.io/?' + query, {
method: 'POST', mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.json().then((data) => {
if (res.ok) {
return data.message;
}
throw new Error(data.message || 'unknown error');
});
});
};

View File

@ -48,7 +48,11 @@ export default class Contract {
this._instance[fn.signature] = fn; this._instance[fn.signature] = fn;
}); });
this._sendSubscriptionChanges(); this._subscribedToPendings = false;
this._pendingsSubscriptionId = null;
this._subscribedToBlock = false;
this._blockSubscriptionId = null;
} }
get address () { get address () {
@ -239,44 +243,71 @@ export default class Contract {
return event; return event;
} }
subscribe (eventName = null, options = {}, callback) { _findEvent (eventName = null) {
return new Promise((resolve, reject) => { const event = eventName
let event = null; ? this._events.find((evt) => evt.name === eventName)
: null;
if (eventName) { if (eventName && !event) {
event = this._events.find((evt) => evt.name === eventName); const events = this._events.map((evt) => evt.name).join(', ');
throw new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`);
}
if (!event) { return event;
const events = this._events.map((evt) => evt.name).join(', '); }
reject(new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`));
return;
}
}
return this._subscribe(event, options, callback).then(resolve).catch(reject); _createEthFilter (event = null, _options) {
const optionTopics = _options.topics || [];
const signature = event && event.signature || null;
// If event provided, remove the potential event signature
// as the first element of the topics
const topics = signature
? [ signature ].concat(optionTopics.filter((t, idx) => idx > 0 || t !== signature))
: optionTopics;
const options = Object.assign({}, _options, {
address: this._address,
topics
}); });
return this._api.eth.newFilter(options);
}
subscribe (eventName = null, options = {}, callback) {
try {
const event = this._findEvent(eventName);
return this._subscribe(event, options, callback);
} catch (e) {
return Promise.reject(e);
}
} }
_subscribe (event = null, _options, callback) { _subscribe (event = null, _options, callback) {
const subscriptionId = nextSubscriptionId++; const subscriptionId = nextSubscriptionId++;
const options = Object.assign({}, _options, { const { skipInitFetch } = _options;
address: this._address, delete _options['skipInitFetch'];
topics: [event ? event.signature : null]
});
return this._api.eth return this
.newFilter(options) ._createEthFilter(event, _options)
.then((filterId) => { .then((filterId) => {
this._subscriptions[subscriptionId] = {
options: _options,
callback,
filterId
};
if (skipInitFetch) {
this._subscribeToChanges();
return subscriptionId;
}
return this._api.eth return this._api.eth
.getFilterLogs(filterId) .getFilterLogs(filterId)
.then((logs) => { .then((logs) => {
callback(null, this.parseEventLogs(logs)); callback(null, this.parseEventLogs(logs));
this._subscriptions[subscriptionId] = {
options,
callback,
filterId
};
this._subscribeToChanges();
return subscriptionId; return subscriptionId;
}); });
}); });
@ -285,19 +316,89 @@ export default class Contract {
unsubscribe (subscriptionId) { unsubscribe (subscriptionId) {
return this._api.eth return this._api.eth
.uninstallFilter(this._subscriptions[subscriptionId].filterId) .uninstallFilter(this._subscriptions[subscriptionId].filterId)
.then(() => {
delete this._subscriptions[subscriptionId];
})
.catch((error) => { .catch((error) => {
console.error('unsubscribe', error); console.error('unsubscribe', error);
})
.then(() => {
delete this._subscriptions[subscriptionId];
this._unsubscribeFromChanges();
}); });
} }
_sendSubscriptionChanges = () => { _subscribeToChanges = () => {
const subscriptions = Object.values(this._subscriptions); const subscriptions = Object.values(this._subscriptions);
const timeout = () => setTimeout(this._sendSubscriptionChanges, 1000);
Promise const pendingSubscriptions = subscriptions
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
const otherSubscriptions = subscriptions
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
if (pendingSubscriptions.length > 0 && !this._subscribedToPendings) {
this._subscribedToPendings = true;
this._subscribeToPendings();
}
if (otherSubscriptions.length > 0 && !this._subscribedToBlock) {
this._subscribedToBlock = true;
this._subscribeToBlock();
}
}
_unsubscribeFromChanges = () => {
const subscriptions = Object.values(this._subscriptions);
const pendingSubscriptions = subscriptions
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
const otherSubscriptions = subscriptions
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
if (pendingSubscriptions.length === 0 && this._subscribedToPendings) {
this._subscribedToPendings = false;
clearTimeout(this._pendingsSubscriptionId);
}
if (otherSubscriptions.length === 0 && this._subscribedToBlock) {
this._subscribedToBlock = false;
this._api.unsubscribe(this._blockSubscriptionId);
}
}
_subscribeToBlock = () => {
this._api
.subscribe('eth_blockNumber', (error) => {
if (error) {
console.error('::_subscribeToBlock', error, error && error.stack);
}
const subscriptions = Object.values(this._subscriptions)
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
this._sendSubscriptionChanges(subscriptions);
})
.then((blockSubId) => {
this._blockSubscriptionId = blockSubId;
})
.catch((e) => {
console.error('::_subscribeToBlock', e, e && e.stack);
});
}
_subscribeToPendings = () => {
const subscriptions = Object.values(this._subscriptions)
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
const timeout = () => setTimeout(() => this._subscribeToPendings(), 1000);
this._sendSubscriptionChanges(subscriptions)
.then(() => {
this._pendingsSubscriptionId = timeout();
});
}
_sendSubscriptionChanges = (subscriptions) => {
return Promise
.all( .all(
subscriptions.map((subscription) => { subscriptions.map((subscription) => {
return this._api.eth.getFilterChanges(subscription.filterId); return this._api.eth.getFilterChanges(subscription.filterId);
@ -315,12 +416,9 @@ export default class Contract {
console.error('_sendSubscriptionChanges', error); console.error('_sendSubscriptionChanges', error);
} }
}); });
timeout();
}) })
.catch((error) => { .catch((error) => {
console.error('_sendSubscriptionChanges', error); console.error('_sendSubscriptionChanges', error);
timeout();
}); });
} }
} }

View File

@ -437,6 +437,7 @@ describe('api/contract/Contract', () => {
] ]
} }
]; ];
const logs = [{ const logs = [{
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c', address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
@ -450,6 +451,7 @@ describe('api/contract/Contract', () => {
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0' transactionIndex: '0x0'
}]; }];
const parsed = [{ const parsed = [{
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C', address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
@ -466,11 +468,13 @@ describe('api/contract/Contract', () => {
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' } sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
}, },
topics: [ topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0' '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
], ],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: new BigNumber(0) transactionIndex: new BigNumber(0)
}]; }];
let contract; let contract;
beforeEach(() => { beforeEach(() => {
@ -496,18 +500,19 @@ describe('api/contract/Contract', () => {
scope = mockHttp([ scope = mockHttp([
{ method: 'eth_newFilter', reply: { result: '0x123' } }, { method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } }, { method: 'eth_getFilterLogs', reply: { result: logs } },
{ method: 'eth_getFilterChanges', reply: { result: logs } },
{ method: 'eth_newFilter', reply: { result: '0x123' } }, { method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } } { method: 'eth_getFilterLogs', reply: { result: logs } }
]); ]);
cbb = sinon.stub(); cbb = sinon.stub();
cbe = sinon.stub(); cbe = sinon.stub();
return contract.subscribe('Message', {}, cbb); return contract.subscribe('Message', { toBlock: 'pending' }, cbb);
}); });
it('sets the subscriptionId returned', () => { it('sets the subscriptionId returned', () => {
return contract return contract
.subscribe('Message', {}, cbe) .subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => { .then((subscriptionId) => {
expect(subscriptionId).to.equal(1); expect(subscriptionId).to.equal(1);
}); });
@ -515,7 +520,7 @@ describe('api/contract/Contract', () => {
it('creates a new filter and retrieves the logs on it', () => { it('creates a new filter and retrieves the logs on it', () => {
return contract return contract
.subscribe('Message', {}, cbe) .subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => { .then((subscriptionId) => {
expect(scope.isDone()).to.be.true; expect(scope.isDone()).to.be.true;
}); });
@ -523,7 +528,7 @@ describe('api/contract/Contract', () => {
it('returns the logs to the callback', () => { it('returns the logs to the callback', () => {
return contract return contract
.subscribe('Message', {}, cbe) .subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => { .then((subscriptionId) => {
expect(cbe).to.have.been.calledWith(null, parsed); expect(cbe).to.have.been.calledWith(null, parsed);
}); });

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { range } from 'lodash';
import { isArray, isHex, isInstanceOf, isString } from '../util/types'; import { isArray, isHex, isInstanceOf, isString } from '../util/types';
@ -50,14 +51,19 @@ export function inHash (hash) {
return inHex(hash); return inHex(hash);
} }
export function pad (input, length) {
const value = inHex(input).substr(2, length * 2);
return '0x' + value + range(length * 2 - value.length).map(() => '0').join('');
}
export function inTopics (_topics) { export function inTopics (_topics) {
let topics = (_topics || []) let topics = (_topics || [])
.filter((topic) => topic) .filter((topic) => topic === null || topic)
.map(inHex); .map((topic) => topic === null ? null : pad(topic, 32));
while (topics.length < 4) { // while (topics.length < 4) {
topics.push(null); // topics.push(null);
} // }
return topics; return topics;
} }

View File

@ -123,6 +123,11 @@ export default class Parity {
.then((accounts) => (accounts || []).map(outAddress)); .then((accounts) => (accounts || []).map(outAddress));
} }
killAccount (account, password) {
return this._transport
.execute('parity_killAccount', inAddress(account), password);
}
listGethAccounts () { listGethAccounts () {
return this._transport return this._transport
.execute('parity_listGethAccounts') .execute('parity_listGethAccounts')

View File

@ -59,6 +59,7 @@ export default class Personal {
} }
switch (data.method) { switch (data.method) {
case 'parity_killAccount':
case 'parity_importGethAccounts': case 'parity_importGethAccounts':
case 'personal_newAccount': case 'personal_newAccount':
case 'parity_newAccountFromPhrase': case 'parity_newAccountFromPhrase':

View File

@ -29,21 +29,33 @@ export default class Ws extends JsonRpcBase {
this._token = token; this._token = token;
this._messages = {}; this._messages = {};
this._connecting = true; this._connecting = false;
this._connected = false;
this._lastError = null; this._lastError = null;
this._autoConnect = false; this._autoConnect = true;
this._retries = 0;
this._reconnectTimeoutId = null;
this._connect(); this._connect();
} }
updateToken (token) { updateToken (token) {
this._token = token; this._token = token;
this._autoConnect = false; this._autoConnect = true;
this._connect(); this._connect();
} }
_connect () { _connect () {
if (this._connecting) {
return;
}
if (this._reconnectTimeoutId) {
window.clearTimeout(this._reconnectTimeoutId);
this._reconnectTimeoutId = null;
}
const time = parseInt(new Date().getTime() / 1000, 10); const time = parseInt(new Date().getTime() / 1000, 10);
const sha3 = keccak_256(`${this._token}:${time}`); const sha3 = keccak_256(`${this._token}:${time}`);
const hash = `${sha3}_${time}`; const hash = `${sha3}_${time}`;
@ -53,6 +65,7 @@ export default class Ws extends JsonRpcBase {
this._ws.onopen = null; this._ws.onopen = null;
this._ws.onclose = null; this._ws.onclose = null;
this._ws.onmessage = null; this._ws.onmessage = null;
this._ws.close();
this._ws = null; this._ws = null;
} }
@ -65,6 +78,27 @@ export default class Ws extends JsonRpcBase {
this._ws.onopen = this._onOpen; this._ws.onopen = this._onOpen;
this._ws.onclose = this._onClose; this._ws.onclose = this._onClose;
this._ws.onmessage = this._onMessage; this._ws.onmessage = this._onMessage;
// Get counts in dev mode
if (process.env.NODE_ENV === 'development') {
this._count = 0;
this._lastCount = {
timestamp: Date.now(),
count: 0
};
window.setInterval(() => {
const n = this._count - this._lastCount.count;
const t = (Date.now() - this._lastCount.timestamp) / 1000;
const s = Math.round(1000 * n / t) / 1000;
if (this._debug) {
console.log('::parityWS', `speed: ${s} req/s`, `count: ${this._count}`);
}
}, 5000);
window._parityWS = this;
}
} }
_onOpen = (event) => { _onOpen = (event) => {
@ -72,6 +106,7 @@ export default class Ws extends JsonRpcBase {
this._connected = true; this._connected = true;
this._connecting = false; this._connecting = false;
this._autoConnect = true; this._autoConnect = true;
this._retries = 0;
Object.keys(this._messages) Object.keys(this._messages)
.filter((id) => this._messages[id].queued) .filter((id) => this._messages[id].queued)
@ -79,18 +114,39 @@ export default class Ws extends JsonRpcBase {
} }
_onClose = (event) => { _onClose = (event) => {
console.log('ws:onClose', event);
this._connected = false; this._connected = false;
this._connecting = false; this._connecting = false;
this._lastError = event;
if (this._autoConnect) { if (this._autoConnect) {
setTimeout(() => this._connect(), 500); const timeout = this.retryTimeout;
const time = timeout < 1000
? Math.round(timeout) + 'ms'
: (Math.round(timeout / 10) / 100) + 's';
console.log('ws:onClose', `trying again in ${time}...`);
this._reconnectTimeoutId = setTimeout(() => {
this._connect();
}, timeout);
return;
} }
console.log('ws:onClose', event);
} }
_onError = (event) => { _onError = (event) => {
console.error('ws:onError', event); // Only print error if the WS is connected
this._lastError = event; // ie. don't print if error == closed
window.setTimeout(() => {
if (this._connected) {
console.error('ws:onError', event);
this._lastError = event;
}
}, 50);
} }
_onMessage = (event) => { _onMessage = (event) => {
@ -127,11 +183,16 @@ export default class Ws extends JsonRpcBase {
_send = (id) => { _send = (id) => {
const message = this._messages[id]; const message = this._messages[id];
message.queued = !this._connected;
if (this._connected) { if (this._connected) {
this._ws.send(message.json); if (process.env.NODE_ENV === 'development') {
this._count++;
}
return this._ws.send(message.json);
} }
message.queued = !this._connected;
message.timestamp = Date.now();
} }
execute (method, ...params) { execute (method, ...params) {
@ -159,4 +220,27 @@ export default class Ws extends JsonRpcBase {
get lastError () { get lastError () {
return this._lastError; return this._lastError;
} }
/**
* Exponential Timeout for Retries
*
* @see http://dthain.blogspot.de/2009/02/exponential-backoff-in-distributed.html
*/
get retryTimeout () {
// R between 1 and 2
const R = Math.random() + 1;
// Initial timeout (100ms)
const T = 100;
// Exponential Factor
const F = 2;
// Max timeout (4s)
const M = 4000;
// Current number of retries
const N = this._retries;
// Increase retries number
this._retries++;
return Math.min(R * T * Math.pow(F, N), M);
}
} }

View File

@ -29,3 +29,7 @@ export function hex2Ascii (_hex) {
return str; return str;
} }
export function asciiToHex (string) {
return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join('');
}

View File

@ -16,7 +16,7 @@
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address'; import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode'; import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
import { bytesToHex, hex2Ascii } from './format'; import { bytesToHex, hex2Ascii, asciiToHex } from './format';
import { fromWei, toWei } from './wei'; import { fromWei, toWei } from './wei';
import { sha3 } from './sha3'; import { sha3 } from './sha3';
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types'; import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
@ -31,6 +31,7 @@ export default {
isString, isString,
bytesToHex, bytesToHex,
hex2Ascii, hex2Ascii,
asciiToHex,
createIdentityImg, createIdentityImg,
decodeCallData, decodeCallData,
decodeMethodInput, decodeMethodInput,

View File

@ -21,25 +21,39 @@ export default class Registry {
this._api = api; this._api = api;
this._contracts = []; this._contracts = [];
this._instance = null; this._instance = null;
this._fetching = false;
this._queue = [];
this.getInstance(); this.getInstance();
} }
getInstance () { getInstance () {
return new Promise((resolve, reject) => { if (this._instance) {
if (this._instance) { return Promise.resolve(this._instance);
resolve(this._instance); }
return;
}
this._api.parity if (this._fetching) {
.registryAddress() return new Promise((resolve) => {
.then((address) => { this._queue.push({ resolve });
this._instance = this._api.newContract(abis.registry, address).instance; });
resolve(this._instance); }
})
.catch(reject); this._fetching = true;
});
return this._api.parity
.registryAddress()
.then((address) => {
this._fetching = false;
this._instance = this._api.newContract(abis.registry, address).instance;
this._queue.forEach((queued) => {
queued.resolve(this._instance);
});
this._queue = [];
return this._instance;
});
} }
getContract (_name) { getContract (_name) {

View File

@ -14,39 +14,39 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
export const checkIfVerified = (contract, account) => { export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]); return contract.instance.certified.call({}, [account]);
}; };
export const checkIfRequested = (contract, account) => { export const checkIfRequested = (contract, account) => {
return new Promise((resolve, reject) => { let subId = null;
contract.subscribe('Requested', { let resolved = false;
fromBlock: 0, toBlock: 'pending'
}, (err, logs) => {
if (err) {
return reject(err);
}
const e = logs.find((l) => {
return l.type === 'mined' && l.params.who && l.params.who.value === account;
});
resolve(e ? e.transactionHash : false);
});
});
};
export const postToServer = (query) => { return new Promise((resolve, reject) => {
query = stringify(query); contract
return fetch('https://sms-verification.parity.io/?' + query, { .subscribe('Requested', {
method: 'POST', mode: 'cors', cache: 'no-store' fromBlock: 0, toBlock: 'pending'
}) }, (err, logs) => {
.then((res) => { if (err) {
return res.json().then((data) => { return reject(err);
if (res.ok) { }
return data.message; const e = logs.find((l) => {
} return l.type === 'mined' && l.params.who && l.params.who.value === account;
throw new Error(data.message || 'unknown error'); });
});
resolve(e ? e.transactionHash : false);
resolved = true;
if (subId) {
contract.unsubscribe(subId);
}
})
.then((_subId) => {
subId = _subId;
if (resolved) {
contract.unsubscribe(subId);
}
});
}); });
}; };

View File

@ -105,7 +105,7 @@ export function attachInstances () {
]) ])
.then(([registryAddress, netChain]) => { .then(([registryAddress, netChain]) => {
const registry = api.newContract(abis.registry, registryAddress).instance; const registry = api.newContract(abis.registry, registryAddress).instance;
isTest = netChain === 'morden' || netChain === 'testnet'; isTest = ['morden', 'ropsten', 'testnet'].includes(netChain);
console.log(`contract was found at registry=${registryAddress}`); console.log(`contract was found at registry=${registryAddress}`);
console.log(`running on ${netChain}, isTest=${isTest}`); console.log(`running on ${netChain}, isTest=${isTest}`);

17
js/src/dapps/dappreg.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>Dapp Registry</title>
</head>
<body>
<div id="container"></div>
<script src="vendor.js"></script>
<script src="commons.js"></script>
<script src="/parity-utils/parity.js"></script>
<script src="dappreg.js"></script>
</body>
</html>

35
js/src/dapps/dappreg.js Normal file
View File

@ -0,0 +1,35 @@
// 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 <http://www.gnu.org/licenses/>.
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { useStrict } from 'mobx';
injectTapEventPlugin();
useStrict(true);
import Application from './dappreg/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
import './dappreg.html';
ReactDOM.render(
<Application />,
document.querySelector('#container')
);

View File

@ -0,0 +1,58 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.body {
color: #333;
background: #eee;
padding: 4.5em 0;
text-align: center;
}
.apps {
background: #fff;
border-radius: 0.5em;
margin: 0 auto;
max-width: 980px;
padding: 1.5em;
text-align: left;
}
.footer {
font-size: 0.75em;
margin: 1em;
padding: 1.5em;
text-align: center;
}
.header {
background: #44e;
border-radius: 0 0 0.25em 0.25em;
color: #fff;
left: 0;
padding: 1em;
position: fixed;
right: 0;
top: 0;
z-index: 25;
}
.loading {
text-align: center;
padding-top: 5em;
font-size: 2em;
color: #999;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ButtonBar from '../ButtonBar';
import Dapp from '../Dapp';
import ModalDelete from '../ModalDelete';
import ModalRegister from '../ModalRegister';
import ModalUpdate from '../ModalUpdate';
import SelectDapp from '../SelectDapp';
import Warning from '../Warning';
import styles from './application.css';
@observer
export default class Application extends Component {
dappsStore = DappsStore.instance();
render () {
if (this.dappsStore.isLoading) {
return (
<div className={ styles.loading }>
Loading application
</div>
);
}
return (
<div className={ styles.body }>
<div className={ styles.header }>
DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together.
</div>
<div className={ styles.apps }>
<SelectDapp />
<ButtonBar />
<Dapp />
</div>
<div className={ styles.footer }>
{ this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user
</div>
<Warning />
<ModalDelete />
<ModalRegister />
<ModalUpdate />
</div>
);
}
}

View File

@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './RequestPendingWeb3'; export default from './application';

View File

@ -0,0 +1,38 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.button {
background: #44e;
border: none;
border-radius: 0.25em;
color: #fff;
cursor: pointer;
font-size: 1em;
margin: 1em 0.375em;
opacity: 0.85;
padding: 0.75em 2em;
&[disabled] {
opacity: 0.5;
cursor: default;
background: #aaa;
}
&[data-warning="true"] {
background: #e44;
}
}

View File

@ -0,0 +1,52 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './button.css';
export default class Button extends Component {
static propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool,
label: PropTypes.string.isRequired,
warning: PropTypes.bool,
onClick: PropTypes.func.isRequired
}
render () {
const { className, disabled, label, warning } = this.props;
const classes = `${styles.button} ${className}`;
return (
<button
className={ classes }
data-warning={ warning }
disabled={ disabled }
onClick={ this.onClick }>
{ label }
</button>
);
}
onClick = (event) => {
if (this.props.disabled) {
return;
}
this.props.onClick(event);
}
}

View File

@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './RequestFinishedWeb3'; export default from './button';

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
.buttonbar {
text-align: center;
margin: 1em 0 0 0;
}

View File

@ -0,0 +1,101 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import styles from './buttonBar.css';
@observer
export default class ButtonBar extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
let buttons = [];
if (this.dappsStore.isEditing || this.dappsStore.isNew) {
buttons = [
<Button
key='cancel'
label='Cancel'
warning
onClick={ this.onCancelClick } />,
<Button
key='save'
label={ this.dappsStore.isNew ? 'Register' : 'Update' }
disabled={ !this.dappsStore.canSave }
onClick={ this.onSaveClick } />
];
} else {
buttons = [
<Button
key='delete'
label='Delete'
warning
disabled={ !this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner }
onClick={ this.onDeleteClick } />,
<Button
key='edit'
label='Edit'
disabled={ !this.dappsStore.currentApp.isOwner }
onClick={ this.onEditClick } />,
<Button
key='new'
label='New'
onClick={ this.onNewClick } />
];
}
return (
<div className={ styles.buttonbar }>
{ buttons }
</div>
);
}
onCancelClick = () => {
if (this.dappsStore.isEditing) {
this.dappsStore.setEditing(false);
} else {
this.dappsStore.setNew(false);
}
}
onDeleteClick = () => {
this.modalStore.showDelete();
}
onEditClick = () => {
this.dappsStore.setEditing(true);
}
onNewClick = () => {
this.dappsStore.setNew(true);
}
onSaveClick = () => {
if (this.dappsStore.isEditing) {
this.modalStore.showUpdate();
} else {
this.modalStore.showRegister();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './buttonBar';

View File

@ -0,0 +1,19 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.app {
}

View File

@ -0,0 +1,162 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import Input from '../Input';
import SelectAccount from '../SelectAccount';
import styles from './dapp.css';
@observer
export default class Dapp extends Component {
dappsStore = DappsStore.instance();
render () {
const app = this.dappsStore.isNew || this.dappsStore.isEditing
? this.dappsStore.wipApp
: this.dappsStore.currentApp;
return (
<div className={ styles.app }>
{ this.dappsStore.isNew ? this.renderOwnerSelect(app) : this.renderOwnerStatic(app) }
{ this.renderInputs(app) }
</div>
);
}
renderInputs (app) {
if (this.dappsStore.isNew) {
return null;
}
return [
this.renderHashInput(app, 'image', 'Image hash, as generated by Githubhint', true),
this.renderHashInput(app, 'manifest', 'Manifest hash, as generated by Githubhint'),
this.renderHashInput(app, 'content', 'Content hash, as generated by Githubhint')
];
}
renderOwnerSelect (app) {
const overlayImage = (
<img
className={ styles.overlayImage }
src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 4) } />
);
return (
<Input
hint={ this.dappsStore.currentAccount.address }
label='Owner, select the application owner and editor'
overlay={ overlayImage }>
<SelectAccount />
</Input>
);
}
renderOwnerStatic (app) {
const overlayImage = (
<img
className={ styles.overlayImage }
src={ api.util.createIdentityImg(app.owner, 4) } />
);
return (
<Input
hint={ app.owner }
label='Owner, the application owner and editor'
overlay={ overlayImage }>
<input value={ app.ownerName } readOnly />
</Input>
);
}
renderHashInput (app, type, label, withImage = false) {
const onChange = (event) => this.onChangeHash(event, type);
const hash = app[`${type}Hash`];
let overlayImage = null;
if (withImage && hash) {
overlayImage = (
<img
className={ styles.overlayImage }
src={ `/api/content/${hash.substr(2)}` } />
);
}
return (
<Input
hint={ app[`${type}Error`] || app[`${type}Url`] || '...' }
label={ label }
key={ `${type}Edit` }
overlay={ overlayImage }>
<input
value={ app[`${type}Hash`] || '' }
data-dirty={ app[`${type}Changed`] }
data-error={ !!app[`${type}Error`] }
readOnly={ !this.dappsStore.isEditing && !this.dappsStore.isNew }
onChange={ onChange } />
</Input>
);
}
onChangeHash (event, type) {
if (!this.dappsStore.isNew && !this.dappsStore.isEditing) {
return;
}
const hash = event.target.value;
let changed = false;
let url = null;
if (this.dappsStore.isNew) {
if (hash && hash.length) {
changed = true;
}
} else {
if (this.dappsStore.currentApp[`${type}Hash`] !== hash) {
changed = true;
} else {
url = this.dappsStore.currentApp[`${type}Url`];
}
}
this.dappsStore.editWip({
[`${type}Changed`]: changed,
[`${type}Error`]: null,
[`${type}Hash`]: hash,
[`${type}Url`]: changed ? 'Resolving url from hash' : url
});
if (changed) {
if (hash.length) {
this.dappsStore
.lookupHash(hash)
.then((url) => {
this.dappsStore.editWip({
[`${type}Error`]: url ? null : 'Unable to resolve url',
[`${type}Url`]: url
});
});
} else {
this.dappsStore.editWip({ [`${type}Url`]: null });
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './dapp';

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './input';

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
.input {
position: relative;
input, select {
background: rgba(255, 255, 255, 0.85);
border: 4px solid rgba(223, 223, 223, 0.85);
border-radius: 0.25em;
box-sizing: border-box;
color: #333;
font-size: 1em;
margin: 0.25em 0 0.25em 0;
padding: 0.5em 0.5em 1.5em 0.5em;
width: 100%;
}
input {
padding-bottom: 1.5em;
&[data-dirty="true"] {
background: rgba(255, 255, 203, 0.85);
border-color: rgba(203, 203, 151, 0.85);
}
&[data-error="true"] {
background: rgba(255, 223, 223, 0.85) !important;
border-color: rgba(223, 191, 191, 0.85) !important;
}
&[readonly] {
background: rgba(239, 239, 239, 0.85);
border-color: rgba(223, 223, 223, 0.85);
}
}
label {
color: #888;
display: block;
font-size: 0.75em;
margin-top: 1.5em;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
height: 58px;
&[disabled] {
background: rgba(239, 239, 239, 0.85);
border-color: rgba(223, 223, 223, 0.85);
}
}
.hint {
color: #888;
display: block;
font-size: 0.75em;
position: absolute;
right: 52px;
text-align: right;
top: 52px;
}
.overlay {
right: 10px;
position: absolute;
top: 30px;
img {
border-radius: 50%;
height: 32px;
width: 32px;
}
}
}

View File

@ -0,0 +1,47 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './input.css';
export default class Input extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
hint: PropTypes.string,
label: PropTypes.string.isRequired,
overlay: PropTypes.node
}
render () {
const { children, hint, label, overlay } = this.props;
return (
<div className={ styles.input }>
<label>
{ label }
</label>
{ children }
<div className={ styles.hint }>
{ hint }
</div>
<div className={ styles.overlay }>
{ overlay }
</div>
</div>
);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './modal';

View File

@ -0,0 +1,116 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.modal {
.body {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
text-align: center;
z-index: 50;
.dialog {
background: #fff;
border-radius: 0 0 0.25em 0.25em;
margin: 0 auto;
max-width: 840px;
text-align: left;
.content {
line-height: 1.5em;
padding: 2em;
text-align: center;
.section {
margin: 0;
padding: 0;
&.error {
color: #f44;
}
}
.section+.section {
margin-top: 1em;
}
}
.footer {
padding: 0.5em 1.625em;
text-align: right;
}
.header {
background: #44e;
color: #fff;
opacity: 0.85;
padding: 1em;
&.error {
background: #e44;
}
}
}
}
.overlay {
background: rgba(204, 204, 204, 0.7);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 49;
}
}
.account {
div {
display: inline-block;
vertical-align: top;
}
img {
border-radius: 50%;
margin-right: 0.5em;
}
}
.hint {
display: block !important;
color: #888;
font-size: 0.75em;
margin-top: -0.5em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.center {
text-align: center;
}
.heading {
color: #888;
font-size: 0.75em;
}
.light {
color: #888;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './modal.css';
export default class Modal extends Component {
static propTypes = {
buttons: PropTypes.node,
children: PropTypes.node,
error: PropTypes.object,
header: PropTypes.string
}
render () {
const { children, buttons, error, header } = this.props;
return (
<div className={ styles.modal }>
<div className={ styles.overlay } />
<div className={ styles.body }>
<div className={ styles.dialog }>
<div className={ `${styles.header} ${error ? styles.error : ''}` }>
{ header }
</div>
<div className={ styles.content }>
{ error ? this.renderError() : children }
</div>
<div className={ styles.footer }>
{ buttons }
</div>
</div>
</div>
</div>
);
}
renderError () {
const { error } = this.props;
return (
<div>
<div className={ styles.section }>
Your operation failed to complete sucessfully. The following error was returned:
</div>
<div className={ `${styles.section} ${styles.error}` }>
{ error.toString() }
</div>
</div>
);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './modalDelete';

View File

@ -0,0 +1,159 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Deletion',
'Confirm Application Deletion',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Deletion Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalDelete extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingDelete) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorDelete }
header={ HEADERS[this.modalStore.stepDelete] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepDelete) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='delete'
label='Yes, Delete'
warning
onClick={ this.onClickYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepDelete) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application has been removed from the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to remove a distributed application from the registry, the details of this application is given below. Removal does not return any fees, however the application will not be available to users anymore.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Owner account
</div>
<div className={ styles.account }>
<img src={ api.util.createIdentityImg(this.dappsStore.currentApp.owner, 3) } />
<div>{ this.dappsStore.currentApp.ownerName }</div>
<div className={ styles.address }>{ this.dappsStore.currentApp.owner }</div>
</div>
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Application identifier
</div>
<div>
{ this.dappsStore.currentApp.id }
</div>
</div>
</div>
);
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideDelete();
}
onClickYes = () => {
this.modalStore.doDelete();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './modalRegister';

View File

@ -0,0 +1,159 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Registration',
'Confirm Application Registration',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Registration Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalRegister extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingRegister) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorRegister }
header={ HEADERS[this.modalStore.stepRegister] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepRegister) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='register'
label='Yes, Register'
warning
onClick={ this.onClickConfirmYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepRegister) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application has been registered in the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to register a new distributed application on the network, the details of this application is given below. This will require a non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small>.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Selected owner account
</div>
<div className={ styles.account }>
<img src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 3) } />
<div>{ this.dappsStore.currentAccount.name }</div>
<div className={ styles.hint }>{ this.dappsStore.currentAccount.address }</div>
</div>
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Unique assigned application identifier
</div>
<div>
{ this.dappsStore.wipApp.id }
</div>
</div>
</div>
);
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideRegister();
}
onClickConfirmYes = () => {
this.modalStore.doRegister();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './modalUpdate';

View File

@ -0,0 +1,169 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Update',
'Confirm Application Update',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Update Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalUpdate extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingUpdate) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorUpdate }
header={ HEADERS[this.modalStore.stepUpdate] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepUpdate) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='delete'
label='Yes, Update'
warning
onClick={ this.onClickYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepUpdate) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application metadata has been updated in the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to update the application details in the registry, the details of these updates are given below. Please note that each update will generate a seperate transaction.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Application identifier
</div>
<div>
{ this.dappsStore.wipApp.id }
</div>
</div>
{ this.renderChanges() }
</div>
);
}
renderChanges () {
return ['content', 'image', 'manifest']
.filter((type) => this.dappsStore.wipApp[`${type}Changed`])
.map((type) => {
return (
<div className={ styles.section } key={ `${type}Update` }>
<div className={ styles.heading }>
Updates to { type } hash
</div>
<div>
<div>{ this.dappsStore.wipApp[`${type}Hash`] || '(removed)' }</div>
<div className={ styles.hint }>
{ this.dappsStore.wipApp[`${type}Url`] || 'current url to be removed from registry' }
</div>
</div>
</div>
);
});
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideUpdate();
}
onClickYes = () => {
this.modalStore.doUpdate();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './selectAccount';

View File

@ -0,0 +1,49 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
@observer
export default class SelectAccount extends Component {
dappsStore = DappsStore.instance();
render () {
return (
<select
value={ this.dappsStore.currentAccount.address }
onChange={ this.onSelect }>
{ this.renderOptions() }
</select>
);
}
renderOptions () {
return this.dappsStore.accounts.map((account) => {
return (
<option value={ account.address } key={ account.address }>
{ account.name }
</option>
);
});
}
onSelect = (event) => {
this.dappsStore.setCurrentAccount(event.target.value);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './selectDapp';

View File

@ -0,0 +1,76 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import Input from '../Input';
@observer
export default class SelectDapp extends Component {
dappsStore = DappsStore.instance();
render () {
if (this.dappsStore.isNew) {
return (
<Input
hint='...'
label='Application Id, the unique assigned identifier'>
<input value={ this.dappsStore.wipApp.id } readOnly />
</Input>
);
}
let overlayImg = null;
if (this.dappsStore.currentApp.imageHash) {
overlayImg = (
<img src={ `/api/content/${this.dappsStore.currentApp.imageHash.substr(2)}` } />
);
}
return (
<Input
hint={ this.dappsStore.currentApp.id }
label='Application, the actual application details to show below'
overlay={ overlayImg }>
<select
disabled={ this.dappsStore.isEditing }
value={ this.dappsStore.currentApp.id }
onChange={ this.onSelect }>
{ this.renderOptions() }
</select>
</Input>
);
}
renderOptions () {
return this.dappsStore.apps.map((app) => {
return (
<option
value={ app.id }
key={ app.id }>
{ app.name }
</option>
);
});
}
onSelect = (event) => {
this.dappsStore.setCurrentApp(event.target.value);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './warning';

View File

@ -0,0 +1,36 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.warning {
background: #f44;
border-top-right-radius: 0.25em;
bottom: 0;
color: #fff;
cursor: pointer;
font-size: 0.75em;
left: 0;
line-height: 1.5em;
opacity: 1;
padding: 1.5em;
position: fixed;
max-width: 540px;
z-index: 100;
div+div {
margin-top: 1.5em;
}
}

View File

@ -0,0 +1,51 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import styles from './warning.css';
@observer
export default class Warning extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingWarning) {
return null;
}
return (
<div className={ styles.warning } onClick={ this.onClose }>
<div>
WARNING: Registering a dapp is for developers only. Please ensure you understand the steps needed to develop and deploy applications, should you wish to use this dapp for anything apart from queries.
</div>
<div>
A non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small> is required for any registration.
</div>
</div>
);
}
onClose = () => {
this.modalStore.hideWarning();
}
}

View File

@ -0,0 +1,482 @@
// 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 <http://www.gnu.org/licenses/>.
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 { api } from './parity';
let instance = null;
export default class DappsStore {
@observable accounts = [];
@observable addresses = [];
@observable apps = [];
@observable contractOwner = null;
@observable currentAccount = null;
@observable currentApp = null;
@observable count = 0;
@observable fee = new BigNumber(0);
@observable isContractOwner = false;
@observable isEditing = false;
@observable isLoading = true;
@observable isNew = false;
@observable wipApp = null;
_startTime = Date.now();
constructor () {
this._loadDapps();
}
static instance () {
if (!instance) {
instance = new DappsStore();
}
return instance;
}
@computed get canSave () {
const app = this.wipApp;
const hasError = app.contentError || app.imageError || app.manifestError;
const isDirty = this.isNew || app.contentChanged || app.imageChanged || app.manifestChanged;
const isEditMode = this.isEditing || this.isNew;
return isEditMode && isDirty && !hasError;
}
@computed get isCurrentEditable () {
return !!this.accounts.find((account) => account.address === this.currentApp.owner);
}
@computed get ownedCount () {
return (this.apps.filter((app) => app.isOwner) || []).length;
}
@action copyToWip = () => {
let wipApp;
if (this.isNew) {
wipApp = {
id: api.util.sha3(`${this._startTime}_${Date.now()}`),
contentHash: null,
contentUrl: null,
imageHash: null,
imageUrl: null,
manifestHash: null,
manifestUrl: null
};
} else {
const app = this.currentApp;
wipApp = {
id: app.id,
contentHash: app.contentHash,
contentUrl: app.contentUrl,
imageHash: app.imageHash,
imageUrl: app.imageUrl,
manifestHash: app.manifestHash,
manifestUrl: app.manifestUrl,
owner: app.owner,
ownerName: app.ownerName
};
}
this.wipApp = Object.assign(wipApp, {
contentChanged: false,
contentError: null,
imageChanged: false,
imageError: null,
manifestChanged: false,
manifestError: null
});
return this.wipApp;
}
@action editWip = (details) => {
if (this.isNew || this.isEditing) {
transaction(() => {
Object
.keys(details)
.forEach((key) => {
this.wipApp[key] = details[key];
});
});
}
return this.wipApp;
}
@action sortApps = (apps = this.apps) => {
transaction(() => {
const ownApps = apps
.filter((app) => app.isOwner)
.sort((a, b) => a.name.localeCompare(b.name));
const otherApps = apps
.filter((app) => !app.isOwner)
.sort((a, b) => a.name.localeCompare(b.name));
this.apps = ownApps.concat(otherApps);
this.currentApp = this.apps[0];
});
}
@action setApps = (apps) => {
this.sortApps(apps.filter((app) => {
const bnid = new BigNumber(app.id);
return bnid.gt(0);
}));
return this.apps;
}
@action _addApp = (app) => {
transaction(() => {
this.setApps(this.apps.concat([app]));
this.setCurrentApp(app.id);
});
}
@action addApp = (appId, account) => {
this
._loadDapp({
id: appId,
isOwner: true,
name: `- ${appId}`,
owner: account.address,
ownerName: account.name
})
.then(this._addApp);
}
@action refreshApp = (appId) => {
this._loadDapp(this.apps.find((app) => app.id === appId));
}
@action removeApp = (appId) => {
this.setApps(this.apps.filter((app) => app.id !== appId));
}
@action setAppInfo = (app, info) => {
transaction(() => {
Object.keys(info).forEach((key) => {
app[key] = info[key];
});
});
return app;
}
@action setAccounts = (accountsInfo) => {
transaction(() => {
this.addresses = Object
.keys(accountsInfo)
.map((address) => {
const account = accountsInfo[address];
account.address = address;
return account;
});
this.accounts = this.addresses.filter((account) => account.uuid);
this.currentAccount = this.accounts[0];
});
return this.accounts;
}
@action setContractOwner = (contractOwner) => {
transaction(() => {
this.contractOwner = contractOwner;
this.isContractOwner = !!this.accounts.find((account) => account.address === contractOwner);
});
return contractOwner;
}
@action setCurrentApp = (id) => {
this.currentApp = this.apps.find((app) => app.id === id);
return this.currentApp;
}
@action setCurrentAccount = (address) => {
this.currentAccount = this.accounts.find((account) => account.address === address);
return this.currentAccount;
}
@action setCount = (count) => {
this.count = count;
return count;
}
@action setEditing = (mode) => {
transaction(() => {
this.isEditing = mode;
this.copyToWip();
});
return mode;
}
@action setFee = (fee) => {
this.fee = fee;
return fee;
}
@action setLoading = (loading) => {
this.isLoading = loading;
return loading;
}
@action setNew = (mode) => {
transaction(() => {
this.isNew = mode;
this.copyToWip();
});
return mode;
}
lookupHash (hash) {
return this._retrieveUrl(hash);
}
_getCount () {
return this._instanceReg
.count.call()
.then((count) => {
this.setCount(count.toNumber());
})
.catch((error) => {
console.error('Store:getCount', error);
});
}
_getFee () {
return this._instanceReg
.fee.call()
.then(this.setFee)
.catch((error) => {
console.error('Store:getFee', error);
});
}
_getOwner () {
return this._instanceReg
.owner.call()
.then(this.setContractOwner)
.catch((error) => {
console.error('Store:getOwner', error);
});
}
_loadDapps () {
return this._loadRegistry()
.then(() => Promise.all([
this._attachContracts(),
this._loadAccounts()
]))
.then(() => Promise.all([
this._getCount(),
this._getFee(),
this._getOwner()
]))
.then(() => {
const promises = [];
for (let index = 0; index < this.count; index++) {
promises.push(this._instanceReg.at.call({}, [index]));
}
return Promise.all(promises);
})
.then((appsInfo) => {
return Promise.all(
this
.setApps(appsInfo.map(([appId, owner]) => {
const isOwner = !!this.accounts.find((account) => account.address === owner);
const account = this.addresses.find((account) => account.address === owner);
const id = api.util.bytesToHex(appId);
return {
id,
owner,
ownerName: account ? account.name : owner,
isOwner,
name: `- ${id}`
};
}))
.map(this._loadDapp)
);
})
.then(() => {
this.sortApps();
this.setLoading(this.count === 0);
})
.catch((error) => {
console.error('Store:loadDapps', error);
});
}
_loadDapp = (app) => {
return Promise
.all([
this._loadMeta(app.id, 'CONTENT'),
this._loadMeta(app.id, 'IMG'),
this._loadMeta(app.id, 'MANIFEST')
])
.then(([contentHash, imageHash, manifestHash]) => {
return Promise
.all([
this._retrieveUrl(contentHash),
this._retrieveUrl(imageHash),
this._retrieveUrl(manifestHash)
])
.then(([contentUrl, imageUrl, manifestUrl]) => {
return this
._loadManifest(app.id, manifestHash)
.then((manifest) => {
this.setAppInfo(app, {
manifest,
manifestHash,
manifestUrl,
contentHash,
contentUrl,
imageHash,
imageUrl,
name: (manifest && manifest.name) || `- ${app.id}`
});
return app;
});
});
})
.catch((error) => {
console.error('Store:loadDapp', error);
});
}
_loadMeta (appId, key) {
return this._instanceReg
.meta.call({}, [appId, key])
.then((meta) => {
const hash = api.util.bytesToHex(meta);
const bnhash = new BigNumber(hash);
return bnhash.gt(0)
? hash
: null;
})
.catch((error) => {
console.error('Store:loadMeta', error);
return null;
});
}
_loadManifest (appId, manifestHash) {
const builtin = builtins.find((app) => app.id === appId);
if (builtin) {
return Promise.resolve(builtin);
} else if (!manifestHash) {
return Promise.resolve(null);
}
return fetch(`/api/content/${manifestHash.substr(2)}/`, { redirect: 'follow', mode: 'cors' })
.then((response) => {
return response.ok
? response.json()
: null;
})
.catch((error) => {
console.error('Store:loadManifest', error);
return null;
});
}
_retrieveUrl (urlHash) {
if (!urlHash) {
return Promise.resolve(null);
}
return this._instanceGhh
.entries.call({}, [urlHash])
.then(([repo, _commit, owner]) => {
const bnowner = new BigNumber(owner);
if (bnowner.eq(0)) {
return null;
}
const commit = api.util.bytesToHex(_commit);
const bncommit = new BigNumber(commit);
if (bncommit.eq(0)) {
return repo;
} else {
return `https://codeload.github.com/${repo}/zip/${commit.substr(2)}`;
}
})
.catch((error) => {
console.error('Store:retriveUrl', error);
return null;
});
}
_loadAccounts () {
return api.parity
.accounts()
.then(this.setAccounts)
.catch((error) => {
console.error('Store:loadAccounts', error);
});
}
_loadRegistry () {
return api.parity
.registryAddress()
.then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`);
this._registry = api.newContract(abis.registry, registryAddress).instance;
})
.catch((error) => {
console.error('Store:loadRegistry', error);
});
}
_attachContracts () {
return Promise
.all([
this._registry.getAddress.call({}, [api.util.sha3('dappreg'), 'A']),
this._registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A'])
])
.then(([dappregAddress, ghhAddress]) => {
console.log(`dappreg was found at ${dappregAddress}`);
this._contractReg = api.newContract(abis.dappreg, dappregAddress);
this._instanceReg = this._contractReg.instance;
console.log(`githubhint was found at ${ghhAddress}`);
this._contractGhh = api.newContract(abis.githubhint, ghhAddress);
this._instanceGhh = this._contractGhh.instance;
})
.catch((error) => {
console.error('Store:attachContract', error);
});
}
}

View File

@ -0,0 +1,266 @@
// 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 <http://www.gnu.org/licenses/>.
import { action, observable, transaction } from 'mobx';
import { trackRequest } from './parity';
import DappsStore from './dappsStore';
let instance = null;
export default class ModalStore {
@observable errorDelete = null;
@observable errorRegister = null;
@observable errorUpdate = null;
@observable stepDelete = 0;
@observable stepRegister = 0;
@observable stepUpdate = 0;
@observable showingDelete = false;
@observable showingRegister = false;
@observable showingUpdate = false;
@observable showingWarning = true;
_dappsStore = DappsStore.instance();
static instance () {
if (!instance) {
instance = new ModalStore();
}
return instance;
}
@action setDeleteError (error) {
transaction(() => {
this.setDeleteStep(0);
this.errorDelete = error;
});
}
@action setDeleteStep (step) {
this.stepDelete = step;
}
@action showDelete () {
transaction(() => {
this.setDeleteStep(1);
this.errorDelete = null;
this.showingDelete = true;
});
}
@action hideDelete () {
this.showingDelete = false;
}
@action setRegisterError (error) {
transaction(() => {
this.setRegisterStep(0);
this.errorRegister = error;
});
}
@action setRegisterStep (step) {
this.stepRegister = step;
}
@action showRegister () {
transaction(() => {
this.setRegisterStep(1);
this.errorRegister = null;
this.showingRegister = true;
});
}
@action hideRegister () {
transaction(() => {
this._dappsStore.setEditing(false);
this._dappsStore.setNew(false);
this.showingRegister = false;
});
}
@action setUpdateError (error) {
transaction(() => {
this.setUpdateStep(0);
this.errorUpdate = error;
});
}
@action setUpdateStep (step) {
this.stepUpdate = step;
}
@action showUpdate () {
transaction(() => {
this.setUpdateStep(1);
this.errorUpdate = null;
this.showingUpdate = true;
});
}
@action hideUpdate () {
transaction(() => {
this._dappsStore.setEditing(false);
this._dappsStore.setNew(false);
this.showingUpdate = false;
});
}
@action hideWarning () {
this.showingWarning = false;
}
doDelete () {
this.setDeleteStep(2);
const appId = this._dappsStore.currentApp.id;
const values = [appId];
const options = {
from: this._dappsStore.currentApp.isOwner ? this._dappsStore.currentApp.owner : this._dappsStore.contractOwner
};
console.log('ModalStore:doDelete', `performing deletion for ${appId} from ${options.from}`);
this._dappsStore._instanceReg
.unregister.estimateGas(options, values)
.then((gas) => {
const newGas = gas.mul(1.2);
console.log('ModalStore:doDelete', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
options.gas = newGas.toFixed(0);
const request = this._dappsStore._instanceReg.unregister.postTransaction(options, values);
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setDeleteStep(3);
} else if (status.transactionReceipt) {
this.setDeleteStep(4);
this._dappsStore.removeApp(appId);
}
};
return trackRequest(request, statusCallback);
})
.catch((error) => {
console.error('ModalStore:doDelete', error);
this.setDeleteError(error);
});
}
doRegister () {
this.setRegisterStep(2);
const appId = this._dappsStore.wipApp.id;
const values = [appId];
const options = {
from: this._dappsStore.currentAccount.address,
value: this._dappsStore.fee
};
console.log('ModalStore:doRegister', `performing registration for ${appId} from ${this._dappsStore.currentAccount.address}`);
this._dappsStore._instanceReg
.register.estimateGas(options, values)
.then((gas) => {
const newGas = gas.mul(1.2);
console.log('ModalStore:doRegister', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
options.gas = newGas.toFixed(0);
const request = this._dappsStore._instanceReg.register.postTransaction(options, values);
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setRegisterStep(3);
} else if (status.transactionReceipt) {
this.setRegisterStep(4);
this._dappsStore.addApp(appId, this._dappsStore.currentAccount);
}
};
return trackRequest(request, statusCallback);
})
.catch((error) => {
console.error('ModalStore:doRegister', error);
this.setRegisterError(error);
});
}
doUpdate () {
this.setUpdateStep(2);
const appId = this._dappsStore.wipApp.id;
const options = {
from: this._dappsStore.wipApp.owner
};
const types = {
'content': 'CONTENT',
'image': 'IMG',
'manifest': 'MANIFEST'
};
const values = Object
.keys(types)
.filter((type) => this._dappsStore.wipApp[`${type}Changed`])
.map((type) => {
return [appId, types[type], this._dappsStore.wipApp[`${type}Hash`] || '0x0'];
});
console.log('ModalStore:doUpdate', `performing updates for ${appId} from ${options.from}`);
Promise
.all(values.map((value) => this._dappsStore._instanceReg.setMeta.estimateGas(options, value)))
.then((gas) => {
const newGas = gas.map((gas) => gas.mul(1.2));
gas.forEach((gas, index) => {
console.log('ModalStore:doUpdate', `${values[index][1]} gas estimated as ${gas.toFormat(0)}, setting to ${newGas[index].toFormat(0)}`);
});
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setUpdateStep(3);
} else if (status.transactionReceipt) {
this.setUpdateStep(4);
}
};
return Promise.all(
newGas.map((gas, index) => {
return trackRequest(
this._dappsStore._instanceReg.setMeta.postTransaction(
Object.assign(options, { gas: gas.toFixed(0) }), values[index]
), statusCallback
);
})
);
})
.then(() => {
this._dappsStore.refreshApp(appId);
})
.catch((error) => {
console.error('ModalStore:doUpdate', error);
this.setUpdateError(error);
});
}
}

View File

@ -0,0 +1,53 @@
// 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 <http://www.gnu.org/licenses/>.
const { api } = window.parity;
function trackRequest (requestPromise, statusCallback) {
return requestPromise
.then((signerRequestId) => {
console.log('trackRequest', `posted to signer with requestId ${signerRequestId.toString()}`);
statusCallback(null, { signerRequestId });
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((transactionHash) => {
console.log('trackRequest', `received transaction hash ${transactionHash}`);
statusCallback(null, { transactionHash });
return api.pollMethod('eth_getTransactionReceipt', transactionHash, (receipt) => {
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
return false;
}
return true;
});
})
.then((transactionReceipt) => {
console.log('trackRequest', 'received transaction receipt', transactionReceipt);
statusCallback(null, { transactionReceipt });
})
.catch((error) => {
console.error('trackRequest', error);
statusCallback(error);
throw error;
});
}
export {
api,
trackRequest
};

View File

@ -32,8 +32,13 @@ export default class Application extends Component {
} }
componentDidMount () { componentDidMount () {
const poll = () => this.fetchTransactionData().then(poll).catch(poll); const poll = () => {
this._timeout = setTimeout(poll, 2000); this._timeout = window.setTimeout(() => {
this.fetchTransactionData().then(poll).catch(poll);
}, 1000);
};
poll();
} }
componentWillUnmount () { componentWillUnmount () {

View File

@ -77,7 +77,7 @@ export default class Lookup extends Component {
label='Lookup' label='Lookup'
primary primary
icon={ <SearchIcon /> } icon={ <SearchIcon /> }
onClick={ this.onLookupClick } onTouchTap={ this.onLookupClick }
/> />
</div> </div>
<CardText>{ output }</CardText> <CardText>{ output }</CardText>

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { sha3, toWei } from '../parity.js'; import { sha3, api } from '../parity.js';
const alreadyQueued = (queue, action, name) => const alreadyQueued = (queue, action, name) =>
!!queue.find((entry) => entry.action === action && entry.name === name); !!queue.find((entry) => entry.action === action && entry.name === name);
@ -29,6 +29,8 @@ export const reserve = (name) => (dispatch, getState) => {
const state = getState(); const state = getState();
const account = state.accounts.selected; const account = state.accounts.selected;
const contract = state.contract; const contract = state.contract;
const fee = state.fee;
if (!contract || !account) return; if (!contract || !account) return;
if (alreadyQueued(state.names.queue, 'reserve', name)) return; if (alreadyQueued(state.names.queue, 'reserve', name)) return;
const reserve = contract.functions.find((f) => f.name === 'reserve'); const reserve = contract.functions.find((f) => f.name === 'reserve');
@ -36,19 +38,28 @@ export const reserve = (name) => (dispatch, getState) => {
name = name.toLowerCase(); name = name.toLowerCase();
const options = { const options = {
from: account.address, from: account.address,
value: toWei(1).toString() value: fee
}; };
const values = [ sha3(name) ]; const values = [ sha3(name) ];
dispatch(reserveStart(name)); dispatch(reserveStart(name));
reserve.estimateGas(options, values) reserve.estimateGas(options, values)
.then((gas) => { .then((gas) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
return reserve.postTransaction(options, values); return reserve.postTransaction(options, values);
}) })
.then((data) => { .then((requestId) => {
return api.pollMethod('parity_checkRequest', requestId);
})
.then((txhash) => {
dispatch(reserveSuccess(name)); dispatch(reserveSuccess(name));
}).catch((err) => { })
.catch((err) => {
if (err && err.type === 'REQUEST_REJECTED') {
return dispatch(reserveFail(name));
}
console.error(`could not reserve ${name}`); console.error(`could not reserve ${name}`);
if (err) console.error(err.stack); if (err) console.error(err.stack);
dispatch(reserveFail(name)); dispatch(reserveFail(name));
@ -79,9 +90,17 @@ export const drop = (name) => (dispatch, getState) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
return drop.postTransaction(options, values); return drop.postTransaction(options, values);
}) })
.then((data) => { .then((requestId) => {
return api.pollMethod('parity_checkRequest', requestId);
})
.then((txhash) => {
dispatch(dropSuccess(name)); dispatch(dropSuccess(name));
}).catch((err) => { })
.catch((err) => {
if (err && err.type === 'REQUEST_REJECTED') {
dispatch(reserveFail(name));
}
console.error(`could not drop ${name}`); console.error(`could not drop ${name}`);
if (err) console.error(err.stack); if (err) console.error(err.stack);
dispatch(reserveFail(name)); dispatch(reserveFail(name));

View File

@ -86,6 +86,22 @@ export default class Names extends Component {
name: '' name: ''
}; };
componentWillReceiveProps (nextProps) {
const nextQueue = nextProps.queue;
const prevQueue = this.props.queue;
if (nextQueue.length > prevQueue.length) {
const newQueued = nextQueue[nextQueue.length - 1];
const newName = newQueued.name;
if (newName !== this.state.name) {
return;
}
this.setState({ name: '' });
}
}
render () { render () {
const { action, name } = this.state; const { action, name } = this.state;
const { fee, pending, queue } = this.props; const { fee, pending, queue } = this.props;
@ -120,7 +136,7 @@ export default class Names extends Component {
label={ action === 'reserve' ? 'Reserve' : 'Drop' } label={ action === 'reserve' ? 'Reserve' : 'Drop' }
primary primary
icon={ <CheckIcon /> } icon={ <CheckIcon /> }
onClick={ this.onSubmitClick } onTouchTap={ this.onSubmitClick }
/> />
{ queue.length > 0 { queue.length > 0
? (<div>{ useSignerText }{ renderQueue(queue) }</div>) ? (<div>{ useSignerText }{ renderQueue(queue) }</div>)

View File

@ -52,7 +52,7 @@ export default class Records extends Component {
label='Save' label='Save'
primary primary
icon={ <SaveIcon /> } icon={ <SaveIcon /> }
onClick={ this.onSaveClick } onTouchTap={ this.onSaveClick }
/> />
</CardText> </CardText>
</Card> </Card>

View File

@ -46,6 +46,12 @@ import './index.html';
injectTapEventPlugin(); injectTapEventPlugin();
if (process.env.NODE_ENV === 'development') {
// Expose the React Performance Tools on the`window` object
const Perf = require('react-addons-perf');
window.Perf = Perf;
}
const AUTH_HASH = '#/auth?'; const AUTH_HASH = '#/auth?';
const parityUrl = process.env.PARITY_URL || const parityUrl = process.env.PARITY_URL ||
( (

View File

@ -238,6 +238,24 @@ export default {
} }
}, },
killAccount: {
desc: 'Deletes an account',
params: [
{
type: Address,
desc: 'The account to remove'
},
{
type: String,
desc: 'Account password'
}
],
returns: {
type: Boolean,
desc: 'true on success'
}
},
listGethAccounts: { listGethAccounts: {
desc: 'Returns a list of the accounts available from Geth', desc: 'Returns a list of the accounts available from Geth',
params: [], params: [],

View File

@ -54,8 +54,6 @@ export default class RecoveryPhrase extends Component {
<Input <Input
hint='the account recovery phrase' hint='the account recovery phrase'
label='account recovery phrase' label='account recovery phrase'
multiLine
rows={ 1 }
value={ recoveryPhrase } value={ recoveryPhrase }
onChange={ this.onEditPhrase } /> onChange={ this.onEditPhrase } />
<Input <Input
@ -112,17 +110,23 @@ export default class RecoveryPhrase extends Component {
} }
onEditPhrase = (event) => { onEditPhrase = (event) => {
const value = event.target.value; const recoveryPhrase = event.target.value
let error = null; .toLowerCase() // wordlists are lowercase
.trim() // remove whitespace at both ends
.replace(/\s/g, ' ') // replace any whitespace with single space
.replace(/ +/g, ' '); // replace multiple spaces with a single space
if (!value || value.trim().length < 25) { const parts = recoveryPhrase.split(' ');
error = ERRORS.noPhrase; let recoveryPhraseError = null;
if (!recoveryPhrase || recoveryPhrase.length < 25 || parts.length < 8) {
recoveryPhraseError = ERRORS.noPhrase;
} }
this.setState({ this.setState({
recoveryPhrase: value, recoveryPhrase,
recoveryPhraseError: error, recoveryPhraseError,
isValidPhrase: !error isValidPhrase: !recoveryPhraseError
}, this.updateParent); }, this.updateParent);
} }

View File

@ -0,0 +1,54 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.body {
.hero {
padding-bottom: 1em;
}
.info {
display: inline-block;
}
.icon {
display: inline-block;
}
.nameinfo {
display: inline-block;
text-align: left;
}
.header {
text-transform: uppercase;
font-size: 1.25em;
padding-bottom: 0.25em;
}
.address {
}
.description {
padding-top: 1em;
font-size: 0.75em;
color: #aaa;
}
.password {
padding: 1em 5em;
}
}

View File

@ -0,0 +1,125 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ConfirmDialog, IdentityIcon, IdentityName, Input } from '../../ui';
import { newError } from '../../redux/actions';
import styles from './deleteAccount.css';
class DeleteAccount extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
router: PropTypes.object
}
static propTypes = {
account: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
newError: PropTypes.func.isRequired
}
state = {
password: ''
}
render () {
const { account } = this.props;
const { password } = this.state;
return (
<ConfirmDialog
className={ styles.body }
title='confirm removal'
visible
onDeny={ this.closeDeleteDialog }
onConfirm={ this.onDeleteConfirmed }>
<div className={ styles.hero }>
Are you sure you want to permanently delete the following account?
</div>
<div className={ styles.info }>
<IdentityIcon
className={ styles.icon }
address={ account.address } />
<div className={ styles.nameinfo }>
<div className={ styles.header }>
<IdentityName address={ account.address } unknown />
</div>
<div className={ styles.address }>
{ account.address }
</div>
</div>
</div>
<div className={ styles.description }>
{ account.meta.description }
</div>
<div className={ styles.password }>
<Input
label='account password'
hint='provide the account password to confirm the account deletion'
type='password'
value={ password }
onChange={ this.onChangePassword } />
</div>
</ConfirmDialog>
);
}
onChangePassword = (event, password) => {
this.setState({ password });
}
onDeleteConfirmed = () => {
const { api, router } = this.context;
const { account, newError } = this.props;
const { password } = this.state;
api.parity
.killAccount(account.address, password)
.then((result) => {
if (result === true) {
router.push('/accounts');
this.closeDeleteDialog();
} else {
newError(new Error('Deletion failed.'));
}
})
.catch((error) => {
console.error('onDeleteConfirmed', error);
newError(new Error(`Deletion failed: ${error.message}`));
});
}
closeDeleteDialog = () => {
this.props.onClose();
}
}
function mapStateToProps (state) {
return {};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({ newError }, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(DeleteAccount);

View File

@ -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 <http://www.gnu.org/licenses/>.
export default from './deleteAccount';

View File

@ -36,6 +36,7 @@ export default class DetailsStep extends Component {
onFuncChange: PropTypes.func, onFuncChange: PropTypes.func,
values: PropTypes.array.isRequired, values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired,
warning: PropTypes.string,
onValueChange: PropTypes.func.isRequired onValueChange: PropTypes.func.isRequired
} }
@ -44,6 +45,7 @@ export default class DetailsStep extends Component {
return ( return (
<Form> <Form>
{ this.renderWarning() }
<AddressSelect <AddressSelect
label='from account' label='from account'
hint='the account to transact with' hint='the account to transact with'
@ -178,6 +180,20 @@ export default class DetailsStep extends Component {
}); });
} }
renderWarning () {
const { warning } = this.props;
if (!warning) {
return null;
}
return (
<div className={ styles.warning }>
{ warning }
</div>
);
}
onFuncChange = (event, index, signature) => { onFuncChange = (event, index, signature) => {
const { contract, onFuncChange } = this.props; const { contract, onFuncChange } = this.props;

View File

@ -33,3 +33,12 @@
.txhash { .txhash {
word-break: break-all; word-break: break-all;
} }
.warning {
border-radius: 0.5em;
background: #f80;
color: white;
font-size: 0.75em;
padding: 0.75em;
text-align: center;
}

View File

@ -15,17 +15,21 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { MAX_GAS_ESTIMATION } from '../../util/constants';
import { validateAddress, validateUint } from '../../util/validation'; import { validateAddress, validateUint } from '../../util/validation';
import DetailsStep from './DetailsStep'; import DetailsStep from './DetailsStep';
import ERRORS from '../Transfer/errors';
import { ERROR_CODES } from '../../api/transport/error'; import { ERROR_CODES } from '../../api/transport/error';
export default class ExecuteContract extends Component { class ExecuteContract extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired, api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired store: PropTypes.object.isRequired
@ -36,6 +40,7 @@ export default class ExecuteContract extends Component {
fromAddress: PropTypes.string, fromAddress: PropTypes.string,
accounts: PropTypes.object, accounts: PropTypes.object,
contract: PropTypes.object, contract: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired onFromAddressChange: PropTypes.func.isRequired
} }
@ -46,6 +51,8 @@ export default class ExecuteContract extends Component {
fromAddressError: null, fromAddressError: null,
func: null, func: null,
funcError: null, funcError: null,
gas: null,
gasLimitError: null,
values: [], values: [],
valuesError: [], valuesError: [],
step: 0, step: 0,
@ -64,6 +71,12 @@ export default class ExecuteContract extends Component {
this.onFuncChange(null, functions[0]); this.onFuncChange(null, functions[0]);
} }
componentWillReceiveProps (newProps) {
if (newProps.fromAddress !== this.props.fromAddress) {
this.estimateGas(newProps.fromAddress);
}
}
render () { render () {
const { sending } = this.state; const { sending } = this.state;
@ -119,7 +132,7 @@ export default class ExecuteContract extends Component {
renderStep () { renderStep () {
const { onFromAddressChange } = this.props; const { onFromAddressChange } = this.props;
const { step, busyState, txhash, rejected } = this.state; const { step, busyState, gasLimitError, txhash, rejected } = this.state;
if (rejected) { if (rejected) {
return ( return (
@ -135,6 +148,7 @@ export default class ExecuteContract extends Component {
<DetailsStep <DetailsStep
{ ...this.props } { ...this.props }
{ ...this.state } { ...this.state }
warning={ gasLimitError }
onAmountChange={ this.onAmountChange } onAmountChange={ this.onAmountChange }
onFromAddressChange={ onFromAddressChange } onFromAddressChange={ onFromAddressChange }
onFuncChange={ this.onFuncChange } onFuncChange={ this.onFuncChange }
@ -156,7 +170,7 @@ export default class ExecuteContract extends Component {
} }
onAmountChange = (amount) => { onAmountChange = (amount) => {
this.setState({ amount }); this.setState({ amount }, this.estimateGas);
} }
onFuncChange = (event, func) => { onFuncChange = (event, func) => {
@ -182,7 +196,7 @@ export default class ExecuteContract extends Component {
this.setState({ this.setState({
func, func,
values values
}); }, this.estimateGas);
} }
onValueChange = (event, index, _value) => { onValueChange = (event, index, _value) => {
@ -211,14 +225,55 @@ export default class ExecuteContract extends Component {
this.setState({ this.setState({
values: [].concat(values), values: [].concat(values),
valuesError: [].concat(valuesError) valuesError: [].concat(valuesError)
}, () => {
if (!valueError) {
this.estimateGas();
}
}); });
} }
estimateGas = (_fromAddress) => {
const { api } = this.context;
const { fromAddress, gasLimit } = this.props;
const { amount, func, values } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: _fromAddress || fromAddress,
value: api.util.toWei(amount || 0)
};
if (!func) {
return;
}
func
.estimateGas(options, values)
.then((gasEst) => {
const gas = gasEst.mul(1.2);
let gasLimitError = null;
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
this.setState({
gas,
gasLimitError
});
})
.catch((error) => {
console.warn('estimateGas', error);
});
}
postTransaction = () => { postTransaction = () => {
const { api, store } = this.context; const { api, store } = this.context;
const { fromAddress } = this.props; const { fromAddress } = this.props;
const { amount, func, values } = this.state; const { amount, func, values } = this.state;
const options = { const options = {
gas: MAX_GAS_ESTIMATION,
from: fromAddress, from: fromAddress,
value: api.util.toWei(amount || 0) value: api.util.toWei(amount || 0)
}; };
@ -237,13 +292,13 @@ export default class ExecuteContract extends Component {
return api return api
.pollMethod('parity_checkRequest', requestId) .pollMethod('parity_checkRequest', requestId)
.catch((e) => { .catch((error) => {
if (e.code === ERROR_CODES.REQUEST_REJECTED) { if (error.code === ERROR_CODES.REQUEST_REJECTED) {
this.setState({ rejected: true }); this.setState({ rejected: true });
return false; return false;
} }
throw e; throw error;
}); });
}) })
.then((txhash) => { .then((txhash) => {
@ -255,3 +310,18 @@ export default class ExecuteContract extends Component {
}); });
} }
} }
function mapStateToProps (state) {
const { gasLimit } = state.nodeStatus;
return { gasLimit };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExecuteContract);

View File

@ -174,7 +174,7 @@ export default class LoadContract extends Component {
const secondaryText = description || `Saved ${moment(timestamp).fromNow()}`; const secondaryText = description || `Saved ${moment(timestamp).fromNow()}`;
const remove = removable const remove = removable
? ( ? (
<IconButton onClick={ onDelete }> <IconButton onTouchTap={ onDelete }>
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
) )

View File

@ -25,7 +25,7 @@ import ErrorIcon from 'material-ui/svg-icons/navigation/close';
import { fromWei } from '../../../api/util/wei'; import { fromWei } from '../../../api/util/wei';
import { Form, Input } from '../../../ui'; import { Form, Input } from '../../../ui';
import terms from '../terms-of-service'; import { termsOfService } from '../../../3rdparty/sms-verification';
import styles from './gatherData.css'; import styles from './gatherData.css';
export default class GatherData extends Component { export default class GatherData extends Component {
@ -66,7 +66,7 @@ export default class GatherData extends Component {
disabled={ isVerified } disabled={ isVerified }
onCheck={ this.consentOnChange } onCheck={ this.consentOnChange }
/> />
<div className={ styles.terms }>{ terms }</div> <div className={ styles.terms }>{ termsOfService }</div>
</Form> </Form>
); );
} }
@ -123,8 +123,7 @@ export default class GatherData extends Component {
<p className={ styles.message }>You already requested verification.</p> <p className={ styles.message }>You already requested verification.</p>
</div> </div>
); );
} } else if (hasRequested === false) {
if (hasRequested === false) {
return ( return (
<div className={ styles.container }> <div className={ styles.container }>
<SuccessIcon /> <SuccessIcon />

View File

@ -16,8 +16,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import DoneIcon from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import CancelIcon from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '../../ui'; import { Button, IdentityIcon, Modal } from '../../ui';
@ -77,7 +77,7 @@ export default class SMSVerification extends Component {
const cancel = ( const cancel = (
<Button <Button
key='cancel' label='Cancel' key='cancel' label='Cancel'
icon={ <ContentClear /> } icon={ <CancelIcon /> }
onClick={ onClose } onClick={ onClose }
/> />
); );
@ -92,7 +92,7 @@ export default class SMSVerification extends Component {
<Button <Button
key='done' label='Done' key='done' label='Done'
disabled={ !isStepValid } disabled={ !isStepValid }
icon={ <ActionDoneAll /> } icon={ <DoneIcon /> }
onClick={ onClose } onClick={ onClose }
/> />
</div> </div>
@ -140,37 +140,47 @@ export default class SMSVerification extends Component {
setNumber, setConsentGiven, setCode setNumber, setConsentGiven, setCode
} = this.props.store; } = this.props.store;
if (phase === 5) { switch (phase) {
return (<Done />); case 0:
} return (
if (phase === 4) { <p>Loading SMS Verification.</p>
return (<SendConfirmation step={ step } tx={ confirmationTx } />); );
}
if (phase === 3) {
return (
<QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid }
setCode={ setCode }
/>
);
}
if (phase === 2) {
return (<SendRequest step={ step } tx={ requestTx } />);
}
if (phase === 1) {
const { setNumber, setConsentGiven } = this.props.store;
return (
<GatherData
fee={ fee } isNumberValid={ isNumberValid }
isVerified={ isVerified } hasRequested={ hasRequested }
setNumber={ setNumber } setConsentGiven={ setConsentGiven }
/>
);
}
if (phase === 0) {
return (<p>Preparing awesomeness!</p>);
}
return null; case 1:
const { setNumber, setConsentGiven } = this.props.store;
return (
<GatherData
fee={ fee } isNumberValid={ isNumberValid }
isVerified={ isVerified } hasRequested={ hasRequested }
setNumber={ setNumber } setConsentGiven={ setConsentGiven }
/>
);
case 2:
return (
<SendRequest step={ step } tx={ requestTx } />
);
case 3:
return (
<QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid }
setCode={ setCode }
/>
);
case 4:
return (
<SendConfirmation step={ step } tx={ confirmationTx } />
);
case 5:
return (
<Done />
);
default:
return null;
}
} }
} }

View File

@ -20,7 +20,8 @@ import { sha3 } from '../../api/util/sha3';
import Contracts from '../../contracts'; import Contracts from '../../contracts';
import { checkIfVerified, checkIfRequested, postToServer } from '../../contracts/sms-verification'; import { checkIfVerified, checkIfRequested } from '../../contracts/sms-verification';
import { postToServer } from '../../3rdparty/sms-verification';
import checkIfTxFailed from '../../util/check-if-tx-failed'; import checkIfTxFailed from '../../util/check-if-tx-failed';
import waitForConfirmations from '../../util/wait-for-block-confirmations'; import waitForConfirmations from '../../util/wait-for-block-confirmations';
@ -87,7 +88,7 @@ export default class VerificationStore {
this.account = account; this.account = account;
this.step = LOADING; this.step = LOADING;
Contracts.create(api).registry.getContract('smsVerification') Contracts.create(api).registry.getContract('smsverification')
.then((contract) => { .then((contract) => {
this.contract = contract; this.contract = contract;
this.load(); this.load();

View File

@ -63,6 +63,16 @@ export default class Shapeshift extends Component {
this.retrieveCoins(); this.retrieveCoins();
} }
componentWillUnmount () {
this.unsubscribe();
}
unsubscribe () {
// Unsubscribe from Shapeshit
const { depositAddress } = this.state;
shapeshift.unsubscribe(depositAddress);
}
render () { render () {
const { error, stage } = this.state; const { error, stage } = this.state;
@ -205,6 +215,10 @@ export default class Shapeshift extends Component {
console.log('onShift', result); console.log('onShift', result);
const depositAddress = result.deposit; const depositAddress = result.deposit;
if (this.state.depositAddress) {
this.unsubscribe();
}
shapeshift.subscribe(depositAddress, this.onExchangeInfo); shapeshift.subscribe(depositAddress, this.onExchangeInfo);
this.setState({ depositAddress }); this.setState({ depositAddress });
}) })

View File

@ -19,7 +19,9 @@ const ERRORS = {
invalidAddress: 'the supplied address is an invalid network address', invalidAddress: 'the supplied address is an invalid network address',
invalidAmount: 'the supplied amount should be a valid positive number', invalidAmount: 'the supplied amount should be a valid positive number',
invalidDecimals: 'the supplied amount exceeds the allowed decimals', invalidDecimals: 'the supplied amount exceeds the allowed decimals',
largeAmount: 'the transaction total is higher than the available balance' largeAmount: 'the transaction total is higher than the available balance',
gasException: 'the transaction will throw an exception with the current values',
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
}; };
export default ERRORS; export default ERRORS;

View File

@ -14,6 +14,7 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.info { .info {
line-height: 1.618em; line-height: 1.618em;
width: 100%; width: 100%;
@ -151,3 +152,13 @@
.gasPriceDesc { .gasPriceDesc {
font-size: 0.9em; font-size: 0.9em;
} }
.warning {
border-radius: 0.5em;
background: #f80;
color: white;
font-size: 0.75em;
margin-bottom: 1em;
padding: 0.75em;
text-align: center;
}

View File

@ -16,12 +16,15 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants';
import Details from './Details'; import Details from './Details';
import Extras from './Extras'; import Extras from './Extras';
@ -30,8 +33,6 @@ import styles from './transfer.css';
import { ERROR_CODES } from '../../api/transport/error'; import { ERROR_CODES } from '../../api/transport/error';
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const TITLES = { const TITLES = {
transfer: 'transfer details', transfer: 'transfer details',
sending: 'sending', sending: 'sending',
@ -42,7 +43,7 @@ const TITLES = {
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete]; const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
export default class Transfer extends Component { class Transfer extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired, api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired store: PropTypes.object.isRequired
@ -52,6 +53,7 @@ export default class Transfer extends Component {
account: PropTypes.object, account: PropTypes.object,
balance: PropTypes.object, balance: PropTypes.object,
balances: PropTypes.object, balances: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
images: PropTypes.object.isRequired, images: PropTypes.object.isRequired,
onClose: PropTypes.func onClose: PropTypes.func
} }
@ -64,6 +66,7 @@ export default class Transfer extends Component {
gas: DEFAULT_GAS, gas: DEFAULT_GAS,
gasEst: '0', gasEst: '0',
gasError: null, gasError: null,
gasLimitError: null,
gasPrice: DEFAULT_GASPRICE, gasPrice: DEFAULT_GASPRICE,
gasPriceHistogram: {}, gasPriceHistogram: {},
gasPriceError: null, gasPriceError: null,
@ -103,6 +106,7 @@ export default class Transfer extends Component {
visible visible
scroll scroll
> >
{ this.renderWarning() }
{ this.renderPage() } { this.renderPage() }
</Modal> </Modal>
); );
@ -264,6 +268,20 @@ export default class Transfer extends Component {
} }
} }
renderWarning () {
const { gasLimitError } = this.state;
if (!gasLimitError) {
return null;
}
return (
<div className={ styles.warning }>
{ gasLimitError }
</div>
);
}
isValid () { isValid () {
const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError; const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError;
const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError; const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError;
@ -519,6 +537,7 @@ export default class Transfer extends Component {
return token.contract.instance.transfer return token.contract.instance.transfer
.estimateGas({ .estimateGas({
gas: MAX_GAS_ESTIMATION,
from: account.address, from: account.address,
to: token.address to: token.address
}, [ }, [
@ -532,6 +551,7 @@ export default class Transfer extends Component {
const { account } = this.props; const { account } = this.props;
const { data, recipient, value } = this.state; const { data, recipient, value } = this.state;
const options = { const options = {
gas: MAX_GAS_ESTIMATION,
from: account.address, from: account.address,
to: recipient, to: recipient,
value: api.util.toWei(value || 0) value: api.util.toWei(value || 0)
@ -552,19 +572,29 @@ export default class Transfer extends Component {
return; return;
} }
const { gasLimit } = this.props;
(this.state.isEth (this.state.isEth
? this._estimateGasEth() ? this._estimateGasEth()
: this._estimateGasToken() : this._estimateGasToken()
).then((_value) => { ).then((gasEst) => {
let gas = _value; let gas = gasEst;
let gasLimitError = null;
if (gas.gt(DEFAULT_GAS)) { if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2); gas = gas.mul(1.2);
} }
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
this.setState({ this.setState({
gas: gas.toFixed(0), gas: gas.toFixed(0),
gasEst: _value.toFormat() gasEst: gasEst.toFormat(),
gasLimitError
}, this.recalculate); }, this.recalculate);
}) })
.catch((error) => { .catch((error) => {
@ -649,3 +679,18 @@ export default class Transfer extends Component {
store.dispatch({ type: 'newError', error }); store.dispatch({ type: 'newError', error });
} }
} }
function mapStateToProps (state) {
const { gasLimit } = state.nodeStatus;
return { gasLimit };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Transfer);

View File

@ -17,6 +17,7 @@
import AddAddress from './AddAddress'; import AddAddress from './AddAddress';
import AddContract from './AddContract'; import AddContract from './AddContract';
import CreateAccount from './CreateAccount'; import CreateAccount from './CreateAccount';
import DeleteAccount from './DeleteAccount';
import DeployContract from './DeployContract'; import DeployContract from './DeployContract';
import EditMeta from './EditMeta'; import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract'; import ExecuteContract from './ExecuteContract';
@ -32,6 +33,7 @@ export {
AddAddress, AddAddress,
AddContract, AddContract,
CreateAccount, CreateAccount,
DeleteAccount,
DeployContract, DeployContract,
EditMeta, EditMeta,
ExecuteContract, ExecuteContract,

View File

@ -14,9 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { throttle } from 'lodash';
import { getBalances, getTokens } from './balancesActions'; import { getBalances, getTokens } from './balancesActions';
import { setAddressImage } from './imagesActions'; import { setAddressImage } from './imagesActions';
import Contracts from '../../contracts';
import * as abis from '../../contracts/abi'; import * as abis from '../../contracts/abi';
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
@ -31,13 +34,32 @@ export default class Balances {
constructor (store, api) { constructor (store, api) {
this._api = api; this._api = api;
this._store = store; this._store = store;
this._tokens = {};
this._images = {};
this._accountsInfo = null; this._accountsInfo = null;
this._tokens = []; this._tokenreg = null;
this._fetchingBalances = false;
this._fetchingTokens = false;
this._fetchedTokens = false;
this._tokenregSubId = null;
this._tokenregMetaSubId = null;
// Throttled `retrieveTokens` function
// that gets called max once every 20s
this._throttledRetrieveTokens = throttle(
this._retrieveTokens,
20 * 1000,
{ trailing: true }
);
} }
start () { start () {
this._subscribeBlockNumber(); this._subscribeBlockNumber();
this._subscribeAccountsInfo(); this._subscribeAccountsInfo();
this._retrieveTokens();
} }
_subscribeAccountsInfo () { _subscribeAccountsInfo () {
@ -48,10 +70,7 @@ export default class Balances {
} }
this._accountsInfo = accountsInfo; this._accountsInfo = accountsInfo;
this._retrieveBalances(); this._retrieveTokens();
})
.then((subscriptionId) => {
console.log('_subscribeAccountsInfo', 'subscriptionId', subscriptionId);
}) })
.catch((error) => { .catch((error) => {
console.warn('_subscribeAccountsInfo', error); console.warn('_subscribeAccountsInfo', error);
@ -62,139 +81,269 @@ export default class Balances {
this._api this._api
.subscribe('eth_blockNumber', (error) => { .subscribe('eth_blockNumber', (error) => {
if (error) { if (error) {
return; return console.warn('_subscribeBlockNumber', error);
} }
const { syncing } = this._store.getState().nodeStatus;
// If syncing, only retrieve balances once every
// few seconds
if (syncing) {
return this._throttledRetrieveTokens();
}
this._throttledRetrieveTokens.cancel();
this._retrieveTokens(); this._retrieveTokens();
}) })
.then((subscriptionId) => {
console.log('_subscribeBlockNumber', 'subscriptionId', subscriptionId);
})
.catch((error) => { .catch((error) => {
console.warn('_subscribeBlockNumber', error); console.warn('_subscribeBlockNumber', error);
}); });
} }
getTokenRegistry () {
if (this._tokenreg) {
return Promise.resolve(this._tokenreg);
}
return Contracts.get().tokenReg
.getContract()
.then((tokenreg) => {
this._tokenreg = tokenreg;
this.attachToTokens();
return tokenreg;
});
}
_retrieveTokens () { _retrieveTokens () {
this._api.parity if (this._fetchingTokens) {
.registryAddress() return;
.then((registryAddress) => { }
const registry = this._api.newContract(abis.registry, registryAddress);
return registry.instance.getAddress.call({}, [this._api.util.sha3('tokenreg'), 'A']); if (this._fetchedTokens) {
}) return this._retrieveBalances();
.then((tokenregAddress) => { }
const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress);
this._fetchingTokens = true;
this._fetchedTokens = false;
this
.getTokenRegistry()
.then((tokenreg) => {
return tokenreg.instance.tokenCount return tokenreg.instance.tokenCount
.call() .call()
.then((numTokens) => { .then((numTokens) => {
const promisesTokens = []; const promises = [];
const promisesImages = [];
while (promisesTokens.length < numTokens.toNumber()) { for (let i = 0; i < numTokens.toNumber(); i++) {
const index = promisesTokens.length; promises.push(this.fetchTokenInfo(tokenreg, i));
promisesTokens.push(tokenreg.instance.token.call({}, [index]));
promisesImages.push(tokenreg.instance.meta.call({}, [index, 'IMG']));
} }
return Promise.all([ return Promise.all(promises);
Promise.all(promisesTokens),
Promise.all(promisesImages)
]);
}); });
}) })
.then(([_tokens, images]) => { .then(() => {
const tokens = {}; this._fetchingTokens = false;
this._tokens = _tokens this._fetchedTokens = true;
.map((_token, index) => {
const [address, tag, format, name] = _token;
const token = { this._store.dispatch(getTokens(this._tokens));
address,
name,
tag,
format: format.toString(),
contract: this._api.newContract(abis.eip20, address)
};
tokens[address] = token;
this._store.dispatch(setAddressImage(address, images[index]));
return token;
})
.sort((a, b) => {
if (a.tag < b.tag) {
return -1;
} else if (a.tag > b.tag) {
return 1;
}
return 0;
});
this._store.dispatch(getTokens(tokens));
this._retrieveBalances(); this._retrieveBalances();
}) })
.catch((error) => { .catch((error) => {
console.warn('_retrieveTokens', error); console.warn('balances::_retrieveTokens', error);
this._retrieveBalances();
}); });
} }
_retrieveBalances () { _retrieveBalances () {
if (this._fetchingBalances) {
return;
}
if (!this._accountsInfo) { if (!this._accountsInfo) {
return; return;
} }
const addresses = Object.keys(this._accountsInfo); this._fetchingBalances = true;
const addresses = Object
.keys(this._accountsInfo)
.filter((address) => {
const account = this._accountsInfo[address];
return !account.meta || !account.meta.deleted;
});
this._balances = {}; this._balances = {};
Promise Promise
.all( .all(addresses.map((a) => this.fetchAccountBalance(a)))
addresses.map((address) => Promise.all([ .then((balances) => {
this._api.eth.getBalance(address), addresses.forEach((a, idx) => {
this._api.eth.getTransactionCount(address) this._balances[a] = balances[idx];
]))
)
.then((balanceTxCount) => {
return Promise.all(
balanceTxCount.map(([value, txCount], idx) => {
const address = addresses[idx];
this._balances[address] = {
txCount,
tokens: [{
token: ETH,
value
}]
};
return Promise.all(
this._tokens.map((token) => {
return token.contract.instance.balanceOf.call({}, [address]);
})
);
})
);
})
.then((tokenBalances) => {
addresses.forEach((address, idx) => {
const balanceOf = tokenBalances[idx];
const balance = this._balances[address];
this._tokens.forEach((token, tidx) => {
balance.tokens.push({
token,
value: balanceOf[tidx]
});
});
}); });
this._store.dispatch(getBalances(this._balances)); this._store.dispatch(getBalances(this._balances));
this._fetchingBalances = false;
}) })
.catch((error) => { .catch((error) => {
console.warn('_retrieveBalances', error); console.warn('_retrieveBalances', error);
this._fetchingBalances = false;
});
}
attachToTokens () {
this.attachToTokenMetaChange();
this.attachToNewToken();
}
attachToNewToken () {
if (this._tokenregSubId) {
return;
}
this._tokenreg
.instance
.Registered
.subscribe({
fromBlock: 0,
toBlock: 'latest',
skipInitFetch: true
}, (error, logs) => {
if (error) {
return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack);
}
const promises = logs.map((log) => {
const id = log.params.id.value.toNumber();
return this.fetchTokenInfo(this._tokenreg, id);
});
return Promise.all(promises);
})
.then((tokenregSubId) => {
this._tokenregSubId = tokenregSubId;
})
.catch((e) => {
console.warn('balances::attachToNewToken', e);
});
}
attachToTokenMetaChange () {
if (this._tokenregMetaSubId) {
return;
}
this._tokenreg
.instance
.MetaChanged
.subscribe({
fromBlock: 0,
toBlock: 'latest',
topics: [ null, this._api.util.asciiToHex('IMG') ],
skipInitFetch: true
}, (error, logs) => {
if (error) {
return console.error('balances::attachToTokenMetaChange', 'failed to attach to tokenreg MetaChanged', error.toString(), error.stack);
}
// In case multiple logs for same token
// in one block. Take the last value.
const tokens = logs
.filter((log) => log.type === 'mined')
.reduce((_tokens, log) => {
const id = log.params.id.value.toNumber();
const image = log.params.value.value;
const token = Object.values(this._tokens).find((c) => c.id === id);
const { address } = token;
_tokens[address] = { address, id, image };
return _tokens;
}, {});
Object
.values(tokens)
.forEach((token) => {
const { address, image } = token;
if (this._images[address] !== image.toString()) {
this._store.dispatch(setAddressImage(address, image));
this._images[address] = image.toString();
}
});
})
.then((tokenregMetaSubId) => {
this._tokenregMetaSubId = tokenregMetaSubId;
})
.catch((e) => {
console.warn('balances::attachToTokenMetaChange', e);
});
}
fetchTokenInfo (tokenreg, tokenId) {
return Promise
.all([
tokenreg.instance.token.call({}, [tokenId]),
tokenreg.instance.meta.call({}, [tokenId, 'IMG'])
])
.then(([ tokenData, image ]) => {
const [ address, tag, format, name ] = tokenData;
const contract = this._api.newContract(abis.eip20, address);
if (this._images[address] !== image.toString()) {
this._store.dispatch(setAddressImage(address, image));
this._images[address] = image.toString();
}
const token = {
format: format.toString(),
id: tokenId,
address,
tag,
name,
contract
};
this._tokens[address] = token;
return token;
})
.catch((e) => {
console.warn('balances::fetchTokenInfo', `couldn't fetch token #${tokenId}`, e);
});
}
/**
* TODO?: txCount is only shown on an address page, so we
* might not need to fetch it for each address for each block,
* but only for one address when the user is on the account
* view.
*/
fetchAccountBalance (address) {
const _tokens = Object.values(this._tokens);
const tokensPromises = _tokens
.map((token) => {
return token.contract.instance.balanceOf.call({}, [ address ]);
});
return Promise
.all([
this._api.eth.getTransactionCount(address),
this._api.eth.getBalance(address)
].concat(tokensPromises))
.then(([ txCount, ethBalance, ...tokensBalance ]) => {
const tokens = []
.concat(
{ token: ETH, value: ethBalance },
_tokens
.map((token, index) => ({
token,
value: tokensBalance[index]
}))
);
const balance = { txCount, tokens };
return balance;
}); });
} }
} }

View File

@ -71,8 +71,8 @@ export default class Status {
* @see src/views/Connection/connection.js * @see src/views/Connection/connection.js
*/ */
_shouldPing = () => { _shouldPing = () => {
const { isConnected, isConnecting } = this._apiStatus; const { isConnected } = this._apiStatus;
return isConnecting || !isConnected; return !isConnected;
} }
_stopPollPing = () => { _stopPollPing = () => {
@ -119,7 +119,7 @@ export default class Status {
_pollStatus = () => { _pollStatus = () => {
const nextTimeout = (timeout = 1000) => { const nextTimeout = (timeout = 1000) => {
setTimeout(this._pollStatus, timeout); setTimeout(() => this._pollStatus(), timeout);
}; };
const { isConnected, isConnecting, needsToken, secureToken } = this._api; const { isConnected, isConnecting, needsToken, secureToken } = this._api;
@ -134,7 +134,8 @@ export default class Status {
const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected; const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected;
if (gotReconnected) { if (gotReconnected) {
this._pollLongStatus(); this._pollLongStatus(true);
this._store.dispatch(statusCollection({ isPingable: true }));
} }
if (!isEqual(apiStatus, this._apiStatus)) { if (!isEqual(apiStatus, this._apiStatus)) {
@ -155,37 +156,32 @@ export default class Status {
const { refreshStatus } = this._store.getState().nodeStatus; const { refreshStatus } = this._store.getState().nodeStatus;
const statusPromises = [ this._api.eth.syncing() ]; const statusPromises = [ this._api.eth.syncing(), this._api.parity.netPeers() ];
if (refreshStatus) { if (refreshStatus) {
statusPromises.push(this._api.eth.hashrate()); statusPromises.push(this._api.eth.hashrate());
statusPromises.push(this._api.parity.netPeers());
} }
Promise Promise
.all(statusPromises) .all(statusPromises)
.then((statusResults) => { .then(([ syncing, netPeers, ...statusResults ]) => {
const status = statusResults.length === 1 const status = statusResults.length === 0
? { ? { syncing, netPeers }
syncing: statusResults[0]
}
: { : {
syncing: statusResults[0], syncing, netPeers,
hashrate: statusResults[1], hashrate: statusResults[0]
netPeers: statusResults[2]
}; };
if (!isEqual(status, this._status)) { if (!isEqual(status, this._status)) {
this._store.dispatch(statusCollection(status)); this._store.dispatch(statusCollection(status));
this._status = status; this._status = status;
} }
nextTimeout();
}) })
.catch((error) => { .catch((error) => {
console.error('_pollStatus', error); console.error('_pollStatus', error);
nextTimeout(250);
}); });
nextTimeout();
} }
/** /**
@ -227,7 +223,11 @@ export default class Status {
* fetched every 30s just in case, and whenever * fetched every 30s just in case, and whenever
* the client got reconnected. * the client got reconnected.
*/ */
_pollLongStatus = () => { _pollLongStatus = (newConnection = false) => {
if (!this._api.isConnected) {
return;
}
const nextTimeout = (timeout = 30000) => { const nextTimeout = (timeout = 30000) => {
if (this._longStatusTimeoutId) { if (this._longStatusTimeoutId) {
clearTimeout(this._longStatusTimeoutId); clearTimeout(this._longStatusTimeoutId);
@ -246,12 +246,12 @@ export default class Status {
this._api.parity.netChain(), this._api.parity.netChain(),
this._api.parity.netPort(), this._api.parity.netPort(),
this._api.parity.rpcSettings(), this._api.parity.rpcSettings(),
this._api.parity.enode() newConnection ? Promise.resolve(null) : this._api.parity.enode()
]) ])
.then(([ .then(([
clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
]) => { ]) => {
const isTest = netChain === 'morden' || netChain === 'testnet'; const isTest = netChain === 'morden' || netChain === 'ropsten' || netChain === 'testnet';
const longStatus = { const longStatus = {
clientVersion, clientVersion,
@ -259,21 +259,23 @@ export default class Status {
netChain, netChain,
netPort, netPort,
rpcSettings, rpcSettings,
enode,
isTest isTest
}; };
if (enode) {
longStatus.enode = enode;
}
if (!isEqual(longStatus, this._longStatus)) { if (!isEqual(longStatus, this._longStatus)) {
this._store.dispatch(statusCollection(longStatus)); this._store.dispatch(statusCollection(longStatus));
this._longStatus = longStatus; this._longStatus = longStatus;
} }
nextTimeout();
}) })
.catch((error) => { .catch((error) => {
console.error('_pollLongStatus', error); console.error('_pollLongStatus', error);
nextTimeout(250);
}); });
nextTimeout(newConnection ? 5000 : 30000);
} }
_pollLogs = () => { _pollLogs = () => {

View File

@ -31,7 +31,7 @@ const initialState = {
gasLimit: new BigNumber(0), gasLimit: new BigNumber(0),
hashrate: new BigNumber(0), hashrate: new BigNumber(0),
minGasPrice: new BigNumber(0), minGasPrice: new BigNumber(0),
netChain: 'morden', netChain: 'ropsten',
netPeers: { netPeers: {
active: new BigNumber(0), active: new BigNumber(0),
connected: new BigNumber(0), connected: new BigNumber(0),
@ -39,7 +39,7 @@ const initialState = {
}, },
netPort: new BigNumber(0), netPort: new BigNumber(0),
rpcSettings: {}, rpcSettings: {},
syncing: false, syncing: true,
isConnected: false, isConnected: false,
isConnecting: false, isConnecting: false,
isPingable: false, isPingable: false,

View File

@ -25,12 +25,13 @@ export default class SecureApi extends Api {
this._isConnecting = true; this._isConnecting = true;
this._connectState = sysuiToken === 'initial' ? 1 : 0; this._connectState = sysuiToken === 'initial' ? 1 : 0;
this._needsToken = false; this._needsToken = false;
this._nextToken = nextToken;
this._dappsPort = 8080; this._dappsPort = 8080;
this._dappsInterface = null; this._dappsInterface = null;
this._signerPort = 8180; this._signerPort = 8180;
this._followConnectionTimeoutId = null;
console.log('SecureApi:constructor', sysuiToken); // Try tokens from localstorage, then from hash
this._tokensToTry = [ sysuiToken, nextToken ].filter((t) => t && t.length);
this._followConnection(); this._followConnection();
} }
@ -40,15 +41,30 @@ export default class SecureApi extends Api {
console.log('SecureApi:setToken', this._transport.token); console.log('SecureApi:setToken', this._transport.token);
} }
_checkNodeUp () {
return fetch('/', { method: 'HEAD' })
.then(
(r) => r.status === 200,
() => false
)
.catch(() => false);
}
_followConnection = () => { _followConnection = () => {
const nextTick = () => { const nextTick = () => {
setTimeout(() => this._followConnection(), 250); if (this._followConnectionTimeoutId) {
clearTimeout(this._followConnectionTimeoutId);
}
this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250);
}; };
const setManual = () => { const setManual = () => {
this._connectState = 100; this._connectState = 100;
this._needsToken = true; this._needsToken = true;
this._isConnecting = false; this._isConnecting = false;
}; };
const lastError = this._transport.lastError; const lastError = this._transport.lastError;
const isConnected = this._transport.isConnected; const isConnected = this._transport.isConnected;
@ -58,11 +74,23 @@ export default class SecureApi extends Api {
if (isConnected) { if (isConnected) {
return this.connectSuccess(); return this.connectSuccess();
} else if (lastError) { } else if (lastError) {
const nextToken = this._nextToken || 'initial'; return this
const nextState = this._nextToken ? 0 : 1; ._checkNodeUp()
.then((isNodeUp) => {
const nextToken = this._tokensToTry[0] || 'initial';
const nextState = nextToken !== 'initial' ? 0 : 1;
this._nextToken = null; // If previous token was wrong (error while node up), delete it
this.updateToken(nextToken, nextState); if (isNodeUp) {
this._tokensToTry = this._tokensToTry.slice(1);
}
if (nextToken !== this._transport.token) {
this.updateToken(nextToken, nextState);
}
nextTick();
});
} }
break; break;

View File

@ -96,7 +96,7 @@ export default class TypedInput extends Component {
<IconButton <IconButton
iconStyle={ iconStyle } iconStyle={ iconStyle }
style={ style } style={ style }
onClick={ this.onAddField } onTouchTap={ this.onAddField }
> >
<AddIcon /> <AddIcon />
</IconButton> </IconButton>
@ -104,7 +104,7 @@ export default class TypedInput extends Component {
<IconButton <IconButton
iconStyle={ iconStyle } iconStyle={ iconStyle }
style={ style } style={ style }
onClick={ this.onRemoveField } onTouchTap={ this.onRemoveField }
> >
<RemoveIcon /> <RemoveIcon />
</IconButton> </IconButton>

View File

@ -16,26 +16,17 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ParityBackground extends Component { class ParityBackground extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static propTypes = { static propTypes = {
style: PropTypes.object.isRequired,
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string, className: PropTypes.string,
gradient: PropTypes.string,
seed: PropTypes.any,
settings: PropTypes.object.isRequired,
onClick: PropTypes.func onClick: PropTypes.func
} };
render () { render () {
const { muiTheme } = this.context; const { children, className, style, onClick } = this.props;
const { children, className, gradient, seed, settings, onClick } = this.props;
const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed);
return ( return (
<div <div
@ -48,17 +39,29 @@ class ParityBackground extends Component {
} }
} }
function mapStateToProps (state) { function mapStateToProps (_, initProps) {
const { settings } = state; const { gradient, seed, muiTheme } = initProps;
return { settings }; let _seed = seed;
} let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) };
function mapDispatchToProps (dispatch) { return (state, props) => {
return bindActionCreators({}, dispatch); const { backgroundSeed } = state.settings;
const { seed } = props;
const newSeed = seed || backgroundSeed;
if (newSeed === _seed) {
return _props;
}
_seed = newSeed;
_props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) };
return _props;
};
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps
mapDispatchToProps
)(ParityBackground); )(ParityBackground);

26
js/src/util/constants.js Normal file
View File

@ -0,0 +1,26 @@
// 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 <http://www.gnu.org/licenses/>.
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const MAX_GAS_ESTIMATION = '50000000';
export {
DEFAULT_GAS,
DEFAULT_GASPRICE,
MAX_GAS_ESTIMATION
};

View File

@ -17,12 +17,13 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import ActionDelete from 'material-ui/svg-icons/action/delete';
import ContentCreate from 'material-ui/svg-icons/content/create'; import ContentCreate from 'material-ui/svg-icons/content/create';
import ContentSend from 'material-ui/svg-icons/content/send'; import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock'; import LockIcon from 'material-ui/svg-icons/action/lock';
import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
import { EditMeta, Shapeshift, SMSVerification, Transfer, PasswordManager } from '../../modals'; import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '../../modals';
import { Actionbar, Button, Page } from '../../ui'; import { Actionbar, Button, Page } from '../../ui';
import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
@ -50,6 +51,7 @@ class Account extends Component {
propName = null propName = null
state = { state = {
showDeleteDialog: false,
showEditDialog: false, showEditDialog: false,
showFundDialog: false, showFundDialog: false,
showVerificationDialog: false, showVerificationDialog: false,
@ -62,8 +64,8 @@ class Account extends Component {
const { api } = this.context; const { api } = this.context;
const { address } = this.props.params; const { address } = this.props.params;
const store = new VerificationStore(api, address); const verificationStore = new VerificationStore(api, address);
this.setState({ verificationStore: store }); this.setState({ verificationStore });
} }
render () { render () {
@ -79,6 +81,7 @@ class Account extends Component {
return ( return (
<div className={ styles.account }> <div className={ styles.account }>
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) } { this.renderEditDialog(account) }
{ this.renderFundDialog() } { this.renderFundDialog() }
{ this.renderVerificationDialog() } { this.renderVerificationDialog() }
@ -131,7 +134,12 @@ class Account extends Component {
key='passwordManager' key='passwordManager'
icon={ <LockIcon /> } icon={ <LockIcon /> }
label='password' label='password'
onClick={ this.onPasswordClick } /> onClick={ this.onPasswordClick } />,
<Button
key='delete'
icon={ <ActionDelete /> }
label='delete account'
onClick={ this.onDeleteClick } />
]; ];
return ( return (
@ -141,6 +149,20 @@ class Account extends Component {
); );
} }
renderDeleteDialog (account) {
const { showDeleteDialog } = this.state;
if (!showDeleteDialog) {
return null;
}
return (
<DeleteAccount
account={ account }
onClose={ this.onDeleteClose } />
);
}
renderEditDialog (account) { renderEditDialog (account) {
const { showEditDialog } = this.state; const { showEditDialog } = this.state;
@ -228,6 +250,14 @@ class Account extends Component {
); );
} }
onDeleteClick = () => {
this.setState({ showDeleteDialog: true });
}
onDeleteClose = () => {
this.setState({ showDeleteDialog: false });
}
onEditClick = () => { onEditClick = () => {
this.setState({ this.setState({
showEditDialog: !this.state.showEditDialog showEditDialog: !this.state.showEditDialog

View File

@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { isEqual } from 'lodash';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
@ -30,7 +31,6 @@ export default class Summary extends Component {
link: PropTypes.string, link: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
noLink: PropTypes.bool, noLink: PropTypes.bool,
children: PropTypes.node,
handleAddSearchToken: PropTypes.func handleAddSearchToken: PropTypes.func
}; };
@ -42,8 +42,42 @@ export default class Summary extends Component {
name: 'Unnamed' name: 'Unnamed'
}; };
shouldComponentUpdate (nextProps) {
const prev = {
link: this.props.link, name: this.props.name,
noLink: this.props.noLink,
meta: this.props.account.meta, address: this.props.account.address
};
const next = {
link: nextProps.link, name: nextProps.name,
noLink: nextProps.noLink,
meta: nextProps.account.meta, address: nextProps.account.address
};
if (!isEqual(next, prev)) {
return true;
}
const prevTokens = this.props.balance.tokens || [];
const nextTokens = nextProps.balance.tokens || [];
if (prevTokens.length !== nextTokens.length) {
return true;
}
const prevValues = prevTokens.map((t) => t.value.toNumber());
const nextValues = nextTokens.map((t) => t.value.toNumber());
if (!isEqual(prevValues, nextValues)) {
return true;
}
return false;
}
render () { render () {
const { account, children, handleAddSearchToken } = this.props; const { account, handleAddSearchToken } = this.props;
const { tags } = account.meta; const { tags } = account.meta;
if (!account) { if (!account) {
@ -71,7 +105,6 @@ export default class Summary extends Component {
byline={ addressComponent } /> byline={ addressComponent } />
{ this.renderBalance() } { this.renderBalance() }
{ children }
</Container> </Container>
); );
} }

View File

@ -30,3 +30,25 @@
right: 1em; right: 1em;
top: 4em; top: 4em;
} }
.loadings {
display: flex;
flex-wrap: wrap;
.loading {
flex: 0 1 50%;
width: 50%;
height: 150px;
display: flex;
padding: 0.25em;
box-sizing: border-box;
> div {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.8);
}
}
}

View File

@ -42,33 +42,63 @@ class Accounts extends Component {
newDialog: false, newDialog: false,
sortOrder: '', sortOrder: '',
searchValues: [], searchValues: [],
searchTokens: [] searchTokens: [],
show: false
}
componentWillMount () {
window.setTimeout(() => {
this.setState({ show: true });
}, 100);
} }
render () { render () {
const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return ( return (
<div className={ styles.accounts }> <div className={ styles.accounts }>
{ this.renderNewDialog() } { this.renderNewDialog() }
{ this.renderActionbar() } { this.renderActionbar() }
<Page>
<List { this.state.show ? this.renderAccounts() : this.renderLoading() }
search={ searchValues }
accounts={ accounts }
balances={ balances }
empty={ !hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
<Tooltip
className={ styles.accountTooltip }
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
</Page>
</div> </div>
); );
} }
renderLoading () {
const { accounts } = this.props;
const loadings = ((accounts && Object.keys(accounts)) || []).map((_, idx) => (
<div key={ idx } className={ styles.loading }>
<div></div>
</div>
));
return (
<div className={ styles.loadings }>
{ loadings }
</div>
);
}
renderAccounts () {
const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<Page>
<List
search={ searchValues }
accounts={ accounts }
balances={ balances }
empty={ !hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
<Tooltip
className={ styles.accountTooltip }
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
</Page>
);
}
renderSearchButton () { renderSearchButton () {
const onChange = (searchTokens, searchValues) => { const onChange = (searchTokens, searchValues) => {
this.setState({ searchTokens, searchValues }); this.setState({ searchTokens, searchValues });

View File

@ -22,6 +22,10 @@ import { Errors, ParityBackground, Tooltips } from '../../../ui';
import styles from '../application.css'; import styles from '../application.css';
export default class Container extends Component { export default class Container extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
};
static propTypes = { static propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
showFirstRun: PropTypes.bool, showFirstRun: PropTypes.bool,
@ -30,9 +34,10 @@ export default class Container extends Component {
render () { render () {
const { children, showFirstRun, onCloseFirstRun } = this.props; const { children, showFirstRun, onCloseFirstRun } = this.props;
const { muiTheme } = this.context;
return ( return (
<ParityBackground className={ styles.container }> <ParityBackground className={ styles.container } muiTheme={ muiTheme }>
<FirstRun <FirstRun
visible={ showFirstRun } visible={ showFirstRun }
onClose={ onCloseFirstRun } /> onClose={ onCloseFirstRun } />

View File

@ -23,6 +23,11 @@
.tabs { .tabs {
width: 100%; width: 100%;
position: relative; position: relative;
display: flex;
& > * {
flex: 1;
}
} }
.tabs button, .tabs button,
@ -38,6 +43,7 @@
button.tabactive, button.tabactive,
button.tabactive:hover { button.tabactive:hover {
color: white !important;
background: rgba(0, 0, 0, 0.25) !important; background: rgba(0, 0, 0, 0.25) !important;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
} }

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
import { Tabs, Tab } from 'material-ui/Tabs'; import { Tab as MUITab } from 'material-ui/Tabs';
import { Badge, Tooltip } from '../../../ui'; import { Badge, Tooltip } from '../../../ui';
@ -33,20 +33,138 @@ const TABMAP = {
deploy: 'contract' deploy: 'contract'
}; };
class Tab extends Component {
static propTypes = {
active: PropTypes.bool,
view: PropTypes.object,
children: PropTypes.node,
pendings: PropTypes.number,
onChange: PropTypes.func
};
shouldComponentUpdate (nextProps) {
return nextProps.active !== this.props.active ||
(nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
}
render () {
const { active, view, children } = this.props;
const label = this.getLabel(view);
return (
<MUITab
className={ active ? styles.tabactive : '' }
selected={ active }
icon={ view.icon }
label={ label }
onClick={ this.handleClick }
>
{ children }
</MUITab>
);
}
getLabel (view) {
const { label } = view;
if (view.id === 'signer') {
return this.renderSignerLabel(label);
}
if (view.id === 'status') {
return this.renderStatusLabel(label);
}
return this.renderLabel(label);
}
renderLabel (name, bubble) {
return (
<div className={ styles.label }>
{ name }
{ bubble }
</div>
);
}
renderSignerLabel (label) {
const { pendings } = this.props;
if (pendings) {
const bubble = (
<Badge
color='red'
className={ styles.labelBubble }
value={ pendings } />
);
return this.renderLabel(label, bubble);
}
return this.renderLabel(label);
}
renderStatusLabel (label) {
// const { isTest, netChain } = this.props;
// const bubble = (
// <Badge
// color={ isTest ? 'red' : 'default' }
// className={ styles.labelBubble }
// value={ isTest ? 'TEST' : netChain } />
// );
return this.renderLabel(label, null);
}
handleClick = () => {
const { onChange, view } = this.props;
onChange(view);
}
}
class TabBar extends Component { class TabBar extends Component {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired router: PropTypes.object.isRequired
} };
static propTypes = { static propTypes = {
views: PropTypes.array.isRequired,
hash: PropTypes.string.isRequired,
pending: PropTypes.array, pending: PropTypes.array,
isTest: PropTypes.bool, isTest: PropTypes.bool,
netChain: PropTypes.string, netChain: PropTypes.string
settings: PropTypes.object.isRequired };
}
static defaultProps = {
pending: []
};
state = { state = {
activeRoute: '/accounts' activeViewId: ''
};
setActiveView (props = this.props) {
const { hash, views } = props;
const view = views.find((view) => view.value === hash);
this.setState({ activeViewId: view.id });
}
componentWillMount () {
this.setActiveView();
}
componentWillReceiveProps (nextProps) {
if (nextProps.hash !== this.props.hash) {
this.setActiveView(nextProps);
}
}
shouldComponentUpdate (nextProps, nextState) {
return (nextProps.hash !== this.props.hash) ||
(nextProps.pending.length !== this.props.pending.length) ||
(nextState.activeViewId !== this.state.activeViewId);
} }
render () { render () {
@ -81,100 +199,64 @@ class TabBar extends Component {
} }
renderTabs () { renderTabs () {
const { settings } = this.props; const { views, pending } = this.props;
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1]; const { activeViewId } = this.state;
const hash = TABMAP[windowHash] || windowHash;
const items = Object.keys(settings.views) const items = views
.filter((id) => settings.views[id].fixed || settings.views[id].active) .map((view, index) => {
.map((id) => { const body = (view.id === 'accounts')
const view = settings.views[id]; ? (
let label = this.renderLabel(view.label); <Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
let body = null; )
: null;
if (id === 'accounts') { const active = activeViewId === view.id;
body = (
<Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
);
} else if (id === 'signer') {
label = this.renderSignerLabel(label);
} else if (id === 'status') {
label = this.renderStatusLabel(label);
}
return ( return (
<Tab <Tab
className={ hash === view.value ? styles.tabactive : '' } active={ active }
value={ view.value } view={ view }
icon={ view.icon } onChange={ this.onChange }
key={ id } key={ index }
label={ label } pendings={ pending.length }
onActive={ this.onActivate(view.route) }> >
{ body } { body }
</Tab> </Tab>
); );
}); });
return ( return (
<Tabs <div
className={ styles.tabs } className={ styles.tabs }
value={ hash }> onChange={ this.onChange }>
{ items } { items }
</Tabs>
);
}
renderLabel = (name, bubble) => {
return (
<div className={ styles.label }>
{ name }
{ bubble }
</div> </div>
); );
} }
renderSignerLabel = (label) => { onChange = (view) => {
const { pending } = this.props;
let bubble = null;
if (pending && pending.length) {
bubble = (
<Badge
color='red'
className={ styles.labelBubble }
value={ pending.length } />
);
}
return this.renderLabel(label, bubble);
}
renderStatusLabel = (label) => {
// const { isTest, netChain } = this.props;
// const bubble = (
// <Badge
// color={ isTest ? 'red' : 'default' }
// className={ styles.labelBubble }
// value={ isTest ? 'TEST' : netChain } />
// );
return this.renderLabel(label, null);
}
onActivate = (activeRoute) => {
const { router } = this.context; const { router } = this.context;
return (event) => { router.push(view.route);
router.push(activeRoute); this.setState({ activeViewId: view.id });
this.setState({ activeRoute });
};
} }
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { settings } = state; const { views } = state.settings;
return { settings }; const filteredViews = Object
.keys(views)
.filter((id) => views[id].fixed || views[id].active)
.map((id) => ({
...views[id],
id
}));
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
const hash = TABMAP[windowHash] || windowHash;
return { views: filteredViews, hash };
} }
function mapDispatchToProps (dispatch) { function mapDispatchToProps (dispatch) {

View File

@ -52,6 +52,7 @@
"description": "Have a peak on internals of transaction queue of your node.", "description": "Have a peak on internals of transaction queue of your node.",
"author": "Parity Team <admin@ethcore.io>", "author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0", "version": "1.0.0",
"visible": true,
"secure": true "secure": true
} }
] ]

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