Merge branch 'master' into rpc-middleware

Conflicts:
	Cargo.lock
	rpc/src/v1/tests/mocked/parity_accounts.rs
This commit is contained in:
Tomasz Drwięga 2016-11-25 20:16:55 +01:00
commit a9028ca986
145 changed files with 5262 additions and 1127 deletions

View File

@ -31,10 +31,12 @@ linux-stable:
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- 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 builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- 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-linux-gnu
- 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 $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:
- rust
- rust-stable
@ -96,6 +98,8 @@ linux-centos:
- md5sum target/release/parity > parity.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/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.md5 --body parity.md5
tags:
@ -126,10 +130,12 @@ linux-i686:
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
- aws configure set aws_access_key_id $s3_key
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5
- 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 builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
- 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/i686-unknown-linux-gnu
- 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 $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:
- rust
- rust-i686
@ -166,10 +172,12 @@ linux-armv7:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- 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 builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- 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/armv7-unknown-linux-gnueabihf
- 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 $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:
- rust
- rust-arm
@ -206,10 +214,12 @@ linux-arm:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- 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 builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- 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/arm-unknown-linux-gnueabihf
- 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 $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:
- rust
- rust-arm
@ -241,8 +251,10 @@ linux-armv6:
- md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity.md5 --body parity.md5
- 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/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:
- rust
- rust-arm
@ -279,10 +291,12 @@ linux-aarch64:
- 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
- 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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5
- 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 builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
- 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-arm
@ -291,6 +305,41 @@ linux-aarch64:
- target/aarch64-unknown-linux-gnu/release/parity
name: "aarch64-unknown-linux-gnu_parity"
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:
stage: build
only:
@ -299,13 +348,22 @@ darwin:
- stable
- triggers
script:
- cargo build --release -p ethstore $CARGOFLAGS
- cargo build --release $CARGOFLAGS
- rm -rf 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_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
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5
- 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-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:
- osx
artifacts:
@ -349,14 +407,20 @@ windows:
- cd ..\..
- aws configure set aws_access_key_id %s3_key%
- 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
- 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
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
- 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
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
- 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 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 builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
- echo %CI_BUILD_REF_NAME%
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %S3_BUCKET%
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
- 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 %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:
- rust-windows
artifacts:
@ -394,13 +458,13 @@ test-rust-stable:
image: ethcore/rust:stable
before_script:
- 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
- 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:
- export RUST_BACKTRACE=1
- 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:
- rust
- rust-stable
@ -411,13 +475,13 @@ test-rust-beta:
image: ethcore/rust:beta
before_script:
- 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
- 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:
- export RUST_BACKTRACE=1
- 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:
- rust
- rust-beta
@ -429,28 +493,17 @@ test-rust-nightly:
image: ethcore/rust:nightly
before_script:
- 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
- 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:
- export RUST_BACKTRACE=1
- 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:
- rust
- rust-nightly
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:
stage: js-build
only:
@ -459,9 +512,11 @@ js-release:
- stable
image: ethcore/rust:stable
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:
- if [[ $NIGHTLY != "master" ]]; then ./js/scripts/build.sh; fi
- if [[ $NIGHTLY != "master" ]]; then ./js/scripts/release.sh; fi
- echo $JS_FILES_MODIFIED
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh&./js/scripts/release.sh; fi
tags:
- javascript

29
Cargo.lock generated
View File

@ -393,7 +393,7 @@ version = "1.5.0"
dependencies = [
"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)",
"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)",
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -483,7 +483,7 @@ dependencies = [
"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)",
"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)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
@ -1033,23 +1033,7 @@ dependencies = [
[[package]]
name = "mio"
version = "0.6.1"
source = "git+https://github.com/carllerche/mio#56f8663510196fdca04bdf7c5f4d60b24297826f"
dependencies = [
"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)",
"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)",
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mio"
version = "0.6.1"
source = "git+https://github.com/ethcore/mio.git#ef182bae193a9c7457cd2cf661fcaffb226e3eef"
source = "git+https://github.com/ethcore/mio#ef182bae193a9c7457cd2cf661fcaffb226e3eef"
dependencies = [
"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)",
@ -1286,7 +1270,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#587684374a12bf715151dd987a552a3d61e42972"
source = "git+https://github.com/ethcore/js-precompiled.git#7cb42b0c636f76eb478c9270a1e507ac3c3ba434"
dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1529,7 +1513,7 @@ version = "0.6.3"
source = "git+https://github.com/ethcore/rotor#c1a2dd0046c5ea2517a5b637fca8ee2e77021e82"
dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.1 (git+https://github.com/ethcore/mio.git)",
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
"quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2078,8 +2062,7 @@ dependencies = [
"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.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.git)" = "<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 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>"

View File

@ -105,7 +105,7 @@ impl RpcHandler for RpcMiddleware {
type Metadata = Meta;
fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Option<Self::Metadata> {
let meta = request.headers().get::<hyper::header::Referer>()
request.headers().get::<hyper::header::Referer>()
.and_then(|referer| hyper::Url::parse(referer).ok())
.and_then(|url| {
url.path_segments()
@ -113,9 +113,7 @@ impl RpcHandler for RpcMiddleware {
.map(|app_id| Meta {
app_id: app_id.to_owned(),
})
});
println!("{:?}, {:?}", meta, request.headers());
meta
})
}
fn handle_request<H>(&self, request_str: &str, response_handler: H, meta: Option<Self::Metadata>) where

View File

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

View File

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

@ -1 +1 @@
Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8
Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be

View File

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

View File

@ -225,14 +225,20 @@ impl AccountProvider {
}
/// Returns `true` if the password for `account` is `password`. `false` if not.
pub fn test_password(&self, account: &Address, password: String) -> Result<bool, Error> {
match self.sstore.sign(account, &password, &Default::default()) {
pub fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error> {
match self.sstore.sign(account, password, &Default::default()) {
Ok(_) => Ok(true),
Err(SSError::InvalidPassword) => Ok(false),
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.
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)

View File

@ -140,7 +140,7 @@ pub trait BlockProvider {
fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockNumber, to_block: BlockNumber) -> Vec<BlockNumber>;
/// 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;
}

View File

@ -417,13 +417,13 @@ mod tests {
let engine = Spec::new_test_round().engine;
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()]);
let first_ok = engine.verify_block_seal(&header).is_ok();
assert!(engine.verify_block_seal(&header).is_err());
step = step + 1;
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!(first_ok ^ second_ok);
assert!(engine.verify_block_seal(&header).is_ok());
}
}

View File

@ -34,7 +34,7 @@ fn do_json_test(json_data: &[u8]) -> Vec<String> {
Some(x) if x < 1_150_000 => &old_schedule,
Some(_) => &new_schedule
};
let allow_network_id_of_one = number.map_or(false, |n| n >= 3_500_000);
let allow_network_id_of_one = number.map_or(false, |n| n >= 2_675_000);
let rlp: Vec<u8> = test.rlp.into();
let res = UntrustedRlp::new(&rlp)

View File

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

View File

@ -128,6 +128,9 @@ impl<T> TraceDB<T> where T: DatabaseExtras {
/// Creates new instance of `TraceDB`.
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
let mut batch = DBTransaction::new(&tracesdb);
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);
tracesdb.write(batch).expect("failed to update version");
@ -413,8 +416,12 @@ mod tests {
struct NoopExtras;
impl DatabaseExtras for NoopExtras {
fn block_hash(&self, _block_number: BlockNumber) -> Option<H256> {
unimplemented!();
fn block_hash(&self, block_number: BlockNumber) -> Option<H256> {
if block_number == 0 {
Some(H256::default())
} else {
unimplemented!()
}
}
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 mut config = Config::default();
config.enabled = true;
let block_0 = H256::from(0xa1);
let block_1 = H256::from(0xa2);
let tx_0 = H256::from(0xff);
let tx_1 = H256::from(0xaf);
let block_1 = H256::from(0xa1);
let block_2 = H256::from(0xa2);
let tx_1 = H256::from(0xff);
let tx_2 = H256::from(0xaf);
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.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(2, vec![tx_2.clone()]);
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
let request = create_simple_import_request(1, block_1.clone());
let mut batch = DBTransaction::new(&db);
@ -617,38 +610,56 @@ mod tests {
db.write(batch).unwrap();
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)]),
to_address: AddressesFilter::from(vec![]),
};
let traces = tracedb.filter(&filter);
assert_eq!(traces.len(), 2);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone()));
assert_eq!(traces[1], 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!(traces[1], create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
let traces = tracedb.block_traces(0).unwrap();
assert_eq!(traces.len(), 1);
assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone()));
assert!(tracedb.block_traces(0).is_some(), "Genesis trace should be always present.");
let traces = tracedb.block_traces(1).unwrap();
assert_eq!(traces.len(), 1);
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.transaction_traces(0, 0).unwrap();
let traces = tracedb.block_traces(2).unwrap();
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();
assert_eq!(traces.len(), 1);
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(2, 0, vec![]).unwrap(), create_simple_localized_trace(2, block_2.clone(), tx_2.clone()));
}
#[test]
@ -660,8 +671,10 @@ mod tests {
let block_0 = H256::from(0xa1);
let tx_0 = H256::from(0xff);
extras.block_hashes.insert(0, block_0.clone());
extras.transaction_hashes.insert(0, vec![tx_0.clone()]);
extras.block_hashes.insert(0, H256::default());
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
config.enabled = true;
@ -669,8 +682,8 @@ mod tests {
{
let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras.clone()));
// import block 0
let request = create_simple_import_request(0, block_0.clone());
// import block 1
let request = create_simple_import_request(1, block_0.clone());
let mut batch = DBTransaction::new(&db);
tracedb.import(&mut batch, request);
db.write(batch).unwrap();
@ -678,8 +691,28 @@ mod tests {
{
let tracedb = TraceDB::new(config.clone(), db.clone(), Arc::new(extras));
let traces = tracedb.transaction_traces(0, 0);
assert_eq!(traces.unwrap(), vec![create_simple_localized_trace(0, block_0, tx_0)]);
let traces = tracedb.transaction_traces(1, 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.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Default)]
pub struct FlatBlockTraces(Vec<FlatTransactionTraces>);
impl HeapSizeOf for FlatBlockTraces {

View File

@ -35,6 +35,9 @@ pub mod kind;
const MIN_MEM_LIMIT: usize = 16384;
const MIN_QUEUE_LIMIT: usize = 512;
// maximum possible number of verification threads.
const MAX_VERIFIERS: usize = 8;
/// Type alias for block queue convenience.
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.
pub struct Verifying<K: Kind> {
hash: H256,
@ -97,11 +131,12 @@ pub struct VerificationQueue<K: Kind> {
engine: Arc<Engine>,
more_to_verify: Arc<SCondvar>,
verification: Arc<Verification<K>>,
verifiers: Vec<JoinHandle<()>>,
verifiers: Mutex<(Vec<VerifierHandle>, usize)>,
deleting: Arc<AtomicBool>,
ready_signal: Arc<QueueSignal>,
empty: Arc<SCondvar>,
processing: RwLock<HashSet<H256>>,
ticks_since_adjustment: AtomicUsize,
max_queue_size: usize,
max_mem_use: usize,
}
@ -157,6 +192,7 @@ struct Verification<K: Kind> {
more_to_verify: SMutex<()>,
empty: SMutex<()>,
sizes: Sizes,
check_seal: bool,
}
impl<K: Kind> VerificationQueue<K> {
@ -173,7 +209,8 @@ impl<K: Kind> VerificationQueue<K> {
unverified: AtomicUsize::new(0),
verifying: AtomicUsize::new(0),
verified: AtomicUsize::new(0),
}
},
check_seal: check_seal,
});
let more_to_verify = Arc::new(SCondvar::new());
let deleting = Arc::new(AtomicBool::new(false));
@ -185,44 +222,82 @@ impl<K: Kind> VerificationQueue<K> {
let empty = Arc::new(SCondvar::new());
let panic_handler = PanicHandler::new_in_arc();
let mut verifiers: Vec<JoinHandle<()>> = Vec::new();
let thread_count = max(::num_cpus::get(), 3) - 2;
for i in 0..thread_count {
let verification = verification.clone();
let engine = engine.clone();
let more_to_verify = more_to_verify.clone();
let ready_signal = ready_signal.clone();
let empty = empty.clone();
let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS);
let default_amount = max(::num_cpus::get(), 3) - 2;
let mut verifiers = Vec::with_capacity(max_verifiers);
debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount);
for i in 0..max_verifiers {
debug!(target: "verification", "Adding verification thread #{}", i);
let deleting = deleting.clone();
let panic_handler = panic_handler.clone();
verifiers.push(
thread::Builder::new()
let verification = verification.clone();
let engine = engine.clone();
let wait = more_to_verify.clone();
let ready = ready_signal.clone();
let empty = empty.clone();
// enable only the first few verifiers.
let sleep = if i < default_amount {
Arc::new(AtomicBool::new(false))
} else {
Arc::new(AtomicBool::new(true))
};
verifiers.push(VerifierHandle {
deleting: deleting.clone(),
sleep: sleep.clone(),
thread: thread::Builder::new()
.name(format!("Verifier #{}", i))
.spawn(move || {
panic_handler.catch_panic(move || {
VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty, check_seal)
VerificationQueue::verify(verification, engine, wait, ready, deleting, empty, sleep)
}).unwrap()
})
.expect("Error starting block verification thread")
);
.expect("Failed to create verifier thread.")
});
}
VerificationQueue {
engine: engine,
panic_handler: panic_handler,
ready_signal: ready_signal.clone(),
more_to_verify: more_to_verify.clone(),
verification: verification.clone(),
verifiers: verifiers,
deleting: deleting.clone(),
ready_signal: ready_signal,
more_to_verify: more_to_verify,
verification: verification,
verifiers: Mutex::new((verifiers, default_amount)),
deleting: deleting,
processing: RwLock::new(HashSet::new()),
empty: empty.clone(),
empty: empty,
ticks_since_adjustment: AtomicUsize::new(0),
max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT),
max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT),
}
}
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 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();
@ -255,7 +330,7 @@ impl<K: Kind> VerificationQueue<K> {
};
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) => {
let mut verifying = verification.verifying.lock();
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 inserted_size = 0;
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
assert!(verifying.pop_front().is_some());
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) {
{
self.verification.unverified.lock().shrink_to_fit();
// number of ticks to average queue stats over
// 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.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();
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...");
self.clear();
self.deleting.store(true, AtomicOrdering::Release);
self.more_to_verify.notify_all();
for t in self.verifiers.drain(..) {
t.join().unwrap();
let mut verifiers = self.verifiers.get_mut();
let mut verifiers = &mut verifiers.0;
// first pass to signal conclusion. must be done before
// notify or deadlock possible.
for handle in verifiers.iter() {
handle.conclude();
}
self.more_to_verify.notify_all();
// second pass to join.
for handle in verifiers.drain(..) {
handle.join();
}
trace!(target: "shutdown", "[VerificationQueue] Closed.");
}
}
@ -611,4 +776,56 @@ mod tests {
}
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

@ -47,7 +47,7 @@ impl Random for [u8; 32] {
pub fn random_phrase(words: usize) -> String {
lazy_static! {
static ref WORDS: Vec<String> = String::from_utf8_lossy(include_bytes!("../res/wordlist.txt"))
.split("\n")
.lines()
.map(|s| s.to_owned())
.collect();
}
@ -55,8 +55,19 @@ pub fn random_phrase(words: usize) -> String {
(0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ")
}
#[cfg(test)]
mod tests {
use super::random_phrase;
#[test]
fn should_produce_right_number_of_words() {
let p = random_phrase(10);
assert_eq!(p.split(" ").count(), 10);
}
#[test]
fn should_not_include_carriage_return() {
let p = random_phrase(10);
assert!(!p.contains('\r'), "Carriage return should be trimmed.");
}
}

View File

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

View File

@ -15,7 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default function (rpc) {
const subscriptions = [];
let subscriptions = [];
let pollStatusIntervalId = null;
function getCoins () {
return rpc.get('getcoins');
@ -45,6 +46,24 @@ export default function (rpc) {
callback,
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) {
@ -81,13 +100,12 @@ export default function (rpc) {
subscriptions.forEach(_getSubscriptionStatus);
}
setInterval(_pollStatus, 2000);
return {
getCoins,
getMarketInfo,
getStatus,
shift,
subscribe
subscribe,
unsubscribe
};
}

View File

@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
import React from 'react';
export default (
export const termsOfService = (
<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>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>
</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._sendSubscriptionChanges();
this._subscribedToPendings = false;
this._pendingsSubscriptionId = null;
this._subscribedToBlock = false;
this._blockSubscriptionId = null;
}
get address () {
@ -239,44 +243,71 @@ export default class Contract {
return event;
}
subscribe (eventName = null, options = {}, callback) {
return new Promise((resolve, reject) => {
let event = null;
_findEvent (eventName = null) {
const event = eventName
? this._events.find((evt) => evt.name === eventName)
: null;
if (eventName) {
event = this._events.find((evt) => evt.name === eventName);
if (!event) {
if (eventName && !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;
}
throw new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`);
}
return this._subscribe(event, options, callback).then(resolve).catch(reject);
return event;
}
_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) {
const subscriptionId = nextSubscriptionId++;
const options = Object.assign({}, _options, {
address: this._address,
topics: [event ? event.signature : null]
});
const { skipInitFetch } = _options;
delete _options['skipInitFetch'];
return this._api.eth
.newFilter(options)
return this
._createEthFilter(event, _options)
.then((filterId) => {
return this._api.eth
.getFilterLogs(filterId)
.then((logs) => {
callback(null, this.parseEventLogs(logs));
this._subscriptions[subscriptionId] = {
options,
options: _options,
callback,
filterId
};
if (skipInitFetch) {
this._subscribeToChanges();
return subscriptionId;
}
return this._api.eth
.getFilterLogs(filterId)
.then((logs) => {
callback(null, this.parseEventLogs(logs));
this._subscribeToChanges();
return subscriptionId;
});
});
@ -285,19 +316,89 @@ export default class Contract {
unsubscribe (subscriptionId) {
return this._api.eth
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
.then(() => {
delete this._subscriptions[subscriptionId];
})
.catch((error) => {
console.error('unsubscribe', error);
})
.then(() => {
delete this._subscriptions[subscriptionId];
this._unsubscribeFromChanges();
});
}
_sendSubscriptionChanges = () => {
_subscribeToChanges = () => {
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(
subscriptions.map((subscription) => {
return this._api.eth.getFilterChanges(subscription.filterId);
@ -315,12 +416,9 @@ export default class Contract {
console.error('_sendSubscriptionChanges', error);
}
});
timeout();
})
.catch((error) => {
console.error('_sendSubscriptionChanges', error);
timeout();
});
}
}

View File

@ -437,6 +437,7 @@ describe('api/contract/Contract', () => {
]
}
];
const logs = [{
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
@ -450,6 +451,7 @@ describe('api/contract/Contract', () => {
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0'
}];
const parsed = [{
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
@ -466,11 +468,13 @@ describe('api/contract/Contract', () => {
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
},
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0'
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: new BigNumber(0)
}];
let contract;
beforeEach(() => {
@ -496,18 +500,19 @@ describe('api/contract/Contract', () => {
scope = mockHttp([
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } },
{ method: 'eth_getFilterChanges', reply: { result: logs } },
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } }
]);
cbb = sinon.stub();
cbe = sinon.stub();
return contract.subscribe('Message', {}, cbb);
return contract.subscribe('Message', { toBlock: 'pending' }, cbb);
});
it('sets the subscriptionId returned', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
expect(subscriptionId).to.equal(1);
});
@ -515,7 +520,7 @@ describe('api/contract/Contract', () => {
it('creates a new filter and retrieves the logs on it', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
expect(scope.isDone()).to.be.true;
});
@ -523,7 +528,7 @@ describe('api/contract/Contract', () => {
it('returns the logs to the callback', () => {
return contract
.subscribe('Message', {}, cbe)
.subscribe('Message', { toBlock: 'pending' }, cbe)
.then((subscriptionId) => {
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/>.
import BigNumber from 'bignumber.js';
import { range } from 'lodash';
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
@ -50,14 +51,19 @@ export function inHash (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) {
let topics = (_topics || [])
.filter((topic) => topic)
.map(inHex);
.filter((topic) => topic === null || topic)
.map((topic) => topic === null ? null : pad(topic, 32));
while (topics.length < 4) {
topics.push(null);
}
// while (topics.length < 4) {
// topics.push(null);
// }
return topics;
}

View File

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

View File

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

View File

@ -29,21 +29,33 @@ export default class Ws extends JsonRpcBase {
this._token = token;
this._messages = {};
this._connecting = true;
this._connecting = false;
this._connected = false;
this._lastError = null;
this._autoConnect = false;
this._autoConnect = true;
this._retries = 0;
this._reconnectTimeoutId = null;
this._connect();
}
updateToken (token) {
this._token = token;
this._autoConnect = false;
this._autoConnect = true;
this._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 sha3 = keccak_256(`${this._token}:${time}`);
const hash = `${sha3}_${time}`;
@ -53,6 +65,7 @@ export default class Ws extends JsonRpcBase {
this._ws.onopen = null;
this._ws.onclose = null;
this._ws.onmessage = null;
this._ws.close();
this._ws = null;
}
@ -65,6 +78,27 @@ export default class Ws extends JsonRpcBase {
this._ws.onopen = this._onOpen;
this._ws.onclose = this._onClose;
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) => {
@ -72,6 +106,7 @@ export default class Ws extends JsonRpcBase {
this._connected = true;
this._connecting = false;
this._autoConnect = true;
this._retries = 0;
Object.keys(this._messages)
.filter((id) => this._messages[id].queued)
@ -79,19 +114,40 @@ export default class Ws extends JsonRpcBase {
}
_onClose = (event) => {
console.log('ws:onClose', event);
this._connected = false;
this._connecting = false;
this._lastError = event;
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) => {
// Only print error if the WS is connected
// ie. don't print if error == closed
window.setTimeout(() => {
if (this._connected) {
console.error('ws:onError', event);
this._lastError = event;
}
}, 50);
}
_onMessage = (event) => {
// Event sent by Signer Broadcaster
@ -127,11 +183,16 @@ export default class Ws extends JsonRpcBase {
_send = (id) => {
const message = this._messages[id];
message.queued = !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) {
@ -159,4 +220,27 @@ export default class Ws extends JsonRpcBase {
get 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;
}
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 { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
import { bytesToHex, hex2Ascii } from './format';
import { bytesToHex, hex2Ascii, asciiToHex } from './format';
import { fromWei, toWei } from './wei';
import { sha3 } from './sha3';
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
@ -31,6 +31,7 @@ export default {
isString,
bytesToHex,
hex2Ascii,
asciiToHex,
createIdentityImg,
decodeCallData,
decodeMethodInput,

View File

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

View File

@ -14,15 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]);
};
export const checkIfRequested = (contract, account) => {
let subId = null;
let resolved = false;
return new Promise((resolve, reject) => {
contract.subscribe('Requested', {
contract
.subscribe('Requested', {
fromBlock: 0, toBlock: 'pending'
}, (err, logs) => {
if (err) {
@ -31,22 +33,20 @@ export const checkIfRequested = (contract, account) => {
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) => {
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;
resolve(e ? e.transactionHash : false);
resolved = true;
if (subId) {
contract.unsubscribe(subId);
}
})
.then((_subId) => {
subId = _subId;
if (resolved) {
contract.unsubscribe(subId);
}
throw new Error(data.message || 'unknown error');
});
});
};

View File

@ -105,7 +105,7 @@ export function attachInstances () {
])
.then(([registryAddress, netChain]) => {
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(`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
// 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
// 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 () {
const poll = () => this.fetchTransactionData().then(poll).catch(poll);
this._timeout = setTimeout(poll, 2000);
const poll = () => {
this._timeout = window.setTimeout(() => {
this.fetchTransactionData().then(poll).catch(poll);
}, 1000);
};
poll();
}
componentWillUnmount () {

View File

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

View File

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

View File

@ -86,6 +86,22 @@ export default class Names extends Component {
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 () {
const { action, name } = this.state;
const { fee, pending, queue } = this.props;
@ -120,7 +136,7 @@ export default class Names extends Component {
label={ action === 'reserve' ? 'Reserve' : 'Drop' }
primary
icon={ <CheckIcon /> }
onClick={ this.onSubmitClick }
onTouchTap={ this.onSubmitClick }
/>
{ queue.length > 0
? (<div>{ useSignerText }{ renderQueue(queue) }</div>)

View File

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

View File

@ -46,6 +46,12 @@ import './index.html';
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 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: {
desc: 'Returns a list of the accounts available from Geth',
params: [],

View File

@ -54,8 +54,6 @@ export default class RecoveryPhrase extends Component {
<Input
hint='the account recovery phrase'
label='account recovery phrase'
multiLine
rows={ 1 }
value={ recoveryPhrase }
onChange={ this.onEditPhrase } />
<Input
@ -112,17 +110,23 @@ export default class RecoveryPhrase extends Component {
}
onEditPhrase = (event) => {
const value = event.target.value;
let error = null;
const recoveryPhrase = event.target.value
.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) {
error = ERRORS.noPhrase;
const parts = recoveryPhrase.split(' ');
let recoveryPhraseError = null;
if (!recoveryPhrase || recoveryPhrase.length < 25 || parts.length < 8) {
recoveryPhraseError = ERRORS.noPhrase;
}
this.setState({
recoveryPhrase: value,
recoveryPhraseError: error,
isValidPhrase: !error
recoveryPhrase,
recoveryPhraseError,
isValidPhrase: !recoveryPhraseError
}, 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,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
warning: PropTypes.string,
onValueChange: PropTypes.func.isRequired
}
@ -44,6 +45,7 @@ export default class DetailsStep extends Component {
return (
<Form>
{ this.renderWarning() }
<AddressSelect
label='from account'
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) => {
const { contract, onFuncChange } = this.props;

View File

@ -33,3 +33,12 @@
.txhash {
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/>.
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 ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { MAX_GAS_ESTIMATION } from '../../util/constants';
import { validateAddress, validateUint } from '../../util/validation';
import DetailsStep from './DetailsStep';
import ERRORS from '../Transfer/errors';
import { ERROR_CODES } from '../../api/transport/error';
export default class ExecuteContract extends Component {
class ExecuteContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
@ -36,6 +40,7 @@ export default class ExecuteContract extends Component {
fromAddress: PropTypes.string,
accounts: PropTypes.object,
contract: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired
}
@ -46,6 +51,8 @@ export default class ExecuteContract extends Component {
fromAddressError: null,
func: null,
funcError: null,
gas: null,
gasLimitError: null,
values: [],
valuesError: [],
step: 0,
@ -64,6 +71,12 @@ export default class ExecuteContract extends Component {
this.onFuncChange(null, functions[0]);
}
componentWillReceiveProps (newProps) {
if (newProps.fromAddress !== this.props.fromAddress) {
this.estimateGas(newProps.fromAddress);
}
}
render () {
const { sending } = this.state;
@ -119,7 +132,7 @@ export default class ExecuteContract extends Component {
renderStep () {
const { onFromAddressChange } = this.props;
const { step, busyState, txhash, rejected } = this.state;
const { step, busyState, gasLimitError, txhash, rejected } = this.state;
if (rejected) {
return (
@ -135,6 +148,7 @@ export default class ExecuteContract extends Component {
<DetailsStep
{ ...this.props }
{ ...this.state }
warning={ gasLimitError }
onAmountChange={ this.onAmountChange }
onFromAddressChange={ onFromAddressChange }
onFuncChange={ this.onFuncChange }
@ -156,7 +170,7 @@ export default class ExecuteContract extends Component {
}
onAmountChange = (amount) => {
this.setState({ amount });
this.setState({ amount }, this.estimateGas);
}
onFuncChange = (event, func) => {
@ -182,7 +196,7 @@ export default class ExecuteContract extends Component {
this.setState({
func,
values
});
}, this.estimateGas);
}
onValueChange = (event, index, _value) => {
@ -211,6 +225,46 @@ export default class ExecuteContract extends Component {
this.setState({
values: [].concat(values),
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);
});
}
@ -219,6 +273,7 @@ export default class ExecuteContract extends Component {
const { fromAddress } = this.props;
const { amount, func, values } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: fromAddress,
value: api.util.toWei(amount || 0)
};
@ -237,13 +292,13 @@ export default class ExecuteContract extends Component {
return api
.pollMethod('parity_checkRequest', requestId)
.catch((e) => {
if (e.code === ERROR_CODES.REQUEST_REJECTED) {
.catch((error) => {
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
this.setState({ rejected: true });
return false;
}
throw e;
throw error;
});
})
.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 remove = removable
? (
<IconButton onClick={ onDelete }>
<IconButton onTouchTap={ onDelete }>
<DeleteIcon />
</IconButton>
)

View File

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

View File

@ -16,8 +16,8 @@
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import DoneIcon from 'material-ui/svg-icons/action/done-all';
import CancelIcon from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '../../ui';
@ -77,7 +77,7 @@ export default class SMSVerification extends Component {
const cancel = (
<Button
key='cancel' label='Cancel'
icon={ <ContentClear /> }
icon={ <CancelIcon /> }
onClick={ onClose }
/>
);
@ -92,7 +92,7 @@ export default class SMSVerification extends Component {
<Button
key='done' label='Done'
disabled={ !isStepValid }
icon={ <ActionDoneAll /> }
icon={ <DoneIcon /> }
onClick={ onClose }
/>
</div>
@ -140,24 +140,13 @@ export default class SMSVerification extends Component {
setNumber, setConsentGiven, setCode
} = this.props.store;
if (phase === 5) {
return (<Done />);
}
if (phase === 4) {
return (<SendConfirmation step={ step } tx={ confirmationTx } />);
}
if (phase === 3) {
switch (phase) {
case 0:
return (
<QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid }
setCode={ setCode }
/>
<p>Loading SMS Verification.</p>
);
}
if (phase === 2) {
return (<SendRequest step={ step } tx={ requestTx } />);
}
if (phase === 1) {
case 1:
const { setNumber, setConsentGiven } = this.props.store;
return (
<GatherData
@ -166,11 +155,32 @@ export default class SMSVerification extends Component {
setNumber={ setNumber } setConsentGiven={ setConsentGiven }
/>
);
}
if (phase === 0) {
return (<p>Preparing awesomeness!</p>);
}
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 { 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 waitForConfirmations from '../../util/wait-for-block-confirmations';
@ -87,7 +88,7 @@ export default class VerificationStore {
this.account = account;
this.step = LOADING;
Contracts.create(api).registry.getContract('smsVerification')
Contracts.create(api).registry.getContract('smsverification')
.then((contract) => {
this.contract = contract;
this.load();

View File

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

View File

@ -19,7 +19,9 @@ const ERRORS = {
invalidAddress: 'the supplied address is an invalid network address',
invalidAmount: 'the supplied amount should be a valid positive number',
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;

View File

@ -14,6 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.info {
line-height: 1.618em;
width: 100%;
@ -151,3 +152,13 @@
.gasPriceDesc {
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 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 ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
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 Extras from './Extras';
@ -30,8 +33,6 @@ import styles from './transfer.css';
import { ERROR_CODES } from '../../api/transport/error';
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const TITLES = {
transfer: 'transfer details',
sending: 'sending',
@ -42,7 +43,7 @@ const TITLES = {
const STAGES_BASIC = [TITLES.transfer, 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 = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
@ -52,6 +53,7 @@ export default class Transfer extends Component {
account: PropTypes.object,
balance: PropTypes.object,
balances: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
onClose: PropTypes.func
}
@ -64,6 +66,7 @@ export default class Transfer extends Component {
gas: DEFAULT_GAS,
gasEst: '0',
gasError: null,
gasLimitError: null,
gasPrice: DEFAULT_GASPRICE,
gasPriceHistogram: {},
gasPriceError: null,
@ -103,6 +106,7 @@ export default class Transfer extends Component {
visible
scroll
>
{ this.renderWarning() }
{ this.renderPage() }
</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 () {
const detailsValid = !this.state.recipientError && !this.state.valueError && !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
.estimateGas({
gas: MAX_GAS_ESTIMATION,
from: account.address,
to: token.address
}, [
@ -532,6 +551,7 @@ export default class Transfer extends Component {
const { account } = this.props;
const { data, recipient, value } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: account.address,
to: recipient,
value: api.util.toWei(value || 0)
@ -552,19 +572,29 @@ export default class Transfer extends Component {
return;
}
const { gasLimit } = this.props;
(this.state.isEth
? this._estimateGasEth()
: this._estimateGasToken()
).then((_value) => {
let gas = _value;
).then((gasEst) => {
let gas = gasEst;
let gasLimitError = null;
if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2);
}
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
this.setState({
gas: gas.toFixed(0),
gasEst: _value.toFormat()
gasEst: gasEst.toFormat(),
gasLimitError
}, this.recalculate);
})
.catch((error) => {
@ -649,3 +679,18 @@ export default class Transfer extends Component {
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 AddContract from './AddContract';
import CreateAccount from './CreateAccount';
import DeleteAccount from './DeleteAccount';
import DeployContract from './DeployContract';
import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract';
@ -32,6 +33,7 @@ export {
AddAddress,
AddContract,
CreateAccount,
DeleteAccount,
DeployContract,
EditMeta,
ExecuteContract,

View File

@ -14,9 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { throttle } from 'lodash';
import { getBalances, getTokens } from './balancesActions';
import { setAddressImage } from './imagesActions';
import Contracts from '../../contracts';
import * as abis from '../../contracts/abi';
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
@ -31,13 +34,32 @@ export default class Balances {
constructor (store, api) {
this._api = api;
this._store = store;
this._tokens = {};
this._images = {};
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 () {
this._subscribeBlockNumber();
this._subscribeAccountsInfo();
this._retrieveTokens();
}
_subscribeAccountsInfo () {
@ -48,10 +70,7 @@ export default class Balances {
}
this._accountsInfo = accountsInfo;
this._retrieveBalances();
})
.then((subscriptionId) => {
console.log('_subscribeAccountsInfo', 'subscriptionId', subscriptionId);
this._retrieveTokens();
})
.catch((error) => {
console.warn('_subscribeAccountsInfo', error);
@ -62,139 +81,269 @@ export default class Balances {
this._api
.subscribe('eth_blockNumber', (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();
})
.then((subscriptionId) => {
console.log('_subscribeBlockNumber', 'subscriptionId', subscriptionId);
})
.catch((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 () {
this._api.parity
.registryAddress()
.then((registryAddress) => {
const registry = this._api.newContract(abis.registry, registryAddress);
if (this._fetchingTokens) {
return;
}
return registry.instance.getAddress.call({}, [this._api.util.sha3('tokenreg'), 'A']);
})
.then((tokenregAddress) => {
const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress);
if (this._fetchedTokens) {
return this._retrieveBalances();
}
this._fetchingTokens = true;
this._fetchedTokens = false;
this
.getTokenRegistry()
.then((tokenreg) => {
return tokenreg.instance.tokenCount
.call()
.then((numTokens) => {
const promisesTokens = [];
const promisesImages = [];
const promises = [];
while (promisesTokens.length < numTokens.toNumber()) {
const index = promisesTokens.length;
promisesTokens.push(tokenreg.instance.token.call({}, [index]));
promisesImages.push(tokenreg.instance.meta.call({}, [index, 'IMG']));
for (let i = 0; i < numTokens.toNumber(); i++) {
promises.push(this.fetchTokenInfo(tokenreg, i));
}
return Promise.all([
Promise.all(promisesTokens),
Promise.all(promisesImages)
]);
return Promise.all(promises);
});
})
.then(([_tokens, images]) => {
const tokens = {};
this._tokens = _tokens
.map((_token, index) => {
const [address, tag, format, name] = _token;
.then(() => {
this._fetchingTokens = false;
this._fetchedTokens = true;
const token = {
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._store.dispatch(getTokens(this._tokens));
this._retrieveBalances();
})
.catch((error) => {
console.warn('_retrieveTokens', error);
this._retrieveBalances();
console.warn('balances::_retrieveTokens', error);
});
}
_retrieveBalances () {
if (this._fetchingBalances) {
return;
}
if (!this._accountsInfo) {
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 = {};
Promise
.all(
addresses.map((address) => Promise.all([
this._api.eth.getBalance(address),
this._api.eth.getTransactionCount(address)
]))
)
.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]
});
});
.all(addresses.map((a) => this.fetchAccountBalance(a)))
.then((balances) => {
addresses.forEach((a, idx) => {
this._balances[a] = balances[idx];
});
this._store.dispatch(getBalances(this._balances));
this._fetchingBalances = false;
})
.catch((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
*/
_shouldPing = () => {
const { isConnected, isConnecting } = this._apiStatus;
return isConnecting || !isConnected;
const { isConnected } = this._apiStatus;
return !isConnected;
}
_stopPollPing = () => {
@ -119,7 +119,7 @@ export default class Status {
_pollStatus = () => {
const nextTimeout = (timeout = 1000) => {
setTimeout(this._pollStatus, timeout);
setTimeout(() => this._pollStatus(), timeout);
};
const { isConnected, isConnecting, needsToken, secureToken } = this._api;
@ -134,7 +134,8 @@ export default class Status {
const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected;
if (gotReconnected) {
this._pollLongStatus();
this._pollLongStatus(true);
this._store.dispatch(statusCollection({ isPingable: true }));
}
if (!isEqual(apiStatus, this._apiStatus)) {
@ -155,37 +156,32 @@ export default class Status {
const { refreshStatus } = this._store.getState().nodeStatus;
const statusPromises = [ this._api.eth.syncing() ];
const statusPromises = [ this._api.eth.syncing(), this._api.parity.netPeers() ];
if (refreshStatus) {
statusPromises.push(this._api.eth.hashrate());
statusPromises.push(this._api.parity.netPeers());
}
Promise
.all(statusPromises)
.then((statusResults) => {
const status = statusResults.length === 1
? {
syncing: statusResults[0]
}
.then(([ syncing, netPeers, ...statusResults ]) => {
const status = statusResults.length === 0
? { syncing, netPeers }
: {
syncing: statusResults[0],
hashrate: statusResults[1],
netPeers: statusResults[2]
syncing, netPeers,
hashrate: statusResults[0]
};
if (!isEqual(status, this._status)) {
this._store.dispatch(statusCollection(status));
this._status = status;
}
nextTimeout();
})
.catch((error) => {
console.error('_pollStatus', error);
nextTimeout(250);
});
nextTimeout();
}
/**
@ -227,7 +223,11 @@ export default class Status {
* fetched every 30s just in case, and whenever
* the client got reconnected.
*/
_pollLongStatus = () => {
_pollLongStatus = (newConnection = false) => {
if (!this._api.isConnected) {
return;
}
const nextTimeout = (timeout = 30000) => {
if (this._longStatusTimeoutId) {
clearTimeout(this._longStatusTimeoutId);
@ -242,16 +242,19 @@ export default class Status {
Promise
.all([
this._api.web3.clientVersion(),
this._api.net.version(),
this._api.parity.defaultExtraData(),
this._api.parity.netChain(),
this._api.parity.netPort(),
this._api.parity.rpcSettings(),
this._api.parity.enode()
newConnection ? Promise.resolve(null) : this._api.parity.enode()
])
.then(([
clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
clientVersion, netVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
]) => {
const isTest = netChain === 'morden' || netChain === 'testnet';
const isTest =
netVersion === '2' || // morden
netVersion === '3'; // ropsten
const longStatus = {
clientVersion,
@ -259,21 +262,23 @@ export default class Status {
netChain,
netPort,
rpcSettings,
enode,
isTest
};
if (enode) {
longStatus.enode = enode;
}
if (!isEqual(longStatus, this._longStatus)) {
this._store.dispatch(statusCollection(longStatus));
this._longStatus = longStatus;
}
nextTimeout();
})
.catch((error) => {
console.error('_pollLongStatus', error);
nextTimeout(250);
});
nextTimeout(newConnection ? 5000 : 30000);
}
_pollLogs = () => {

View File

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

View File

@ -25,12 +25,13 @@ export default class SecureApi extends Api {
this._isConnecting = true;
this._connectState = sysuiToken === 'initial' ? 1 : 0;
this._needsToken = false;
this._nextToken = nextToken;
this._dappsPort = 8080;
this._dappsInterface = null;
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();
}
@ -40,15 +41,30 @@ export default class SecureApi extends Api {
console.log('SecureApi:setToken', this._transport.token);
}
_checkNodeUp () {
return fetch('/', { method: 'HEAD' })
.then(
(r) => r.status === 200,
() => false
)
.catch(() => false);
}
_followConnection = () => {
const nextTick = () => {
setTimeout(() => this._followConnection(), 250);
if (this._followConnectionTimeoutId) {
clearTimeout(this._followConnectionTimeoutId);
}
this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250);
};
const setManual = () => {
this._connectState = 100;
this._needsToken = true;
this._isConnecting = false;
};
const lastError = this._transport.lastError;
const isConnected = this._transport.isConnected;
@ -58,12 +74,24 @@ export default class SecureApi extends Api {
if (isConnected) {
return this.connectSuccess();
} else if (lastError) {
const nextToken = this._nextToken || 'initial';
const nextState = this._nextToken ? 0 : 1;
return this
._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
if (isNodeUp) {
this._tokensToTry = this._tokensToTry.slice(1);
}
if (nextToken !== this._transport.token) {
this.updateToken(nextToken, nextState);
}
nextTick();
});
}
break;
// token = 'initial'

View File

@ -18,7 +18,7 @@
.container {
display: flex;
flex-direction: row;
align-items: flex-end;
align-items: baseline;
position: relative;
}

View File

@ -144,35 +144,17 @@ export default class Input extends Component {
}
renderCopyButton () {
const { allowCopy, label, hint, floatCopy } = this.props;
const { allowCopy, hideUnderline } = this.props;
const { value } = this.state;
if (!allowCopy) {
return null;
}
const style = {
marginBottom: 13
};
const text = typeof allowCopy === 'string'
? allowCopy
: value;
if (!label) {
style.marginBottom = 2;
} else if (label && !hint) {
style.marginBottom = 4;
} else if (label && hint) {
style.marginBottom = 10;
}
if (floatCopy) {
style.position = 'absolute';
style.left = -24;
style.bottom = style.marginBottom;
style.marginBottom = 0;
}
const style = hideUnderline ? {} : { position: 'relative', top: '2px' };
return (
<div className={ styles.copy } style={ style }>

View File

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

View File

@ -16,26 +16,17 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ParityBackground extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static propTypes = {
style: PropTypes.object.isRequired,
children: PropTypes.node,
className: PropTypes.string,
gradient: PropTypes.string,
seed: PropTypes.any,
settings: PropTypes.object.isRequired,
onClick: PropTypes.func
}
};
render () {
const { muiTheme } = this.context;
const { children, className, gradient, seed, settings, onClick } = this.props;
const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed);
const { children, className, style, onClick } = this.props;
return (
<div
@ -48,17 +39,29 @@ class ParityBackground extends Component {
}
}
function mapStateToProps (state) {
const { settings } = state;
function mapStateToProps (_, initProps) {
const { gradient, seed, muiTheme } = initProps;
return { settings };
let _seed = seed;
let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) };
return (state, props) => {
const { backgroundSeed } = state.settings;
const { seed } = props;
const newSeed = seed || backgroundSeed;
if (newSeed === _seed) {
return _props;
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
_seed = newSeed;
_props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) };
return _props;
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps
)(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 { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionDelete from 'material-ui/svg-icons/action/delete';
import ContentCreate from 'material-ui/svg-icons/content/create';
import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock';
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 shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
@ -50,6 +51,7 @@ class Account extends Component {
propName = null
state = {
showDeleteDialog: false,
showEditDialog: false,
showFundDialog: false,
showVerificationDialog: false,
@ -62,8 +64,8 @@ class Account extends Component {
const { api } = this.context;
const { address } = this.props.params;
const store = new VerificationStore(api, address);
this.setState({ verificationStore: store });
const verificationStore = new VerificationStore(api, address);
this.setState({ verificationStore });
}
render () {
@ -79,6 +81,7 @@ class Account extends Component {
return (
<div className={ styles.account }>
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) }
{ this.renderFundDialog() }
{ this.renderVerificationDialog() }
@ -131,7 +134,12 @@ class Account extends Component {
key='passwordManager'
icon={ <LockIcon /> }
label='password'
onClick={ this.onPasswordClick } />
onClick={ this.onPasswordClick } />,
<Button
key='delete'
icon={ <ActionDelete /> }
label='delete account'
onClick={ this.onDeleteClick } />
];
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) {
const { showEditDialog } = this.state;
@ -228,6 +250,14 @@ class Account extends Component {
);
}
onDeleteClick = () => {
this.setState({ showDeleteDialog: true });
}
onDeleteClose = () => {
this.setState({ showDeleteDialog: false });
}
onEditClick = () => {
this.setState({
showEditDialog: !this.state.showEditDialog

View File

@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { isEqual } from 'lodash';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
@ -30,7 +31,6 @@ export default class Summary extends Component {
link: PropTypes.string,
name: PropTypes.string,
noLink: PropTypes.bool,
children: PropTypes.node,
handleAddSearchToken: PropTypes.func
};
@ -42,8 +42,42 @@ export default class Summary extends Component {
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 () {
const { account, children, handleAddSearchToken } = this.props;
const { account, handleAddSearchToken } = this.props;
const { tags } = account.meta;
if (!account) {
@ -71,7 +105,6 @@ export default class Summary extends Component {
byline={ addressComponent } />
{ this.renderBalance() }
{ children }
</Container>
);
}

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