Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74b850e7fc | ||
|
|
e4e25b771e | ||
|
|
7fc3f4eda0 | ||
|
|
b20ffbde8e | ||
|
|
3fe38163f5 | ||
|
|
0d18436a3d | ||
|
|
9ebfb14bb5 | ||
|
|
82e33fa033 | ||
|
|
fa8eb22e79 | ||
|
|
55b51890dd | ||
|
|
0f99b1e123 | ||
|
|
51e4c82337 | ||
|
|
7df702494d | ||
|
|
b5219bc723 | ||
|
|
27765a71dd | ||
|
|
a1b9f03121 | ||
|
|
1c40421982 | ||
|
|
07324795f1 | ||
|
|
19520442c1 | ||
|
|
e0c5baece0 | ||
|
|
f4f7b83d1f | ||
|
|
30d7872226 | ||
|
|
4d4821c577 | ||
|
|
948a5387b3 | ||
|
|
8a050e4464 | ||
|
|
af998d8c40 | ||
|
|
591673821c | ||
|
|
ce5dfd8e34 | ||
|
|
8c049e5d05 | ||
|
|
2bfcfd3813 | ||
|
|
4f869a9ba5 | ||
|
|
dd0b2c2327 | ||
|
|
889f85ea0d | ||
|
|
42eec0f6bd | ||
|
|
449e85743d | ||
|
|
8764563222 | ||
|
|
5b31af4e09 | ||
|
|
b648bd403a | ||
|
|
0994b91af5 | ||
|
|
2e595d60f0 | ||
|
|
c2ceefbc22 | ||
|
|
ab2d02bbb6 | ||
|
|
43bb862adb | ||
|
|
172b40ae30 | ||
|
|
74e859a9d4 | ||
|
|
1fdbc07cb4 | ||
|
|
9912a3158c | ||
|
|
fa6aceca42 | ||
|
|
503137a968 | ||
|
|
184648a734 | ||
|
|
fb817fcdca | ||
|
|
f76b94c2c5 | ||
|
|
78afa8ed54 | ||
|
|
63bf2755ed | ||
|
|
f187b15a43 | ||
|
|
e9b1921950 | ||
|
|
3106835ae3 | ||
|
|
c7dbd87f8e | ||
|
|
b09cfe1885 | ||
|
|
070c40c02f | ||
|
|
33ecf1313f | ||
|
|
971a481b0d | ||
|
|
2b132c38d6 | ||
|
|
dcd9119452 | ||
|
|
f3f25ad50c | ||
|
|
f3c4ea4fb9 | ||
|
|
1b79fc3e27 | ||
|
|
ce824089ac | ||
|
|
d2e6fc0a65 | ||
|
|
29d50f8f30 | ||
|
|
536cf73db8 | ||
|
|
f15a3ca977 | ||
|
|
096a44d9bf | ||
|
|
ff3ae33d99 | ||
|
|
8e6d09e6ac | ||
|
|
516e16a36b | ||
|
|
77b14b2736 | ||
|
|
e01636fc34 | ||
|
|
7b2cfd1cfb | ||
|
|
cf6d870b09 | ||
|
|
f20db41169 | ||
|
|
6830273cb9 | ||
|
|
65594b8865 | ||
|
|
5b30a61158 | ||
|
|
0bc2aeca8d | ||
|
|
1478109c43 | ||
|
|
bbd2bd0e17 | ||
|
|
1e212771b5 | ||
|
|
72bb687f5e | ||
|
|
5e70507c78 | ||
|
|
2b588d57f0 | ||
|
|
e7fbf10819 |
@@ -1,22 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
target
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
*.swn
|
||||
*.DS_Store
|
||||
|
||||
# Visual Studio Code stuff
|
||||
.vscode
|
||||
|
||||
# GitEye stuff
|
||||
.project
|
||||
|
||||
# idea ide
|
||||
.idea
|
||||
|
||||
# git stuff
|
||||
.git
|
||||
|
||||
ethcore/res/ethereum/tests
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,10 +19,6 @@
|
||||
# mac stuff
|
||||
.DS_Store
|
||||
|
||||
# npm stuff
|
||||
npm-debug.log
|
||||
node_modules
|
||||
|
||||
# gdb files
|
||||
.gdb_history
|
||||
|
||||
|
||||
285
.gitlab-ci.yml
285
.gitlab-ci.yml
@@ -9,41 +9,30 @@ variables:
|
||||
RUST_BACKTRACE: "1"
|
||||
RUSTFLAGS: ""
|
||||
CARGOFLAGS: ""
|
||||
CI_SERVER_NAME: "GitLab CI"
|
||||
cache:
|
||||
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:stable
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- rustup default stable
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --release -p ethkey-cli
|
||||
- strip target/release/parity
|
||||
- strip target/release/parity-evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- 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|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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
|
||||
@@ -57,48 +46,10 @@ linux-stable:
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
- target/release/parity-evm
|
||||
- target/release/ethstore
|
||||
- target/release/ethkey
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-snap:
|
||||
stage: build
|
||||
image: parity/snapcraft:gitlab-ci
|
||||
only:
|
||||
- snap
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- cd snap
|
||||
- rm -rf *snap
|
||||
- sed -i 's/master/'"$VER"'/g' snapcraft.yaml
|
||||
- echo "Version:"$VER
|
||||
- snapcraft
|
||||
- ls
|
||||
#- cp "parity_"$CI_BUILD"_amd64.snap" "parity_"$VER"_amd64.snap"
|
||||
- md5sum "parity_"$VER"_amd64.snap" > "parity_"$VER"_amd64.snap.md5"
|
||||
- file "parity_"$VER"_amd64.snap.md5"
|
||||
- cat "parity_"$VER"_amd64.snap.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|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.snap" --body "parity_"$VER"_amd64.snap"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.snap.md5" --body "parity_"$VER"_amd64.snap.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
artifacts:
|
||||
paths:
|
||||
- scripts/parity_*_amd64.snap
|
||||
name: "stable-x86_64-unknown-snap-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
image: parity/rust-debian:gitlab-ci
|
||||
image: ethcore/rust-debian:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -106,26 +57,17 @@ linux-stable-debian:
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --release -p ethkey-cli
|
||||
- strip target/release/parity
|
||||
- strip target/release/parity-evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- 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|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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-debian-gnu
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity.md5 --body parity.md5
|
||||
@@ -142,14 +84,13 @@ linux-stable-debian:
|
||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
||||
linux-beta:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:beta
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- rustup default beta
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
@@ -162,14 +103,13 @@ linux-beta:
|
||||
allow_failure: true
|
||||
linux-nightly:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:nightly
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- rustup default nightly
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
@@ -182,7 +122,7 @@ linux-nightly:
|
||||
allow_failure: true
|
||||
linux-centos:
|
||||
stage: build
|
||||
image: parity/rust-centos:gitlab-ci
|
||||
image: ethcore/rust-centos:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -193,30 +133,15 @@ linux-centos:
|
||||
- export CC="gcc"
|
||||
- export PLATFORM=x86_64-unknown-centos-gnu
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --release -p ethkey-cli
|
||||
- strip target/release/parity
|
||||
- strip target/release/parity-evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- md5sum target/release/parity-evm > parity-evm.md5
|
||||
- md5sum target/release/ethstore > ethstore.md5
|
||||
- md5sum target/release/ethkey > ethkey.md5
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- 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|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm --body target/release/parity-evm
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm.md5 --body parity-evm.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore --body target/release/ethstore
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore.md5 --body ethstore.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey --body target/release/ethkey
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey.md5 --body ethkey.md5
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
@@ -228,7 +153,7 @@ linux-centos:
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: parity/rust-i686:gitlab-ci
|
||||
image: ethcore/rust-i686:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -239,28 +164,18 @@ linux-i686:
|
||||
- export HOST_CXX=g++
|
||||
- export COMMIT=$(git rev-parse HEAD)
|
||||
- export PLATFORM=i686-unknown-linux-gnu
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
|
||||
- strip target/$PLATFORM/release/parity
|
||||
- strip target/$PLATFORM/release/parity-evm
|
||||
- strip target/$PLATFORM/release/ethstore
|
||||
- strip target/$PLATFORM/release/ethkey
|
||||
- cargo build -j $(nproc) --target i686-unknown-linux-gnu --features final --release $CARGOFLAGS
|
||||
- strip target/$PLATFORM/release/parity
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
|
||||
- sh scripts/deb-build.sh i386
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
|
||||
- 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
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
@@ -278,7 +193,7 @@ linux-i686:
|
||||
allow_failure: true
|
||||
linux-armv7:
|
||||
stage: build
|
||||
image: parity/rust-armv7:gitlab-ci
|
||||
image: ethcore/rust-armv7:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -296,29 +211,17 @@ linux-armv7:
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
|
||||
- sh scripts/deb-build.sh i386
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
|
||||
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- 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
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
@@ -336,7 +239,7 @@ linux-armv7:
|
||||
allow_failure: true
|
||||
linux-arm:
|
||||
stage: build
|
||||
image: parity/rust-arm:gitlab-ci
|
||||
image: ethcore/rust-arm:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -354,26 +257,17 @@ linux-arm:
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
|
||||
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- 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
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
@@ -389,9 +283,48 @@ linux-arm:
|
||||
- target/arm-unknown-linux-gnueabihf/release/parity
|
||||
name: "arm-unknown-linux-gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-armv6:
|
||||
stage: build
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
# - beta
|
||||
# - tags
|
||||
# - stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabi-gcc
|
||||
- export CXX=arm-linux-gnueabi-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=arm-unknown-linux-gnueabi
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- arm-linux-gnueabi-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/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/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/arm-unknown-linux-gnueabi/release/parity
|
||||
name: "arm-unknown-linux-gnueabi_parity"
|
||||
allow_failure: true
|
||||
linux-aarch64:
|
||||
stage: build
|
||||
image: parity/rust-arm64:gitlab-ci
|
||||
image: ethcore/rust-aarch64:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
@@ -409,26 +342,17 @@ linux-aarch64:
|
||||
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity-evm
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethstore
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethkey
|
||||
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh arm64
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_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|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- 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/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
|
||||
@@ -453,31 +377,24 @@ darwin:
|
||||
script: |
|
||||
export COMMIT=$(git rev-parse HEAD)
|
||||
export PLATFORM=x86_64-apple-darwin
|
||||
rustup default stable
|
||||
cargo clean
|
||||
cargo build -j 8 --features final --release #$CARGOFLAGS
|
||||
cargo build -j 8 --release -p ethstore-cli #$CARGOFLAGS
|
||||
cargo build -j 8 --release -p ethkey-cli #$CARGOFLAGS
|
||||
cargo build -j 8 --release -p evmbin #$CARGOFLAGS
|
||||
cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS
|
||||
cargo build -j 8 --features final --release -p evmbin #$CARGOFLAGS
|
||||
rm -rf parity.md5
|
||||
md5sum target/release/parity > parity.md5
|
||||
export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
cd mac
|
||||
xcodebuild -configuration Release
|
||||
cd ..
|
||||
packagesbuild -v mac/Parity.pkgproj
|
||||
productsign --sign 'Developer ID Installer: PARITY TECHNOLOGIES LIMITED (P2PX3JU8FT)' target/release/Parity\ Ethereum.pkg target/release/Parity\ Ethereum-signed.pkg
|
||||
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-macos-installer.pkg"
|
||||
md5sum "parity-"$VER"-macos-installer.pkg" >> "parity-"$VER"-macos-installer.pkg.md5"
|
||||
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
|
||||
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
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/$PLATFORM
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-macos-installer.pkg" --body "parity-"$VER"-macos-installer.pkg"
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-macos-installer.pkg.md5" --body "parity-"$VER"-macos-installer.pkg.md5"
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"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/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
@@ -502,17 +419,13 @@ windows:
|
||||
- set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64
|
||||
- set RUST_BACKTRACE=1
|
||||
- set RUSTFLAGS=%RUSTFLAGS%
|
||||
- rustup default stable-x86_64-pc-windows-msvc
|
||||
- cargo clean
|
||||
- rustup default 1.14.0-x86_64-pc-windows-msvc
|
||||
- cargo build --features final --release #%CARGOFLAGS%
|
||||
- cargo build --release -p ethstore-cli #%CARGOFLAGS%
|
||||
- cargo build --release -p ethkey-cli #%CARGOFLAGS%
|
||||
- cargo build --release -p evmbin #%CARGOFLAGS%
|
||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||
- target\release\parity.exe tools hash target\release\parity.exe > parity.sha3
|
||||
- set /P SHA3=<parity.sha3
|
||||
- curl -sL --url "https://github.com/paritytech/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
||||
- curl -sL --url "https://github.com/paritytech/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
|
||||
- cd nsis
|
||||
@@ -523,17 +436,17 @@ windows:
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
- md5sums win-installer.zip > win-installer.zip.md5
|
||||
- cd ..\target\release\
|
||||
- md5sums parity.exe parity.pdb > parity.md5
|
||||
- md5sums parity.exe > parity.exe.md5
|
||||
- zip parity.zip parity.exe parity.md5
|
||||
- zip parity.zip parity.exe parity.pdb parity.md5
|
||||
- md5sums parity.zip > parity.zip.md5
|
||||
- cd ..\..
|
||||
- aws configure set aws_access_key_id %s3_key%
|
||||
- aws configure set aws_secret_access_key %s3_secret%
|
||||
- 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 %CI_BUILD_REF_NAME% | findstr /R "nightly" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
|
||||
- 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
|
||||
@@ -558,38 +471,22 @@ docker-build:
|
||||
stage: build
|
||||
only:
|
||||
- tags
|
||||
- triggers
|
||||
before_script:
|
||||
- docker info
|
||||
script:
|
||||
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
|
||||
- echo "Tag:" $DOCKER_TAG
|
||||
- docker login -u $Docker_Hub_User_Parity -p $Docker_Hub_Pass_Parity
|
||||
- sh scripts/docker-build.sh $DOCKER_TAG
|
||||
- docker logout
|
||||
- cd docker/hub
|
||||
- if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
|
||||
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
|
||||
- scripts/docker-build.sh $DOCKER_TAG
|
||||
tags:
|
||||
- docker
|
||||
test-coverage:
|
||||
stage: test
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- git submodule update --init --recursive
|
||||
- rm -rf target/*
|
||||
- rm -rf js/.coverage
|
||||
- scripts/cov.sh
|
||||
# - COVERAGE=$(grep -Po 'covered":.*?[^\\]"' target/cov/index.json | grep "[0-9]*\.[0-9]" -o)
|
||||
# - echo "Coverage:" $COVERAGE
|
||||
tags:
|
||||
- kcov
|
||||
allow_failure: true
|
||||
test-darwin:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e "^js/" -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
@@ -604,18 +501,17 @@ test-windows:
|
||||
- git submodule update --init --recursive
|
||||
script:
|
||||
- set RUST_BACKTRACE=1
|
||||
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p parity-dapps -p parity-rpc -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity-rpc-client -p parity %CARGOFLAGS% --verbose --release
|
||||
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p evmbin -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
|
||||
tags:
|
||||
- rust-windows
|
||||
allow_failure: true
|
||||
test-rust-stable:
|
||||
stage: test
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- rustup show
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
@@ -623,7 +519,7 @@ test-rust-stable:
|
||||
- rust-stable
|
||||
js-test:
|
||||
stage: test
|
||||
image: parity/rust:gitlab-ci
|
||||
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 | grep ^js/ | wc -l)
|
||||
@@ -637,13 +533,11 @@ test-rust-beta:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
- master
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:beta
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- rustup default beta
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
@@ -654,13 +548,11 @@ test-rust-nightly:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
- master
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:nightly
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- rustup default nightly
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
@@ -673,15 +565,12 @@ js-release:
|
||||
- master
|
||||
- beta
|
||||
- stable
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- 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 -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||
script:
|
||||
- rustup default stable
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
|
||||
tags:
|
||||
@@ -690,10 +579,8 @@ push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:stable
|
||||
script:
|
||||
- rustup default stable
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
tags:
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -2,6 +2,3 @@
|
||||
path = ethcore/res/ethereum/tests
|
||||
url = https://github.com/ethereum/tests.git
|
||||
branch = develop
|
||||
[submodule "ethcore/res/wasm-tests"]
|
||||
path = ethcore/res/wasm-tests
|
||||
url = https://github.com/paritytech/wasm-tests
|
||||
|
||||
85
.travis.yml
Normal file
85
.travis.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: rust
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^beta-.*$/
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
env: RUN_TESTS="true" TEST_OPTIONS=""
|
||||
- rust: stable
|
||||
env: RUN_COVERAGE="true"
|
||||
- rust: stable
|
||||
env: RUN_DOCS="true"
|
||||
|
||||
env:
|
||||
global:
|
||||
- CXX="g++-4.8"
|
||||
- CC="gcc-4.8"
|
||||
- RUST_BACKTRACE="1"
|
||||
- RUN_TESTS="false"
|
||||
- RUN_COVERAGE="false"
|
||||
- RUN_DOCS="false"
|
||||
- TEST_OPTIONS=""
|
||||
- RUSTFLAGS="-D warnings"
|
||||
- TRAVIS_NODE_VERSION="6"
|
||||
# GH_TOKEN for documentation
|
||||
- secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw=
|
||||
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov"
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- $TRAVIS_BUILD_DIR/target
|
||||
- $TRAVIS_BUILD_DIR/kcov-master
|
||||
- $TRAVIS_BUILD_DIR/js/node_modules
|
||||
- $HOME/.cargo
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
- gcc-4.8
|
||||
- g++-4.8
|
||||
|
||||
install:
|
||||
- ([ "$RUN_COVERAGE" = "false" ]) || (test -x $KCOV_CMD) || (
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
tar xzf master.tar.gz &&
|
||||
mkdir -p kcov-master/build &&
|
||||
cd kcov-master/build &&
|
||||
cmake .. &&
|
||||
make && make install DESTDIR=../tmp &&
|
||||
cd
|
||||
)
|
||||
- nvm install $TRAVIS_NODE_VERSION && nvm use $TRAVIS_NODE_VERSION && ./js/scripts/install-deps.sh
|
||||
|
||||
script:
|
||||
- if [ "$RUN_TESTS" = "true" ]; then
|
||||
./js/scripts/lint.sh &&
|
||||
travis_wait 40 ./test.sh $TEST_OPTIONS;
|
||||
fi
|
||||
- if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi
|
||||
|
||||
after_success: |
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
[ "$RUN_DOCS" = "true" ] &&
|
||||
./scripts/doc.sh &&
|
||||
pip install --user ghp-import &&
|
||||
/home/travis/.local/bin/ghp-import -n target/doc &&
|
||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
||||
4394
CHANGELOG.md
4394
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
2608
Cargo.lock
generated
2608
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
91
Cargo.toml
91
Cargo.toml
@@ -1,69 +1,59 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
name = "parity"
|
||||
version = "1.7.2"
|
||||
version = "1.5.4"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.1"
|
||||
ethcore-ipc-codegen = { path = "ipc/codegen" }
|
||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
env_logger = "0.4"
|
||||
rustc-hex = "1.0"
|
||||
docopt = "0.8"
|
||||
env_logger = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
docopt = "0.6"
|
||||
time = "0.1"
|
||||
num_cpus = "1.2"
|
||||
num_cpus = "0.2"
|
||||
number_prefix = "0.2"
|
||||
rpassword = "0.2.1"
|
||||
semver = "0.6"
|
||||
ansi_term = "0.9"
|
||||
regex = "0.2"
|
||||
semver = "0.5"
|
||||
ansi_term = "0.7"
|
||||
lazy_static = "0.2"
|
||||
regex = "0.1"
|
||||
isatty = "0.1"
|
||||
toml = "0.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
toml = "0.2"
|
||||
serde = "0.8.0"
|
||||
serde_json = "0.8.0"
|
||||
app_dirs = "1.1.1"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
hyper = { version = "0.9", default-features = false }
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
fdlimit = "0.1"
|
||||
ws2_32-sys = "0.2"
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
rlp = { path = "util/rlp" }
|
||||
ethsync = { path = "sync" }
|
||||
ethcore = { path = "ethcore" }
|
||||
ethcore-util = { path = "util" }
|
||||
ethcore-io = { path = "util/io" }
|
||||
ethcore-devtools = { path = "devtools" }
|
||||
ethcore-rpc = { path = "rpc" }
|
||||
ethcore-signer = { path = "signer" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
||||
ethcore-light = { path = "ethcore/light" }
|
||||
ethcore-logger = { path = "logger" }
|
||||
ethcore-stratum = { path = "stratum" }
|
||||
ethkey = { path = "ethkey" }
|
||||
node-health = { path = "dapps/node-health" }
|
||||
rlp = { path = "util/rlp" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
evmbin = { path = "evmbin" }
|
||||
rpc-cli = { path = "rpc_cli" }
|
||||
parity-hash-fetch = { path = "hash-fetch" }
|
||||
parity-ipfs-api = { path = "ipfs" }
|
||||
parity-local-store = { path = "local-store" }
|
||||
parity-reactor = { path = "util/reactor" }
|
||||
parity-rpc = { path = "rpc" }
|
||||
parity-rpc-client = { path = "rpc_client" }
|
||||
ethcore-light = { path = "ethcore/light" }
|
||||
parity-hash-fetch = { path = "hash-fetch" }
|
||||
parity-updater = { path = "updater" }
|
||||
path = { path = "util/path" }
|
||||
|
||||
parity-dapps = { path = "dapps", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
ethcore-secretstore = { path = "secret_store", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||
pretty_assertions = "0.1"
|
||||
parity-reactor = { path = "util/reactor" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
@@ -74,37 +64,34 @@ daemonize = "0.2"
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
ui = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui",
|
||||
"dapps",
|
||||
"ethcore-dapps/ui",
|
||||
"ethcore-signer/ui",
|
||||
]
|
||||
ui-precompiled = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui-precompiled",
|
||||
"dapps",
|
||||
"ethcore-signer/ui-precompiled",
|
||||
"ethcore-dapps/ui-precompiled",
|
||||
]
|
||||
ui-enabled = ["dapps"]
|
||||
dapps = ["parity-dapps"]
|
||||
dapps = ["ethcore-dapps"]
|
||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||
jit = ["ethcore/jit"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
|
||||
json-tests = ["ethcore/json-tests"]
|
||||
test-heavy = ["ethcore/test-heavy"]
|
||||
stratum = ["ipc"]
|
||||
ethkey-cli = ["ethcore/ethkey-cli"]
|
||||
ethstore-cli = ["ethcore/ethstore-cli"]
|
||||
evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
final = ["ethcore-util/final"]
|
||||
secretstore = ["ethcore-secretstore"]
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
name = "parity"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
lto = false
|
||||
panic = "abort"
|
||||
|
||||
[workspace]
|
||||
members = ["ethstore/cli", "ethkey/cli", "evmbin", "dapps/node-health"]
|
||||
|
||||
77
README.md
77
README.md
@@ -1,47 +1,60 @@
|
||||
# [Parity](https://parity.io/parity.html) - fast, light, and robust Ethereum client
|
||||
# [Parity](https://ethcore.io/parity.html)
|
||||
### Fast, light, and robust Ethereum implementation
|
||||
|
||||
[](https://gitlab.parity.io/parity/parity/commits/master)
|
||||
[](https://build.snapcraft.io/user/paritytech/parity)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
- [Download the latest release here.](https://github.com/paritytech/parity/releases)
|
||||
[![Build Status][travis-image]][travis-url] [](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
|
||||
|
||||
### Join the chat!
|
||||
|
||||
Get in touch with us on Gitter:
|
||||
[](https://gitter.im/paritytech/parity)
|
||||
[](https://gitter.im/paritytech/parity.js)
|
||||
[](https://gitter.im/paritytech/parity/miners)
|
||||
[](https://gitter.im/paritytech/parity-poa)
|
||||
Parity [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] and
|
||||
parity.js [](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Be sure to check out [our wiki](https://github.com/paritytech/parity/wiki) and the [internal documentation](https://paritytech.github.io/parity/ethcore/index.html) for more information.
|
||||
[Internal Documentation][doc-url]
|
||||
|
||||
|
||||
Be sure to check out [our wiki][wiki-url] for more information.
|
||||
|
||||
[travis-image]: https://travis-ci.org/ethcore/parity.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/ethcore/parity
|
||||
[coveralls-image]: https://coveralls.io/repos/github/ethcore/parity/badge.svg?branch=master
|
||||
[coveralls-url]: https://coveralls.io/github/ethcore/parity?branch=master
|
||||
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
|
||||
[gitter-url]: https://gitter.im/ethcore/parity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
[license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg
|
||||
[license-url]: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
[doc-url]: https://ethcore.github.io/parity/ethcore/index.html
|
||||
[wiki-url]: https://github.com/ethcore/parity/wiki
|
||||
|
||||
**Parity requires Rust version 1.12.0 to build**
|
||||
|
||||
----
|
||||
|
||||
|
||||
## About Parity
|
||||
|
||||
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
|
||||
|
||||
Parity comes with a built-in wallet. To access [Parity Wallet](http://web3.site/) simply go to http://web3.site/ (if you don't have access to the internet, but still want to use the service, you can also use http://127.0.0.1:8180/). It includes various functionality allowing you to:
|
||||
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and
|
||||
cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
|
||||
|
||||
Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It
|
||||
includes various functionality allowing you to:
|
||||
- create and manage your Ethereum accounts;
|
||||
- manage your Ether and any Ethereum tokens;
|
||||
- create and register your own tokens;
|
||||
- and much more.
|
||||
- and much more.
|
||||
|
||||
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number of RPC APIs.
|
||||
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
|
||||
of RPC APIs.
|
||||
|
||||
If you run into an issue while using parity, feel free to file one in this repository or hop on our [gitter chat room](https://gitter.im/paritytech/parity) to ask a question. We are glad to help!
|
||||
If you run into an issue while using parity, feel free to file one in this repository
|
||||
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
|
||||
|
||||
**For security-critical issues**, please refer to the security policy outlined in `SECURITY.MD`.
|
||||
|
||||
Parity's current beta-release is 1.7. You can download it at https://github.com/paritytech/parity/releases or follow the instructions below to build from source.
|
||||
Parity's current release is 1.3. You can download it at https://ethcore.io/parity.html or follow the instructions
|
||||
below to build from source.
|
||||
|
||||
----
|
||||
|
||||
## Build dependencies
|
||||
|
||||
**Parity 1.7.x-beta requires Rust version 1.19.0 to build**.
|
||||
Parity is fully compatible with Stable Rust.
|
||||
|
||||
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
|
||||
|
||||
@@ -49,8 +62,8 @@ We recommend installing Rust through [rustup](https://www.rustup.rs/). If you do
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl`, `libudev-dev` and `pkg-config` packages to be installed.
|
||||
|
||||
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl` and `pkg-config` packages to be installed.
|
||||
- OSX:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
@@ -69,23 +82,19 @@ Once you have rustup, install parity or download and build from source
|
||||
|
||||
----
|
||||
|
||||
## Install from the snap store
|
||||
|
||||
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):
|
||||
## Quick install
|
||||
|
||||
```bash
|
||||
sudo snap install parity --edge
|
||||
cargo install --git https://github.com/ethcore/parity.git parity
|
||||
```
|
||||
|
||||
(Note that this is an experimental and unstable release, at the moment)
|
||||
|
||||
----
|
||||
|
||||
## Build from source
|
||||
|
||||
```bash
|
||||
# download Parity code
|
||||
$ git clone https://github.com/paritytech/parity
|
||||
$ git clone https://github.com/ethcore/parity
|
||||
$ cd parity
|
||||
|
||||
# build in release mode
|
||||
@@ -93,11 +102,7 @@ $ cargo build --release
|
||||
```
|
||||
|
||||
This will produce an executable in the `./target/release` subdirectory.
|
||||
Note: if cargo fails to parse manifest try:
|
||||
|
||||
```bash
|
||||
$ ~/.cargo/bin/cargo build --release
|
||||
```
|
||||
----
|
||||
|
||||
## Simple one-line installer for Mac and Ubuntu
|
||||
@@ -123,4 +128,4 @@ systemd user directory (usually `~/.config/systemd/user`).
|
||||
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
|
||||
`ARGS="ARG1 ARG2 ARG3"`.
|
||||
|
||||
Example: `ARGS="ui --identity MyMachine"`.
|
||||
Example: `ARGS="ui --geth --identity MyMachine"`.
|
||||
|
||||
54
SECURITY.md
54
SECURITY.md
@@ -1,54 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
For security inquiries or vulnerability reports, please send a message to security@parity.io.
|
||||
|
||||
Please use a descriptive subject line so we can identify the report as such.
|
||||
|
||||
If you send a report, we will respond to the e-mail within 48 hours, and provide regular updates from that time onwards.
|
||||
|
||||
If you would like to encrypt your report, please use the PGP key provided below.
|
||||
It is also reproduced [on MIT's key server](https://pgp.mit.edu/pks/lookup?op=get&search=0x5D0F03018D07DE73)
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFlyIAwBCACe0keNPjgYzZ1Oy/8t3zj/Qw9bHHqrzx7FWy8NbXnYBM19NqOZ
|
||||
DIP7Oe0DvCaf/uruBskCS0iVstHlEFQ2AYe0Ei0REt9lQdy61GylU/DEB3879IG+
|
||||
6FO0SnFeYeerv1/hFI2K6uv8v7PyyVDiiJSW0I1KIs2OBwJicTKmWxLAeQsRgx9G
|
||||
yRGalrVk4KP+6pWTA7k3DxmDZKZyfYV/Ej10NtuzmsemwDbv98HKeomp/kgFOfSy
|
||||
3AZjeCpctlsNqpjUuXa0/HudmH2WLxZ0fz8XeoRh8XM9UudNIecjrDqmAFrt/btQ
|
||||
/3guvlzhFCdhYPVGsUusKMECk/JG+Xx1/1ZjABEBAAG0LFBhcml0eSBTZWN1cml0
|
||||
eSBDb250YWN0IDxzZWN1cml0eUBwYXJpdHkuaW8+iQFUBBMBCAA+FiEE2uUVYCjP
|
||||
N6B8aTiDXQ8DAY0H3nMFAllyIAwCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwEC
|
||||
HgECF4AACgkQXQ8DAY0H3nM60wgAkS3A36Zc+upiaxU7tumcGv+an17j7gin0sif
|
||||
+0ELSjVfrXInM6ovai+NhUdcLkJ7tCrKS90fvlaELK5Sg9CXBWCTFccKN4A/B7ey
|
||||
rOg2NPXUecnyBB/XqQgKYH7ujYlOlqBDXMfz6z8Hj6WToxg9PPMGGomyMGh8AWxM
|
||||
3yRPFs5RKt0VKgN++5N00oly5Y8ri5pgCidDvCLYMGTVDHFKwkuc9w6BlWlu1R1e
|
||||
/hXFWUFAP1ffTAul3QwyKhjPn2iotCdxXjvt48KaU8DN4iL7aMBN/ZBKqGS7yRdF
|
||||
D/JbJyaaJ0ZRvFSTSXy/sWY3z1B5mtCPBxco8hqqNfRkCwuZ6LkBDQRZciAMAQgA
|
||||
8BP8xrwe12TOUTqL/Vrbxv/FLdhKh53J6TrPKvC2TEEKOrTNo5ahRq+XOS5E7G2N
|
||||
x3b+fq8gR9BzFcldAx0XWUtGs/Wv++ulaSNqTBxj13J3G3WGsUfMKxRgj//piCUD
|
||||
bCFLQfGZdKk0M1o9QkPVARwwmvCNiNB/l++xGqPtfc44H5jWj3GoGvL2MkShPzrN
|
||||
yN/bJ+m+R5gtFGdInqa5KXBuxxuW25eDKJ+LzjbgUgeC76wNcfOiQHTdMkcupjdO
|
||||
bbGFwo10hcbRAOcZEv6//Zrlmk/6nPxEd2hN20St2bSN0+FqfZ267mWEu3ejsgF8
|
||||
ArdCpv5h4fBvJyNwiTZwIQARAQABiQE8BBgBCAAmFiEE2uUVYCjPN6B8aTiDXQ8D
|
||||
AY0H3nMFAllyIAwCGwwFCQPCZwAACgkQXQ8DAY0H3nNisggAl4fqhRlA34wIb190
|
||||
sqXHVxiCuzPaqS6krE9xAa1+gncX485OtcJNqnjugHm2rFE48lv7oasviuPXuInE
|
||||
/OgVFnXYv9d/Xx2JUeDs+bFTLouCDRY2Unh7KJZasfqnMcCHWcxHx5FvRNZRssaB
|
||||
WTZVo6sizPurGUtbpYe4/OLFhadBqAE0EUmVRFEUMc1YTnu4eLaRBzoWN4d2UWwi
|
||||
LN25RSrVSke7LTSFbgn9ntQrQ2smXSR+cdNkkfRCjFcpUaecvFl9HwIqoyVbT4Ym
|
||||
0hbpbbX/cJdc91tKa+psa29uMeGL/cgL9fAu19yNFRyOTMxjZnvql1X/WE1pLmoP
|
||||
ETBD1Q==
|
||||
=K9Qw
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
||||
Important Legal Information:
|
||||
|
||||
Your submission might be eligible for a bug bounty. The bug bounty program is an experimental and discretionary rewards program for the Parity community to reward those who are helping to improve the Parity software. Rewards are at the sole discretion of Parity Technologies Ltd..
|
||||
|
||||
We are not able to issue rewards to individuals who are on sanctions lists or who are in countries on sanctions lists (e.g. North Korea, Iran, etc).
|
||||
|
||||
You are responsible for all taxes. All rewards are subject to applicable law.
|
||||
|
||||
Finally, your testing must not violate any law or compromise any data that is not yours.
|
||||
52
appveyor.yml
Normal file
52
appveyor.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
cert:
|
||||
secure: ESPpYVVAMG1fbJx6kq4ct/g9SQTXac4Hs6xXr6Oh4Zrk2dwYglNjxmzErdPnvu7gs/gekzrJ6KEQHYRc+5+4dKg6rRADQ681NLVx9vOggBs=
|
||||
certpass:
|
||||
secure: 0BgXJqxq9Ei34/hZ7121FQ==
|
||||
keyfile: C:\users\appveyor\Certificates.p12
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^beta-.*$/
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- ps: Install-Product node 6
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.13.0-x86_64-pc-windows-msvc.exe"
|
||||
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -FileName nsis\SimpleFC.dll
|
||||
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -FileName nsis\vc_redist.x64.exe
|
||||
- rust-1.13.0-x86_64-pc-windows-msvc.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin;C:\Program Files (x86)\NSIS;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
- node -v
|
||||
- npm -v
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose --release
|
||||
|
||||
after_test:
|
||||
- cargo build --verbose --release
|
||||
- ps: if($env:cert) { Start-FileDownload $env:cert -FileName $env:keyfile }
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass target\release\parity.exe }
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\x64\release\ptray.exe }
|
||||
- makensis.exe nsis\installer.nsi
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass nsis\installer.exe }
|
||||
|
||||
artifacts:
|
||||
- path: nsis\installer.exe
|
||||
name: Windows Installer (x86_64)
|
||||
|
||||
cache:
|
||||
- target
|
||||
- C:\users\appveyor\.cargo -> appveyor.yml
|
||||
20
build.rs
20
build.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,20 +16,10 @@
|
||||
|
||||
extern crate rustc_version;
|
||||
|
||||
const MIN_RUSTC_VERSION: &'static str = "1.15.1";
|
||||
use rustc_version::{version_meta, Channel};
|
||||
|
||||
fn main() {
|
||||
let is = rustc_version::version().unwrap();
|
||||
let required = MIN_RUSTC_VERSION.parse().unwrap();
|
||||
assert!(is >= required, format!("
|
||||
|
||||
It looks like you are compiling Parity with an old rustc compiler {}.
|
||||
Parity requires version {}. Please update your compiler.
|
||||
If you use rustup, try this:
|
||||
|
||||
rustup update stable
|
||||
|
||||
and try building Parity again.
|
||||
|
||||
", is, required));
|
||||
if let Channel::Nightly = version_meta().channel {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
[package]
|
||||
description = "Parity Dapps crate"
|
||||
name = "parity-dapps"
|
||||
version = "1.7.0"
|
||||
name = "ethcore-dapps"
|
||||
version = "1.5.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
base32 = "0.3"
|
||||
futures = "0.1"
|
||||
linked-hash-map = "0.3"
|
||||
log = "0.3"
|
||||
parity-dapps-glue = "1.7"
|
||||
mime = "0.2"
|
||||
mime_guess = "1.6.1"
|
||||
rand = "0.3"
|
||||
rustc-hex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1.35"
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
futures = "0.1"
|
||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||
unicase = "1.3"
|
||||
url = "1.0"
|
||||
rustc-serialize = "0.3"
|
||||
serde = "0.8"
|
||||
serde_json = "0.8"
|
||||
linked-hash-map = "0.3"
|
||||
parity-dapps-glue = "1.4"
|
||||
mime = "0.2"
|
||||
mime_guess = "1.6.1"
|
||||
time = "0.1.35"
|
||||
serde_macros = { version = "0.8", optional = true }
|
||||
zip = { version = "0.1", default-features = false }
|
||||
|
||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
|
||||
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-rpc = { path = "../rpc" }
|
||||
ethcore-util = { path = "../util" }
|
||||
fetch = { path = "../util/fetch" }
|
||||
node-health = { path = "./node-health" }
|
||||
parity-ui = { path = "./ui" }
|
||||
parity-hash-fetch = { path = "../hash-fetch" }
|
||||
parity-reactor = { path = "../util/reactor" }
|
||||
parity-ui = { path = "./ui" }
|
||||
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.8", optional = true }
|
||||
|
||||
[features]
|
||||
dev = ["clippy", "ethcore-util/dev"]
|
||||
default = ["serde_codegen"]
|
||||
nightly = ["serde_macros"]
|
||||
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
|
||||
|
||||
ui = ["parity-ui/no-precompiled-js"]
|
||||
ui-precompiled = ["parity-ui/use-precompiled-js"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,19 +14,28 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
mod inner {
|
||||
extern crate serde_codegen;
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
parity: {
|
||||
mode: sinon.stub().resolves('passive'),
|
||||
setMode: sinon.stub().resolves(true),
|
||||
chain: sinon.stub().resolves('foundation'),
|
||||
setChain: sinon.stub().resolves(true)
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
let src = Path::new("./src/api/types.rs.in");
|
||||
let dst = Path::new(&out_dir).join("types.rs");
|
||||
|
||||
serde_codegen::expand(&src, &dst).unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
createApi
|
||||
};
|
||||
#[cfg(feature = "serde_macros")]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
[package]
|
||||
description = "Base Package for all Parity built-in dapps"
|
||||
name = "parity-dapps-glue"
|
||||
version = "1.7.0"
|
||||
version = "1.5.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
quasi_codegen = { version = "0.32", optional = true }
|
||||
syntex = { version = "0.58", optional = true }
|
||||
quasi_codegen = { version = "0.11", optional = true }
|
||||
syntex = { version = "0.33", optional = true }
|
||||
|
||||
[dependencies]
|
||||
glob = { version = "0.2.11" }
|
||||
mime_guess = { version = "1.6.1" }
|
||||
aster = { version = "0.41", default-features = false }
|
||||
quasi = { version = "0.32", default-features = false }
|
||||
quasi_macros = { version = "0.32", optional = true }
|
||||
syntex = { version = "0.58", optional = true }
|
||||
syntex_syntax = { version = "0.58", optional = true }
|
||||
aster = { version = "0.17", default-features = false }
|
||||
quasi = { version = "0.11", default-features = false }
|
||||
quasi_macros = { version = "0.11", optional = true }
|
||||
syntex = { version = "0.33", optional = true }
|
||||
syntex_syntax = { version = "0.33", optional = true }
|
||||
clippy = { version = "0.0.90", optional = true }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -6,7 +6,7 @@ Code generator to simplify creating a built-in Parity Dapp
|
||||
1. Clone this repository.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity.git
|
||||
$ git clone https://github.com/ethcore/parity.git
|
||||
```
|
||||
|
||||
1. Create a new directory for your Dapp. (`./myapp`)
|
||||
@@ -29,10 +29,10 @@ Code generator to simplify creating a built-in Parity Dapp
|
||||
|
||||
The `inject.js` script will create global `web3` instance with proper provider that should be used by your dapp.
|
||||
|
||||
1. Create `./parity/dapps/myapp/Cargo.toml` with you apps details. See example here: [parity-status Cargo.toml](https://github.com/paritytech/parity-ui/blob/master/status/Cargo.toml).
|
||||
1. Create `./parity/dapps/myapp/Cargo.toml` with you apps details. See example here: [parity-status Cargo.toml](https://github.com/ethcore/parity-ui/blob/master/status/Cargo.toml).
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity-ui.git
|
||||
$ git clone https://github.com/ethcore/parity-ui.git
|
||||
$ cd ./parity-ui/
|
||||
$ cp ./home/Cargo.toml ../parity/dapps/myapp/Cargo.toml
|
||||
$ cp ./home/build.rs ../parity/dapps/myapp/build.rs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -25,11 +25,13 @@ mod inner {
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut registry = syntex::Registry::new();
|
||||
quasi_codegen::register(&mut registry);
|
||||
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
|
||||
quasi_codegen::expand(&src, &dst).unwrap();
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ pub mod inner {
|
||||
|
||||
impl fold::Folder for StripAttributeFolder {
|
||||
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
|
||||
if &*attr.value.name.as_str() == "webapp" {
|
||||
return None;
|
||||
match attr.node.value.node {
|
||||
ast::MetaItemKind::List(ref n, _) if n == &"webapp" => { return None; }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Some(attr)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -22,12 +22,16 @@ use self::mime_guess::guess_mime_type;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::ops::Deref;
|
||||
|
||||
use syntax::ast::{MetaItem, Item};
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::attr;
|
||||
use syntax::ast::{self, MetaItem, Item};
|
||||
use syntax::codemap::Span;
|
||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||
use syntax::print::pprust::lit_to_string;
|
||||
use syntax::symbol::InternedString;
|
||||
use syntax::ptr::P;
|
||||
use syntax::print::pprust::{lit_to_string};
|
||||
use syntax::parse::token::{InternedString};
|
||||
|
||||
|
||||
pub fn expand_webapp_implementation(
|
||||
cx: &mut ExtCtxt,
|
||||
@@ -44,7 +48,7 @@ pub fn expand_webapp_implementation(
|
||||
},
|
||||
};
|
||||
let builder = aster::AstBuilder::new().span(span);
|
||||
implement_webapp(cx, &builder, item, push);
|
||||
implement_webapp(cx, &builder, &item, push);
|
||||
}
|
||||
|
||||
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
|
||||
@@ -113,12 +117,11 @@ fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push
|
||||
}
|
||||
|
||||
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
|
||||
for meta_items in item.attrs.iter().filter_map(webapp_meta_items) {
|
||||
for meta_items in item.attrs().iter().filter_map(webapp_meta_items) {
|
||||
for meta_item in meta_items {
|
||||
let is_path = &*meta_item.name.as_str() == "path";
|
||||
match meta_item.node {
|
||||
ast::MetaItemKind::NameValue(ref lit) if is_path => {
|
||||
if let Some(s) = get_str_from_lit(cx, lit) {
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"path" => {
|
||||
if let Some(s) = get_str_from_lit(cx, name, lit) {
|
||||
return s.deref().to_owned();
|
||||
}
|
||||
},
|
||||
@@ -131,32 +134,14 @@ fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
|
||||
"web".to_owned()
|
||||
}
|
||||
|
||||
fn webapp_meta_items(attr: &ast::Attribute) -> Option<Vec<ast::MetaItem>> {
|
||||
let is_webapp = &*attr.value.name.as_str() == "webapp";
|
||||
match attr.value.node {
|
||||
ast::MetaItemKind::List(ref items) if is_webapp => {
|
||||
attr::mark_used(&attr);
|
||||
Some(
|
||||
items.iter()
|
||||
.map(|item| item.node.clone())
|
||||
.filter_map(|item| match item {
|
||||
ast::NestedMetaItemKind::MetaItem(item) => Some(item),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_str_from_lit(cx: &ExtCtxt, lit: &ast::Lit) -> Option<InternedString> {
|
||||
fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<InternedString> {
|
||||
match lit.node {
|
||||
ast::LitKind::Str(ref s, _) => Some(s.clone().as_str()),
|
||||
ast::LitKind::Str(ref s, _) => Some(s.clone()),
|
||||
_ => {
|
||||
cx.span_err(
|
||||
lit.span,
|
||||
&format!("webapp annotation path must be a string, not `{}`",
|
||||
&format!("webapp annotation `{}` must be a string, not `{}`",
|
||||
name,
|
||||
lit_to_string(lit)
|
||||
)
|
||||
);
|
||||
@@ -165,6 +150,16 @@ fn get_str_from_lit(cx: &ExtCtxt, lit: &ast::Lit) -> Option<InternedString> {
|
||||
}
|
||||
}
|
||||
|
||||
fn webapp_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
|
||||
match attr.node.value.node {
|
||||
ast::MetaItemKind::List(ref name, ref items) if name == &"webapp" => {
|
||||
attr::mark_used(&attr);
|
||||
Some(items)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_uri(path: &Path) -> String {
|
||||
let mut s = String::new();
|
||||
for component in path.iter() {
|
||||
@@ -174,6 +169,7 @@ fn as_uri(path: &Path) -> String {
|
||||
s[0..s.len()-1].into()
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_convert_path_separators_on_all_platforms() {
|
||||
// given
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -22,6 +22,7 @@
|
||||
extern crate syntex;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
#[macro_use]
|
||||
extern crate syntex_syntax as syntax;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "node-health"
|
||||
description = "Node's health status"
|
||||
version = "0.1.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
log = "0.3"
|
||||
ntp = "0.2.0"
|
||||
parking_lot = "0.4"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
time = "0.1.35"
|
||||
|
||||
parity-reactor = { path = "../../util/reactor" }
|
||||
@@ -1,122 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Reporting node's health.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use futures::{Future, BoxFuture};
|
||||
use futures::sync::oneshot;
|
||||
use types::{HealthInfo, HealthStatus, Health};
|
||||
use time::{TimeChecker, MAX_DRIFT};
|
||||
use parity_reactor::Remote;
|
||||
use parking_lot::Mutex;
|
||||
use {SyncStatus};
|
||||
|
||||
const TIMEOUT_SECS: u64 = 5;
|
||||
const PROOF: &str = "Only one closure is invoked.";
|
||||
|
||||
/// A struct enabling you to query for node's health.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeHealth {
|
||||
sync_status: Arc<SyncStatus>,
|
||||
time: TimeChecker,
|
||||
remote: Remote,
|
||||
}
|
||||
|
||||
impl NodeHealth {
|
||||
/// Creates new `NodeHealth`.
|
||||
pub fn new(sync_status: Arc<SyncStatus>, time: TimeChecker, remote: Remote) -> Self {
|
||||
NodeHealth { sync_status, time, remote, }
|
||||
}
|
||||
|
||||
/// Query latest health report.
|
||||
pub fn health(&self) -> BoxFuture<Health, ()> {
|
||||
trace!(target: "dapps", "Checking node health.");
|
||||
// Check timediff
|
||||
let sync_status = self.sync_status.clone();
|
||||
let time = self.time.time_drift();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||
let tx2 = tx.clone();
|
||||
self.remote.spawn_with_timeout(
|
||||
move || time.then(move |result| {
|
||||
let _ = tx.lock().take().expect(PROOF).send(Ok(result));
|
||||
Ok(())
|
||||
}),
|
||||
time::Duration::from_secs(TIMEOUT_SECS),
|
||||
move || {
|
||||
let _ = tx2.lock().take().expect(PROOF).send(Err(()));
|
||||
},
|
||||
);
|
||||
|
||||
rx.map_err(|err| {
|
||||
warn!(target: "dapps", "Health request cancelled: {:?}", err);
|
||||
}).and_then(move |time| {
|
||||
// Check peers
|
||||
let peers = {
|
||||
let (connected, max) = sync_status.peers();
|
||||
let (status, message) = match connected {
|
||||
0 => {
|
||||
(HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into())
|
||||
},
|
||||
1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()),
|
||||
_ => (HealthStatus::Ok, "".into()),
|
||||
};
|
||||
HealthInfo { status, message, details: (connected, max) }
|
||||
};
|
||||
|
||||
// Check sync
|
||||
let sync = {
|
||||
let is_syncing = sync_status.is_major_importing();
|
||||
let (status, message) = if is_syncing {
|
||||
(HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into())
|
||||
} else {
|
||||
(HealthStatus::Ok, "".into())
|
||||
};
|
||||
HealthInfo { status, message, details: is_syncing }
|
||||
};
|
||||
|
||||
// Check time
|
||||
let time = {
|
||||
let (status, message, details) = match time {
|
||||
Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => {
|
||||
(HealthStatus::Ok, "".into(), diff)
|
||||
},
|
||||
Ok(Ok(diff)) => {
|
||||
(HealthStatus::Bad, format!(
|
||||
"Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.",
|
||||
diff,
|
||||
), diff)
|
||||
},
|
||||
Ok(Err(err)) => {
|
||||
(HealthStatus::NeedsAttention, format!(
|
||||
"Unable to reach time API: {}. Make sure that your clock is synchronized.",
|
||||
err,
|
||||
), 0)
|
||||
},
|
||||
Err(_) => {
|
||||
(HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0)
|
||||
},
|
||||
};
|
||||
|
||||
HealthInfo { status, message, details, }
|
||||
};
|
||||
|
||||
Ok(Health { peers, sync, time})
|
||||
}).boxed()
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Node Health status reporting.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate futures_cpupool;
|
||||
extern crate ntp;
|
||||
extern crate time as time_crate;
|
||||
extern crate parity_reactor;
|
||||
extern crate parking_lot;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
mod health;
|
||||
mod time;
|
||||
mod types;
|
||||
|
||||
pub use futures_cpupool::CpuPool;
|
||||
pub use health::NodeHealth;
|
||||
pub use types::{Health, HealthInfo, HealthStatus};
|
||||
pub use time::{TimeChecker, Error};
|
||||
|
||||
/// Indicates sync status
|
||||
pub trait SyncStatus: ::std::fmt::Debug + Send + Sync {
|
||||
/// Returns true if there is a major sync happening.
|
||||
fn is_major_importing(&self) -> bool;
|
||||
|
||||
/// Returns number of connected and ideal peers.
|
||||
fn peers(&self) -> (usize, usize);
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Periodically checks node's time drift using [SNTP](https://tools.ietf.org/html/rfc1769).
|
||||
//!
|
||||
//! An NTP packet is sent to the server with a local timestamp, the server then completes the packet, yielding the
|
||||
//! following timestamps:
|
||||
//!
|
||||
//! Timestamp Name ID When Generated
|
||||
//! ------------------------------------------------------------
|
||||
//! Originate Timestamp T1 time request sent by client
|
||||
//! Receive Timestamp T2 time request received at server
|
||||
//! Transmit Timestamp T3 time reply sent by server
|
||||
//! Destination Timestamp T4 time reply received at client
|
||||
//!
|
||||
//! The drift is defined as:
|
||||
//!
|
||||
//! drift = ((T2 - T1) + (T3 - T4)) / 2.
|
||||
//!
|
||||
|
||||
use std::io;
|
||||
use std::{fmt, mem, time};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{self, AtomicUsize};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use futures::{self, Future, BoxFuture};
|
||||
use futures::future::{self, IntoFuture};
|
||||
use futures_cpupool::{CpuPool, CpuFuture};
|
||||
use ntp;
|
||||
use parking_lot::RwLock;
|
||||
use time_crate::{Duration, Timespec};
|
||||
|
||||
/// Time checker error.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Error {
|
||||
/// No servers are currently available for a query.
|
||||
NoServersAvailable,
|
||||
/// There was an error when trying to reach the NTP server.
|
||||
Ntp(String),
|
||||
/// IO error when reading NTP response.
|
||||
Io(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match *self {
|
||||
NoServersAvailable => write!(fmt, "No NTP servers available"),
|
||||
Ntp(ref err) => write!(fmt, "NTP error: {}", err),
|
||||
Io(ref err) => write!(fmt, "Connection Error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self { Error::Io(format!("{}", err)) }
|
||||
}
|
||||
|
||||
impl From<ntp::errors::Error> for Error {
|
||||
fn from(err: ntp::errors::Error) -> Self { Error::Ntp(format!("{}", err)) }
|
||||
}
|
||||
|
||||
/// NTP time drift checker.
|
||||
pub trait Ntp {
|
||||
/// Returned Future.
|
||||
type Future: IntoFuture<Item=Duration, Error=Error>;
|
||||
|
||||
/// Returns the current time drift.
|
||||
fn drift(&self) -> Self::Future;
|
||||
}
|
||||
|
||||
const SERVER_MAX_POLL_INTERVAL_SECS: u64 = 60;
|
||||
#[derive(Debug)]
|
||||
struct Server {
|
||||
pub address: String,
|
||||
next_call: RwLock<time::Instant>,
|
||||
failures: AtomicUsize,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn is_available(&self) -> bool {
|
||||
*self.next_call.read() < time::Instant::now()
|
||||
}
|
||||
|
||||
pub fn report_success(&self) {
|
||||
self.failures.store(0, atomic::Ordering::SeqCst);
|
||||
self.update_next_call(1)
|
||||
}
|
||||
|
||||
pub fn report_failure(&self) {
|
||||
let errors = self.failures.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
self.update_next_call(1 << errors)
|
||||
}
|
||||
|
||||
fn update_next_call(&self, delay: usize) {
|
||||
*self.next_call.write() = time::Instant::now() + time::Duration::from_secs(delay as u64 * SERVER_MAX_POLL_INTERVAL_SECS);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> From<T> for Server {
|
||||
fn from(t: T) -> Self {
|
||||
Server {
|
||||
address: t.as_ref().to_owned(),
|
||||
next_call: RwLock::new(time::Instant::now()),
|
||||
failures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NTP client using the SNTP algorithm for calculating drift.
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleNtp {
|
||||
addresses: Vec<Arc<Server>>,
|
||||
pool: CpuPool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SimpleNtp {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f
|
||||
.debug_struct("SimpleNtp")
|
||||
.field("addresses", &self.addresses)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleNtp {
|
||||
fn new<T: AsRef<str>>(addresses: &[T], pool: CpuPool) -> SimpleNtp {
|
||||
SimpleNtp {
|
||||
addresses: addresses.iter().map(Server::from).map(Arc::new).collect(),
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ntp for SimpleNtp {
|
||||
type Future = future::Either<
|
||||
CpuFuture<Duration, Error>,
|
||||
future::FutureResult<Duration, Error>,
|
||||
>;
|
||||
|
||||
fn drift(&self) -> Self::Future {
|
||||
use self::future::Either::{A, B};
|
||||
|
||||
let server = self.addresses.iter().find(|server| server.is_available());
|
||||
server.map(|server| {
|
||||
let server = server.clone();
|
||||
A(self.pool.spawn_fn(move || {
|
||||
debug!(target: "dapps", "Fetching time from {}.", server.address);
|
||||
|
||||
match ntp::request(&server.address) {
|
||||
Ok(packet) => {
|
||||
let dest_time = ::time_crate::now_utc().to_timespec();
|
||||
let orig_time = Timespec::from(packet.orig_time);
|
||||
let recv_time = Timespec::from(packet.recv_time);
|
||||
let transmit_time = Timespec::from(packet.transmit_time);
|
||||
|
||||
let drift = ((recv_time - orig_time) + (transmit_time - dest_time)) / 2;
|
||||
|
||||
server.report_success();
|
||||
Ok(drift)
|
||||
},
|
||||
Err(err) => {
|
||||
server.report_failure();
|
||||
Err(err.into())
|
||||
},
|
||||
}
|
||||
}))
|
||||
}).unwrap_or_else(|| B(future::err(Error::NoServersAvailable)))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE In a positive scenario first results will be seen after:
|
||||
// MAX_RESULTS * UPDATE_TIMEOUT_INCOMPLETE_SECS seconds.
|
||||
const MAX_RESULTS: usize = 4;
|
||||
const UPDATE_TIMEOUT_OK_SECS: u64 = 6 * 60 * 60;
|
||||
const UPDATE_TIMEOUT_WARN_SECS: u64 = 15 * 60;
|
||||
const UPDATE_TIMEOUT_ERR_SECS: u64 = 60;
|
||||
const UPDATE_TIMEOUT_INCOMPLETE_SECS: u64 = 10;
|
||||
|
||||
/// Maximal valid time drift.
|
||||
pub const MAX_DRIFT: i64 = 500;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A time checker.
|
||||
pub struct TimeChecker<N: Ntp = SimpleNtp> {
|
||||
ntp: N,
|
||||
last_result: Arc<RwLock<(time::Instant, VecDeque<Result<i64, Error>>)>>,
|
||||
}
|
||||
|
||||
impl TimeChecker<SimpleNtp> {
|
||||
/// Creates new time checker given the NTP server address.
|
||||
pub fn new<T: AsRef<str>>(ntp_addresses: &[T], pool: CpuPool) -> Self {
|
||||
let last_result = Arc::new(RwLock::new(
|
||||
// Assume everything is ok at the very beginning.
|
||||
(time::Instant::now(), vec![Ok(0)].into())
|
||||
));
|
||||
|
||||
let ntp = SimpleNtp::new(ntp_addresses, pool);
|
||||
|
||||
TimeChecker {
|
||||
ntp,
|
||||
last_result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Ntp> TimeChecker<N> where <N::Future as IntoFuture>::Future: Send + 'static {
|
||||
/// Updates the time
|
||||
pub fn update(&self) -> BoxFuture<i64, Error> {
|
||||
trace!(target: "dapps", "Updating time from NTP.");
|
||||
let last_result = self.last_result.clone();
|
||||
self.ntp.drift().into_future().then(move |res| {
|
||||
let res = res.map(|d| d.num_milliseconds());
|
||||
|
||||
if let Err(Error::NoServersAvailable) = res {
|
||||
debug!(target: "dapps", "No NTP servers available. Selecting an older result.");
|
||||
return select_result(last_result.read().1.iter());
|
||||
}
|
||||
|
||||
// Update the results.
|
||||
let mut results = mem::replace(&mut last_result.write().1, VecDeque::new());
|
||||
let has_all_results = results.len() >= MAX_RESULTS;
|
||||
let valid_till = time::Instant::now() + time::Duration::from_secs(
|
||||
match res {
|
||||
Ok(time) if has_all_results && time < MAX_DRIFT => UPDATE_TIMEOUT_OK_SECS,
|
||||
Ok(_) if has_all_results => UPDATE_TIMEOUT_WARN_SECS,
|
||||
Err(_) if has_all_results => UPDATE_TIMEOUT_ERR_SECS,
|
||||
_ => UPDATE_TIMEOUT_INCOMPLETE_SECS,
|
||||
}
|
||||
);
|
||||
|
||||
trace!(target: "dapps", "New time drift received: {:?}", res);
|
||||
// Push the result.
|
||||
results.push_back(res);
|
||||
while results.len() > MAX_RESULTS {
|
||||
results.pop_front();
|
||||
}
|
||||
|
||||
// Select a response and update last result.
|
||||
let res = select_result(results.iter());
|
||||
*last_result.write() = (valid_till, results);
|
||||
res
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
/// Returns a current time drift or error if last request to NTP server failed.
|
||||
pub fn time_drift(&self) -> BoxFuture<i64, Error> {
|
||||
// return cached result
|
||||
{
|
||||
let res = self.last_result.read();
|
||||
if res.0 > time::Instant::now() {
|
||||
return futures::done(select_result(res.1.iter())).boxed();
|
||||
}
|
||||
}
|
||||
// or update and return result
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
fn select_result<'a, T: Iterator<Item=&'a Result<i64, Error>>>(results: T) -> Result<i64, Error> {
|
||||
let mut min = None;
|
||||
for res in results {
|
||||
min = Some(match (min.take(), res) {
|
||||
(Some(Ok(min)), &Ok(ref new)) => Ok(::std::cmp::min(min, *new)),
|
||||
(Some(Ok(old)), &Err(_)) => Ok(old),
|
||||
(_, ref new) => (*new).clone(),
|
||||
})
|
||||
}
|
||||
|
||||
min.unwrap_or_else(|| Err(Error::Ntp("NTP server unavailable.".into())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::time::Instant;
|
||||
use time::Duration;
|
||||
use futures::{future, Future};
|
||||
use super::{Ntp, TimeChecker, Error};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeNtp(RefCell<Vec<Duration>>, Cell<u64>);
|
||||
impl FakeNtp {
|
||||
fn new() -> FakeNtp {
|
||||
FakeNtp(
|
||||
RefCell::new(vec![Duration::milliseconds(150)]),
|
||||
Cell::new(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ntp for FakeNtp {
|
||||
type Future = future::FutureResult<Duration, Error>;
|
||||
|
||||
fn drift(&self) -> Self::Future {
|
||||
self.1.set(self.1.get() + 1);
|
||||
future::ok(self.0.borrow_mut().pop().expect("Unexpected call to drift()."))
|
||||
}
|
||||
}
|
||||
|
||||
fn time_checker() -> TimeChecker<FakeNtp> {
|
||||
let last_result = Arc::new(RwLock::new(
|
||||
(Instant::now(), vec![Err(Error::Ntp("NTP server unavailable".into()))].into())
|
||||
));
|
||||
|
||||
TimeChecker {
|
||||
ntp: FakeNtp::new(),
|
||||
last_result: last_result,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fetch_time_on_start() {
|
||||
// given
|
||||
let time = time_checker();
|
||||
|
||||
// when
|
||||
let diff = time.time_drift().wait().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(diff, 150);
|
||||
assert_eq!(time.ntp.1.get(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_fetch_twice_if_timeout_has_not_passed() {
|
||||
// given
|
||||
let time = time_checker();
|
||||
|
||||
// when
|
||||
let diff1 = time.time_drift().wait().unwrap();
|
||||
let diff2 = time.time_drift().wait().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(diff1, 150);
|
||||
assert_eq!(diff2, 150);
|
||||
assert_eq!(time.ntp.1.get(), 1);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Base health types.
|
||||
|
||||
/// Health API endpoint status.
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub enum HealthStatus {
|
||||
/// Everything's OK.
|
||||
#[serde(rename = "ok")]
|
||||
Ok,
|
||||
/// Node health need attention
|
||||
/// (the issue is not critical, but may need investigation)
|
||||
#[serde(rename = "needsAttention")]
|
||||
NeedsAttention,
|
||||
/// There is something bad detected with the node.
|
||||
#[serde(rename = "bad")]
|
||||
Bad,
|
||||
}
|
||||
|
||||
/// Represents a single check in node health.
|
||||
/// Cointains the status of that check and apropriate message and details.
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct HealthInfo<T> {
|
||||
/// Check status.
|
||||
pub status: HealthStatus,
|
||||
/// Human-readable message.
|
||||
pub message: String,
|
||||
/// Technical details of the check.
|
||||
pub details: T,
|
||||
}
|
||||
|
||||
/// Node Health status.
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Health {
|
||||
/// Status of peers.
|
||||
pub peers: HealthInfo<(usize, usize)>,
|
||||
/// Sync status.
|
||||
pub sync: HealthInfo<bool>,
|
||||
/// Time diff info.
|
||||
pub time: HealthInfo<i64>,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,47 +15,52 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use unicase::UniCase;
|
||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||
use hyper::header;
|
||||
use hyper::method::Method;
|
||||
use hyper::status::StatusCode;
|
||||
use hyper::header::AccessControlAllowOrigin;
|
||||
|
||||
use api::{response, types};
|
||||
use api::types::{App, ApiError};
|
||||
use api::response;
|
||||
use apps::fetcher::Fetcher;
|
||||
use handlers::{self, extract_url};
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use node_health::{NodeHealth, HealthStatus, Health};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
use jsonrpc_http_server::cors;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
) -> Box<Endpoint> {
|
||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
fetcher,
|
||||
health,
|
||||
remote,
|
||||
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
endpoints: endpoints,
|
||||
fetcher: fetcher,
|
||||
})
|
||||
}
|
||||
|
||||
fn list_apps(&self) -> Vec<App> {
|
||||
self.endpoints.iter().filter_map(|(ref k, ref e)| {
|
||||
e.info().map(|ref info| App::from_info(k, info))
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for RestApi {
|
||||
fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
|
||||
Box::new(RestApiRouter::new((*self).clone(), path, control))
|
||||
Box::new(RestApiRouter::new(self.clone(), path, control))
|
||||
}
|
||||
}
|
||||
|
||||
struct RestApiRouter {
|
||||
api: RestApi,
|
||||
origin: Option<String>,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
@@ -65,18 +70,18 @@ impl RestApiRouter {
|
||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||
RestApiRouter {
|
||||
path: Some(path),
|
||||
origin: None,
|
||||
control: Some(control),
|
||||
api: api,
|
||||
handler: Box::new(response::as_json_error(StatusCode::NotFound, &types::ApiError {
|
||||
handler: response::as_json_error(&ApiError {
|
||||
code: "404".into(),
|
||||
title: "Not Found".into(),
|
||||
detail: "Resource you requested has not been found.".into(),
|
||||
})),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, control: Control) -> Option<Box<Handler>> {
|
||||
trace!(target: "dapps", "Resolving content: {:?} from path: {:?}", hash, path);
|
||||
match hash {
|
||||
Some(hash) if self.api.fetcher.contains(hash) => {
|
||||
Some(self.api.fetcher.to_async_handler(path, control))
|
||||
@@ -85,35 +90,39 @@ impl RestApiRouter {
|
||||
}
|
||||
}
|
||||
|
||||
fn health(&self, control: Control) -> Box<Handler> {
|
||||
let map = move |health: Result<Result<Health, ()>, ()>| {
|
||||
let status = match health {
|
||||
Ok(Ok(ref health)) => {
|
||||
if [&health.peers.status, &health.sync.status].iter().any(|x| *x != &HealthStatus::Ok) {
|
||||
StatusCode::PreconditionFailed // HTTP 412
|
||||
} else {
|
||||
StatusCode::Ok // HTTP 200
|
||||
}
|
||||
},
|
||||
_ => StatusCode::ServiceUnavailable, // HTTP 503
|
||||
};
|
||||
/// Returns basic headers for a response (it may be overwritten by the handler)
|
||||
fn response_headers(&self) -> header::Headers {
|
||||
let mut headers = header::Headers::new();
|
||||
headers.set(header::AccessControlAllowCredentials);
|
||||
headers.set(header::AccessControlAllowMethods(vec![
|
||||
Method::Options,
|
||||
Method::Post,
|
||||
Method::Get,
|
||||
]));
|
||||
headers.set(header::AccessControlAllowHeaders(vec![
|
||||
UniCase("origin".to_owned()),
|
||||
UniCase("content-type".to_owned()),
|
||||
UniCase("accept".to_owned()),
|
||||
]));
|
||||
|
||||
response::as_json(status, &health)
|
||||
};
|
||||
let health = self.api.health.health();
|
||||
let remote = self.api.remote.clone();
|
||||
Box::new(handlers::AsyncHandler::new(health, map, remote, control))
|
||||
if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) {
|
||||
headers.set(cors_header);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
self.origin = cors::read_origin(&request);
|
||||
|
||||
if let Method::Options = *request.method() {
|
||||
self.handler = response::empty();
|
||||
return Next::write();
|
||||
}
|
||||
|
||||
// TODO [ToDr] Consider using `path.app_params` instead
|
||||
let url = extract_url(&request);
|
||||
if url.is_none() {
|
||||
// Just return 404 if we can't parse URL
|
||||
@@ -131,8 +140,8 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
|
||||
|
||||
let handler = endpoint.and_then(|v| match v {
|
||||
"apps" => Some(response::as_json(&self.api.list_apps())),
|
||||
"ping" => Some(response::ping()),
|
||||
"health" => Some(self.health(control)),
|
||||
"content" => self.resolve_content(hash, path, control),
|
||||
_ => None
|
||||
});
|
||||
@@ -150,10 +159,12 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
*res.headers_mut() = self.response_headers();
|
||||
self.handler.on_response(res)
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
|
||||
self.handler.on_response_writable(encoder)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,8 +16,12 @@
|
||||
|
||||
//! REST API
|
||||
|
||||
#![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))]
|
||||
#![cfg_attr(feature="nightly", plugin(serde_macros, clippy))]
|
||||
|
||||
mod api;
|
||||
mod response;
|
||||
mod types;
|
||||
|
||||
pub use self::api::RestApi;
|
||||
pub use self::types::App;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use endpoint::Handler;
|
||||
use handlers::{ContentHandler, EchoHandler};
|
||||
|
||||
@@ -25,16 +23,16 @@ pub fn empty() -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
|
||||
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
ContentHandler::new(status, json, mime!(Application/Json))
|
||||
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn as_json_error<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
|
||||
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
ContentHandler::new(status, json, mime!(Application/Json))
|
||||
Box::new(ContentHandler::not_found(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn ping() -> Box<Handler> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,14 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// A structure representing any error in REST API.
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApiError {
|
||||
/// Error code.
|
||||
pub code: String,
|
||||
/// Human-readable error summary.
|
||||
pub title: String,
|
||||
/// More technical error details.
|
||||
pub detail: String,
|
||||
}
|
||||
#[cfg(feature = "serde_macros")]
|
||||
include!("types.rs.in");
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
include!(concat!(env!("OUT_DIR"), "/types.rs"));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -53,3 +53,12 @@ impl Into<EndpointInfo> for App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApiError {
|
||||
pub code: String,
|
||||
pub title: String,
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -25,7 +25,6 @@ use util::sha3::sha3;
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use handlers::{ContentValidator, ValidatorResponse};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
|
||||
use Embeddable;
|
||||
|
||||
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
|
||||
|
||||
@@ -117,16 +116,16 @@ pub struct Dapp {
|
||||
id: String,
|
||||
dapps_path: PathBuf,
|
||||
on_done: OnDone,
|
||||
embeddable_on: Embeddable,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Dapp {
|
||||
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
|
||||
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Dapp {
|
||||
id,
|
||||
dapps_path,
|
||||
on_done,
|
||||
embeddable_on,
|
||||
id: id,
|
||||
dapps_path: dapps_path,
|
||||
on_done: on_done,
|
||||
embeddable_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -23,7 +23,7 @@ mod installers;
|
||||
use std::{fs, env};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use rustc_hex::FromHex;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use fetch::{Client as FetchClient, Fetch};
|
||||
use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
|
||||
use parity_reactor::Remote;
|
||||
@@ -31,7 +31,7 @@ use parity_reactor::Remote;
|
||||
use hyper;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use {Embeddable, SyncStatus, random_filename};
|
||||
use {SyncStatus, random_filename};
|
||||
use util::Mutex;
|
||||
use page::LocalPageEndpoint;
|
||||
use handlers::{ContentHandler, ContentFetcherHandler};
|
||||
@@ -47,73 +47,46 @@ pub trait Fetcher: Send + Sync + 'static {
|
||||
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler>;
|
||||
}
|
||||
|
||||
pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHintContract> {
|
||||
cache_path: PathBuf,
|
||||
pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + Send + Sync + 'static = URLHintContract> {
|
||||
dapps_path: PathBuf,
|
||||
resolver: R,
|
||||
cache: Arc<Mutex<ContentCache>>,
|
||||
sync: Arc<SyncStatus>,
|
||||
embeddable_on: Embeddable,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
only_content: bool,
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
fn drop(&mut self) {
|
||||
// Clear cache path
|
||||
let _ = fs::remove_dir_all(&self.cache_path);
|
||||
let _ = fs::remove_dir_all(&self.dapps_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
|
||||
pub fn new(
|
||||
resolver: R,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let mut cache_path = env::temp_dir();
|
||||
cache_path.push(random_filename());
|
||||
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
ContentFetcher {
|
||||
cache_path: cache_path,
|
||||
dapps_path: dapps_path,
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_on: None,
|
||||
embeddable_on: embeddable_on,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
only_content: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow_dapps(mut self, dapps: bool) -> Self {
|
||||
self.only_content = !dapps;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
|
||||
self.embeddable_on = embeddable_on;
|
||||
self
|
||||
}
|
||||
|
||||
fn still_syncing(embeddable: Embeddable) -> Box<Handler> {
|
||||
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Sync In Progress",
|
||||
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
|
||||
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
|
||||
embeddable,
|
||||
))
|
||||
}
|
||||
|
||||
fn dapps_disabled(address: Embeddable) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Network Dapps Not Available",
|
||||
"This interface doesn't support network dapps for security reasons.",
|
||||
None,
|
||||
address,
|
||||
))
|
||||
}
|
||||
@@ -122,19 +95,9 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
||||
self.cache.lock().insert(content_id.to_owned(), status);
|
||||
}
|
||||
|
||||
// resolve contract call synchronously.
|
||||
// TODO: port to futures-based hyper and make it all async.
|
||||
fn resolve(&self, content_id: Vec<u8>) -> Option<URLHintResult> {
|
||||
use futures::Future;
|
||||
|
||||
self.resolver.resolve(content_id)
|
||||
.wait()
|
||||
.unwrap_or_else(|e| { warn!("Error resolving content-id: {}", e); None })
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
fn contains(&self, content_id: &str) -> bool {
|
||||
{
|
||||
let mut cache = self.cache.lock();
|
||||
@@ -145,8 +108,10 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
}
|
||||
// fallback to resolver
|
||||
if let Ok(content_id) = content_id.from_hex() {
|
||||
// else try to resolve the app_id
|
||||
let has_content = self.resolver.resolve(content_id).is_some();
|
||||
// if there is content or we are syncing return true
|
||||
self.sync.is_major_importing() || self.resolve(content_id).is_some()
|
||||
has_content || self.sync.is_major_importing()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -172,7 +137,7 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
_ => {
|
||||
trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id);
|
||||
let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true.");
|
||||
let content = self.resolve(content_hex);
|
||||
let content = self.resolver.resolve(content_hex);
|
||||
|
||||
let cache = self.cache.clone();
|
||||
let id = content_id.clone();
|
||||
@@ -190,9 +155,6 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
||||
},
|
||||
Some(URLHintResult::Dapp(_)) if self.only_content => {
|
||||
(None, Self::dapps_disabled(self.embeddable_on.clone()))
|
||||
},
|
||||
Some(URLHintResult::Dapp(dapp)) => {
|
||||
let handler = ContentFetcherHandler::new(
|
||||
dapp.url(),
|
||||
@@ -200,7 +162,7 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
control,
|
||||
installers::Dapp::new(
|
||||
content_id.clone(),
|
||||
self.cache_path.clone(),
|
||||
self.dapps_path.clone(),
|
||||
Box::new(on_done),
|
||||
self.embeddable_on.clone(),
|
||||
),
|
||||
@@ -219,7 +181,7 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
installers::Content::new(
|
||||
content_id.clone(),
|
||||
content.mime,
|
||||
self.cache_path.clone(),
|
||||
self.dapps_path.clone(),
|
||||
Box::new(on_done),
|
||||
),
|
||||
self.embeddable_on.clone(),
|
||||
@@ -263,7 +225,6 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use util::Bytes;
|
||||
use fetch::{Fetch, Client};
|
||||
use futures::{future, Future, BoxFuture};
|
||||
use hash_fetch::urlhint::{URLHint, URLHintResult};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
@@ -271,29 +232,19 @@ mod tests {
|
||||
use endpoint::EndpointInfo;
|
||||
use page::LocalPageEndpoint;
|
||||
use super::{ContentFetcher, Fetcher};
|
||||
use {SyncStatus};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeResolver;
|
||||
impl URLHint for FakeResolver {
|
||||
fn resolve(&self, _id: Bytes) -> BoxFuture<Option<URLHintResult>, String> {
|
||||
future::ok(None).boxed()
|
||||
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FakeSync(bool);
|
||||
impl SyncStatus for FakeSync {
|
||||
fn is_major_importing(&self) -> bool { self.0 }
|
||||
fn peers(&self) -> (usize, usize) { (0, 5) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_true_if_contains_the_app() {
|
||||
// given
|
||||
let path = env::temp_dir();
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(FakeSync(false)), Remote::new_sync(), Client::new().unwrap())
|
||||
.allow_dapps(true);
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap());
|
||||
let handler = LocalPageEndpoint::new(path, EndpointInfo {
|
||||
name: "fake".into(),
|
||||
description: "".into(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,15 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use endpoint::{Endpoint, EndpointInfo};
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
use Embeddable;
|
||||
|
||||
struct LocalDapp {
|
||||
id: String,
|
||||
@@ -61,14 +59,14 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
|
||||
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
|
||||
/// Parses the path to extract last component (for name).
|
||||
/// `None` is returned when path is invalid or non-existent.
|
||||
pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable) -> Option<(String, Box<LocalPageEndpoint>)> {
|
||||
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
|
||||
let path = path.as_ref().to_owned();
|
||||
path.canonicalize().ok().and_then(|path| {
|
||||
let name = path.file_name().and_then(|name| name.to_str());
|
||||
name.map(|name| {
|
||||
let dapp = local_dapp(name.into(), path.clone());
|
||||
(dapp.id, Box::new(LocalPageEndpoint::new(
|
||||
dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
|
||||
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())
|
||||
))
|
||||
})
|
||||
})
|
||||
@@ -87,12 +85,12 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
|
||||
|
||||
/// Returns endpoints for Local Dapps found for given filesystem path.
|
||||
/// Scans the directory and collects `LocalPageEndpoints`.
|
||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable) -> BTreeMap<String, Box<Endpoint>> {
|
||||
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
|
||||
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
|
||||
let mut pages = Endpoints::new();
|
||||
for dapp in local_dapps(dapps_path.as_ref()) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
|
||||
);
|
||||
}
|
||||
pages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde_json;
|
||||
pub use apps::App as Manifest;
|
||||
pub use api::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,10 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use endpoint::{Endpoints, Endpoint};
|
||||
use page::PageEndpoint;
|
||||
use proxypac::ProxyPac;
|
||||
@@ -25,19 +23,17 @@ use web::Web;
|
||||
use fetch::Fetch;
|
||||
use parity_dapps::WebApp;
|
||||
use parity_reactor::Remote;
|
||||
use parity_ui;
|
||||
use {WebProxyTokens, ParentFrameSettings};
|
||||
use {WebProxyTokens};
|
||||
|
||||
mod app;
|
||||
mod cache;
|
||||
mod fs;
|
||||
mod ui;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
|
||||
pub use self::app::App;
|
||||
extern crate parity_ui;
|
||||
|
||||
pub const HOME_PAGE: &'static str = "home";
|
||||
pub const DAPPS_DOMAIN: &'static str = ".parity";
|
||||
pub const RPC_PATH: &'static str = "rpc";
|
||||
pub const API_PATH: &'static str = "api";
|
||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
||||
@@ -48,27 +44,18 @@ pub fn utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
pub fn ui() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
|
||||
}
|
||||
|
||||
pub fn ui_redirection(embeddable: Option<ParentFrameSettings>) -> Box<Endpoint> {
|
||||
Box::new(ui::Redirection::new(embeddable))
|
||||
}
|
||||
|
||||
pub fn all_endpoints<F: Fetch>(
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
dapps_domain: &str,
|
||||
embeddable: Option<ParentFrameSettings>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Endpoints {
|
||||
// fetch fs dapps at first to avoid overwriting builtins
|
||||
let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
|
||||
for path in extra_dapps {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
|
||||
pages.insert(id, endpoint);
|
||||
} else {
|
||||
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
|
||||
@@ -76,14 +63,14 @@ pub fn all_endpoints<F: Fetch>(
|
||||
}
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned()));
|
||||
pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone()));
|
||||
pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
||||
|
||||
Arc::new(pages)
|
||||
pages
|
||||
}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpoint>>, id: &str, embed_at: Embeddable) {
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
|
||||
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
||||
Embeddable::No => PageEndpoint::new(T::default()),
|
||||
@@ -91,7 +78,7 @@ fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpo
|
||||
}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<ParentFrameSettings>),
|
||||
Yes(Option<(String, u16)>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! UI redirections
|
||||
|
||||
use hyper::{Control, StatusCode};
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use {handlers, Embeddable};
|
||||
|
||||
/// Redirection to UI server.
|
||||
pub struct Redirection {
|
||||
embeddable_on: Embeddable,
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new(
|
||||
embeddable_on: Embeddable,
|
||||
) -> Self {
|
||||
Redirection {
|
||||
embeddable_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for Redirection {
|
||||
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
|
||||
if let Some(ref frame) = self.embeddable_on {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
handlers::Redirection::boxed(&format!("http://{}:{}", &frame.host, frame.port))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(handlers::ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,15 +16,12 @@
|
||||
|
||||
//! URL Endpoint traits
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hyper::{self, server, net};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Clone)]
|
||||
pub struct EndpointPath {
|
||||
pub app_id: String,
|
||||
pub app_params: Vec<String>,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub using_dapps_domains: bool,
|
||||
@@ -39,7 +36,7 @@ pub struct EndpointInfo {
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
pub type Endpoints = Arc<BTreeMap<String, Box<Endpoint>>>;
|
||||
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
pub trait Endpoint : Send + Sync {
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Async Content Handler
|
||||
//! Temporary solution until we switch to future-based server.
|
||||
//! Wraps a future and converts it to hyper::server::Handler;
|
||||
|
||||
use std::{mem, time};
|
||||
use std::sync::mpsc;
|
||||
use futures::Future;
|
||||
use hyper::{server, Decoder, Encoder, Next, Control};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
use handlers::ContentHandler;
|
||||
use parity_reactor::Remote;
|
||||
|
||||
const TIMEOUT_SECS: u64 = 15;
|
||||
|
||||
enum State<F, T, M> {
|
||||
Initial(F, M, Remote, Control),
|
||||
Waiting(mpsc::Receiver<Result<T, ()>>, M),
|
||||
Done(ContentHandler),
|
||||
Invalid,
|
||||
}
|
||||
|
||||
pub struct AsyncHandler<F, T, M> {
|
||||
state: State<F, T, M>,
|
||||
}
|
||||
|
||||
impl<F, T, M> AsyncHandler<F, T, M> {
|
||||
pub fn new(future: F, map: M, remote: Remote, control: Control) -> Self {
|
||||
AsyncHandler {
|
||||
state: State::Initial(future, map, remote, control),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, E, M> server::Handler<HttpStream> for AsyncHandler<F, Result<T, E>, M> where
|
||||
F: Future<Item=T, Error=E> + Send + 'static,
|
||||
M: FnOnce(Result<Result<T, E>, ()>) -> ContentHandler,
|
||||
T: Send + 'static,
|
||||
E: Send + 'static,
|
||||
{
|
||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
||||
if let State::Initial(future, map, remote, control) = mem::replace(&mut self.state, State::Invalid) {
|
||||
let (tx, rx) = mpsc::sync_channel(1);
|
||||
let control2 = control.clone();
|
||||
let tx2 = tx.clone();
|
||||
remote.spawn_with_timeout(move || future.then(move |result| {
|
||||
// Send a result (ignore errors if the connection was dropped)
|
||||
let _ = tx.send(Ok(result));
|
||||
// Resume handler
|
||||
let _ = control.ready(Next::read());
|
||||
|
||||
Ok(())
|
||||
}), time::Duration::from_secs(TIMEOUT_SECS), move || {
|
||||
// Notify about error
|
||||
let _ = tx2.send(Err(()));
|
||||
// Resume handler
|
||||
let _ = control2.ready(Next::read());
|
||||
});
|
||||
|
||||
self.state = State::Waiting(rx, map);
|
||||
}
|
||||
|
||||
Next::wait()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
if let State::Waiting(rx, map) = mem::replace(&mut self.state, State::Invalid) {
|
||||
match rx.try_recv() {
|
||||
Ok(result) => {
|
||||
self.state = State::Done(map(result));
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Resuming handler in incorrect state: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
if let State::Done(ref mut handler) = self.state {
|
||||
handler.on_response(res)
|
||||
} else {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
if let State::Done(ref mut handler) = self.state {
|
||||
handler.on_response_writable(encoder)
|
||||
} else {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
}
|
||||
44
dapps/src/handlers/auth.rs
Normal file
44
dapps/src/handlers/auth.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Authorization Handlers
|
||||
|
||||
use hyper::{server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
pub struct AuthRequiredHandler;
|
||||
|
||||
impl server::Handler<HttpStream> for AuthRequiredHandler {
|
||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
res.set_status(StatusCode::Unauthorized);
|
||||
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -24,7 +24,6 @@ use hyper::status::StatusCode;
|
||||
use util::version;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
use Embeddable;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContentHandler {
|
||||
@@ -32,7 +31,7 @@ pub struct ContentHandler {
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
write_pos: usize,
|
||||
safe_to_embed_on: Embeddable,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
@@ -40,17 +39,15 @@ impl ContentHandler {
|
||||
Self::new(StatusCode::Ok, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
|
||||
pub fn not_found(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::NotFound, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn error(
|
||||
code: StatusCode,
|
||||
title: &str,
|
||||
message: &str,
|
||||
details: Option<&str>,
|
||||
embeddable_on: Embeddable,
|
||||
) -> Self {
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
@@ -64,18 +61,13 @@ impl ContentHandler {
|
||||
Self::new_embeddable(code, content, mimetype, None)
|
||||
}
|
||||
|
||||
pub fn new_embeddable(
|
||||
code: StatusCode,
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
safe_to_embed_on: Embeddable,
|
||||
) -> Self {
|
||||
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
ContentHandler {
|
||||
code,
|
||||
content,
|
||||
mimetype,
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0,
|
||||
safe_to_embed_on,
|
||||
safe_to_embed_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +84,7 @@ impl server::Handler<HttpStream> for ContentHandler {
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
res.set_status(self.code);
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -33,7 +33,6 @@ use hyper::status::StatusCode;
|
||||
use endpoint::EndpointPath;
|
||||
use handlers::{ContentHandler, StreamingHandler};
|
||||
use page::{LocalPageEndpoint, PageHandlerWaiting};
|
||||
use {Embeddable};
|
||||
|
||||
const FETCH_TIMEOUT: u64 = 300;
|
||||
|
||||
@@ -180,7 +179,7 @@ impl server::Handler<HttpStream> for WaitingHandler {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Errors {
|
||||
embeddable_on: Embeddable,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Errors {
|
||||
@@ -242,20 +241,20 @@ impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
|
||||
path: EndpointPath,
|
||||
control: Control,
|
||||
installer: H,
|
||||
embeddable_on: Embeddable,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
ContentFetcherHandler {
|
||||
fetch_control: FetchControl::default(),
|
||||
control,
|
||||
remote,
|
||||
fetch,
|
||||
control: control,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
status: FetchState::NotStarted(url),
|
||||
installer: Some(installer),
|
||||
path,
|
||||
path: path,
|
||||
errors: Errors {
|
||||
embeddable_on,
|
||||
embeddable_on: embeddable_on,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,105 +16,39 @@
|
||||
|
||||
//! Hyper handlers implementations.
|
||||
|
||||
mod async;
|
||||
mod auth;
|
||||
mod content;
|
||||
mod echo;
|
||||
mod fetch;
|
||||
mod redirect;
|
||||
mod streaming;
|
||||
|
||||
pub use self::async::AsyncHandler;
|
||||
pub use self::auth::AuthRequiredHandler;
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::echo::EchoHandler;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
|
||||
pub use self::redirect::Redirection;
|
||||
pub use self::streaming::StreamingHandler;
|
||||
|
||||
use std::iter;
|
||||
use util::Itertools;
|
||||
|
||||
use url::Url;
|
||||
use hyper::{server, header, net, uri};
|
||||
use {apps, address, Embeddable};
|
||||
use address;
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) {
|
||||
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
|
||||
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
|
||||
|
||||
// Embedding header:
|
||||
if let None = embeddable_on {
|
||||
if let Some(embeddable_on) = embeddable_on {
|
||||
headers.set_raw(
|
||||
"X-Frame-Options",
|
||||
vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
|
||||
);
|
||||
} else {
|
||||
// TODO [ToDr] Should we be more strict here (DENY?)?
|
||||
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
|
||||
}
|
||||
|
||||
// Content Security Policy headers
|
||||
headers.set_raw("Content-Security-Policy", vec![
|
||||
// Allow connecting to WS servers and HTTP(S) servers.
|
||||
// We could be more restrictive and allow only RPC server URL.
|
||||
b"connect-src http: https: ws: wss:;".to_vec(),
|
||||
// Allow framing any content from HTTP(S).
|
||||
// Again we could only allow embedding from RPC server URL.
|
||||
// (deprecated)
|
||||
b"frame-src 'self' http: https:;".to_vec(),
|
||||
// Allow framing and web workers from HTTP(S).
|
||||
b"child-src 'self' http: https:;".to_vec(),
|
||||
// We allow data: blob: and HTTP(s) images.
|
||||
// We could get rid of wildcarding HTTP and only allow RPC server URL.
|
||||
// (http required for local dapps icons)
|
||||
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
|
||||
// Allow style from data: blob: and HTTPS.
|
||||
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
|
||||
// Allow fonts from data: and HTTPS.
|
||||
b"font-src 'self' data: https:;".to_vec(),
|
||||
// Allow inline scripts and scripts eval (webpack/jsconsole)
|
||||
{
|
||||
let script_src = embeddable_on.as_ref()
|
||||
.map(|e| e.extra_script_src.iter()
|
||||
.map(|&(ref host, port)| address(host, port))
|
||||
.join(" ")
|
||||
).unwrap_or_default();
|
||||
format!(
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' {};",
|
||||
script_src
|
||||
).into_bytes()
|
||||
},
|
||||
// Same restrictions as script-src with additional
|
||||
// blob: that is required for camera access (worker)
|
||||
b"worker-src 'self' 'unsafe-inline' 'unsafe-eval' https: blob:;".to_vec(),
|
||||
// Restrict everything else to the same origin.
|
||||
b"default-src 'self';".to_vec(),
|
||||
// Run in sandbox mode (although it's not fully safe since we allow same-origin and script)
|
||||
b"sandbox allow-same-origin allow-forms allow-modals allow-popups allow-presentation allow-scripts;".to_vec(),
|
||||
// Disallow subitting forms from any dapps
|
||||
b"form-action 'none';".to_vec(),
|
||||
// Never allow mixed content
|
||||
b"block-all-mixed-content;".to_vec(),
|
||||
// Specify if the site can be embedded.
|
||||
match embeddable_on {
|
||||
Some(ref embed) => {
|
||||
let std = address(&embed.host, embed.port);
|
||||
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
|
||||
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
|
||||
|
||||
let mut ancestors = vec![std, domain, proxy]
|
||||
.into_iter()
|
||||
.chain(embed.extra_embed_on
|
||||
.iter()
|
||||
.map(|&(ref host, port)| address(host, port))
|
||||
);
|
||||
|
||||
let ancestors = if embed.host == "127.0.0.1" {
|
||||
let localhost = address("localhost", embed.port);
|
||||
ancestors.chain(iter::once(localhost)).join(" ")
|
||||
} else {
|
||||
ancestors.join(" ")
|
||||
};
|
||||
|
||||
format!("frame-ancestors {};", ancestors)
|
||||
},
|
||||
None => format!("frame-ancestors 'self';"),
|
||||
}.into_bytes(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -153,3 +87,4 @@ pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) ->
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -24,7 +24,6 @@ use hyper::mime::Mime;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
use Embeddable;
|
||||
|
||||
const BUFFER_SIZE: usize = 1024;
|
||||
|
||||
@@ -34,11 +33,11 @@ pub struct StreamingHandler<R: io::Read> {
|
||||
status: StatusCode,
|
||||
content: io::BufReader<R>,
|
||||
mimetype: Mime,
|
||||
safe_to_embed_on: Embeddable,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl<R: io::Read> StreamingHandler<R> {
|
||||
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
|
||||
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
StreamingHandler {
|
||||
buffer: [0; BUFFER_SIZE],
|
||||
buffer_leftover: 0,
|
||||
@@ -69,7 +68,7 @@ impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
res.set_status(self.status);
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
|
||||
499
dapps/src/lib.rs
499
dapps/src/lib.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,51 +15,72 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore Webapplications for Parity
|
||||
//! ```
|
||||
//! extern crate jsonrpc_core;
|
||||
//! extern crate ethcore_dapps;
|
||||
//!
|
||||
//! use std::sync::Arc;
|
||||
//! use jsonrpc_core::IoHandler;
|
||||
//! use ethcore_dapps::*;
|
||||
//!
|
||||
//! struct SayHello;
|
||||
//! impl MethodCommand for SayHello {
|
||||
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
|
||||
//! Ok(Value::String("hello".to_string()))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let io = IoHandler::new();
|
||||
//! io.add_method("say_hello", SayHello);
|
||||
//! let _server = Server::start_unsecure_http(
|
||||
//! &"127.0.0.1:3030".parse().unwrap(),
|
||||
//! Arc::new(io)
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature="nightly", feature(plugin))]
|
||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||
|
||||
extern crate base32;
|
||||
extern crate futures;
|
||||
extern crate linked_hash_map;
|
||||
extern crate mime_guess;
|
||||
extern crate rand;
|
||||
extern crate rustc_hex;
|
||||
extern crate hyper;
|
||||
extern crate time;
|
||||
extern crate url as url_lib;
|
||||
extern crate unicase;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate time;
|
||||
extern crate unicase;
|
||||
extern crate url as url_lib;
|
||||
extern crate zip;
|
||||
|
||||
extern crate rand;
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
|
||||
extern crate mime_guess;
|
||||
extern crate rustc_serialize;
|
||||
extern crate ethcore_rpc;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate fetch;
|
||||
extern crate node_health;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
extern crate parity_hash_fetch as hash_fetch;
|
||||
extern crate linked_hash_map;
|
||||
extern crate fetch;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
extern crate futures;
|
||||
extern crate parity_reactor;
|
||||
extern crate parity_ui;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate mime;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
mod endpoint;
|
||||
mod apps;
|
||||
mod page;
|
||||
mod router;
|
||||
mod handlers;
|
||||
mod rpc;
|
||||
mod api;
|
||||
mod proxypac;
|
||||
mod url;
|
||||
@@ -67,197 +88,306 @@ mod web;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use jsonrpc_http_server::{self as http, hyper, Origin};
|
||||
|
||||
use fetch::Fetch;
|
||||
use node_health::NodeHealth;
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use fetch::{Fetch, Client as FetchClient};
|
||||
use jsonrpc_core::{IoHandler, IoDelegate};
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use ethcore_rpc::Extendable;
|
||||
use parity_reactor::Remote;
|
||||
|
||||
pub use hash_fetch::urlhint::ContractClient;
|
||||
pub use node_health::SyncStatus;
|
||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
|
||||
/// Indicates sync status
|
||||
pub trait SyncStatus: Send + Sync {
|
||||
/// Returns true if there is a major sync happening.
|
||||
fn is_major_importing(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
|
||||
fn is_major_importing(&self) -> bool { self() }
|
||||
}
|
||||
|
||||
/// Validates Web Proxy tokens
|
||||
pub trait WebProxyTokens: Send + Sync {
|
||||
/// Should return a domain allowed to be accessed by this token or `None` if the token is not valid
|
||||
fn domain(&self, token: &str) -> Option<Origin>;
|
||||
/// Should return true if token is a valid web proxy access token.
|
||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool;
|
||||
}
|
||||
|
||||
impl<F> WebProxyTokens for F where F: Fn(String) -> Option<Origin> + Send + Sync {
|
||||
fn domain(&self, token: &str) -> Option<Origin> { self(token.to_owned()) }
|
||||
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool { self(token.to_owned()) }
|
||||
}
|
||||
|
||||
/// Current supported endpoints.
|
||||
pub struct Endpoints {
|
||||
endpoints: endpoint::Endpoints,
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
handler: Arc<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: Option<T>,
|
||||
}
|
||||
|
||||
impl Endpoints {
|
||||
/// Returns a current list of app endpoints.
|
||||
pub fn list(&self) -> Vec<apps::App> {
|
||||
self.endpoints.iter().filter_map(|(ref k, ref e)| {
|
||||
e.info().map(|ref info| apps::App::from_info(k, info))
|
||||
}).collect()
|
||||
impl<T: Fetch> Extendable for ServerBuilder<T> {
|
||||
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
||||
self.handler.add_delegate(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dapps server as `jsonrpc-http-server` request middleware.
|
||||
pub struct Middleware {
|
||||
router: router::Router,
|
||||
endpoints: endpoint::Endpoints,
|
||||
impl ServerBuilder {
|
||||
/// Construct new dapps server
|
||||
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path.as_ref().to_owned(),
|
||||
extra_dapps: vec![],
|
||||
handler: Arc::new(IoHandler::new()),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(|| false),
|
||||
web_proxy_tokens: Arc::new(|_| false),
|
||||
signer_address: None,
|
||||
remote: remote,
|
||||
fetch: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Middleware {
|
||||
/// Get local endpoints handle.
|
||||
pub fn endpoints(&self) -> Endpoints {
|
||||
Endpoints {
|
||||
endpoints: self.endpoints.clone(),
|
||||
impl<T: Fetch> ServerBuilder<T> {
|
||||
/// Set a fetch client to use.
|
||||
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
|
||||
ServerBuilder {
|
||||
dapps_path: self.dapps_path,
|
||||
extra_dapps: vec![],
|
||||
handler: self.handler,
|
||||
registrar: self.registrar,
|
||||
sync_status: self.sync_status,
|
||||
web_proxy_tokens: self.web_proxy_tokens,
|
||||
signer_address: self.signer_address,
|
||||
remote: self.remote,
|
||||
fetch: Some(fetch),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new middleware for UI server.
|
||||
pub fn ui<F: Fetch>(
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
dapps_domain: &str,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status.clone(),
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
).embeddable_on(None).allow_dapps(false));
|
||||
let special = {
|
||||
let mut special = special_endpoints(
|
||||
health,
|
||||
content_fetcher.clone(),
|
||||
remote.clone(),
|
||||
);
|
||||
special.insert(router::SpecialEndpoint::Home, Some(apps::ui()));
|
||||
special
|
||||
};
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
None,
|
||||
special,
|
||||
None,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
/// Change default sync status.
|
||||
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
|
||||
self.sync_status = status;
|
||||
self
|
||||
}
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: Default::default(),
|
||||
/// Change default web proxy tokens validator.
|
||||
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
|
||||
self.web_proxy_tokens = tokens;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change default signer port.
|
||||
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
|
||||
self.signer_address = signer_address;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change extra dapps paths (apart from `dapps_path`)
|
||||
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
|
||||
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Asynchronously start server with no authentication,
|
||||
/// returns result with `Server` handle on success or an error.
|
||||
pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
NoAuth,
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.extra_dapps.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
self.web_proxy_tokens.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch_client()?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||
/// return result with `Server` handle on success or an error.
|
||||
pub fn start_basic_auth_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.extra_dapps.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
self.web_proxy_tokens.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch_client()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn fetch_client(&self) -> Result<T, ServerError> {
|
||||
match self.fetch.clone() {
|
||||
Some(fetch) => Ok(fetch),
|
||||
None => T::new().map_err(|_| ServerError::FetchInitialization),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
server: Option<hyper::server::Listening>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Returns a list of allowed hosts or `None` if all hosts are allowed.
|
||||
fn allowed_hosts(hosts: Option<Vec<String>>, bind_address: String) -> Option<Vec<String>> {
|
||||
let mut allowed = Vec::new();
|
||||
|
||||
match hosts {
|
||||
Some(hosts) => allowed.extend_from_slice(&hosts),
|
||||
None => return None,
|
||||
}
|
||||
|
||||
// Add localhost domain as valid too if listening on loopback interface.
|
||||
allowed.push(bind_address.replace("127.0.0.1", "localhost").into());
|
||||
allowed.push(bind_address.into());
|
||||
Some(allowed)
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
||||
match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}", address(signer_address)),
|
||||
],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new Dapps server middleware.
|
||||
pub fn dapps<F: Fetch>(
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
ui_address: Option<(String, u16)>,
|
||||
extra_embed_on: Vec<(String, u16)>,
|
||||
extra_script_src: Vec<(String, u16)>,
|
||||
fn start_http<A: Authorization + 'static, F: Fetch>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
dapps_domain: &str,
|
||||
signer_address: Option<(String, u16)>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let embeddable = as_embeddable(ui_address, extra_embed_on, extra_script_src, dapps_domain);
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status.clone(),
|
||||
sync_status,
|
||||
signer_address.clone(),
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
).embeddable_on(embeddable.clone()).allow_dapps(true));
|
||||
let endpoints = apps::all_endpoints(
|
||||
));
|
||||
let endpoints = Arc::new(apps::all_endpoints(
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
dapps_domain,
|
||||
embeddable.clone(),
|
||||
signer_address.clone(),
|
||||
web_proxy_tokens,
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
);
|
||||
));
|
||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
||||
|
||||
let special = {
|
||||
let mut special = special_endpoints(
|
||||
health,
|
||||
content_fetcher.clone(),
|
||||
remote.clone(),
|
||||
);
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Home,
|
||||
Some(apps::ui_redirection(embeddable.clone())),
|
||||
router::SpecialEndpoint::Api,
|
||||
api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone())
|
||||
);
|
||||
special
|
||||
};
|
||||
});
|
||||
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
|
||||
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
Some(endpoints.clone()),
|
||||
special,
|
||||
embeddable,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
hyper::Server::http(addr)?
|
||||
.handle(move |ctrl| router::Router::new(
|
||||
ctrl,
|
||||
signer_address.clone(),
|
||||
content_fetcher.clone(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
authorization.clone(),
|
||||
hosts.clone(),
|
||||
))
|
||||
.map(|(l, srv)| {
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: endpoints,
|
||||
::std::thread::spawn(move || {
|
||||
srv.run();
|
||||
});
|
||||
|
||||
Server {
|
||||
server: Some(l),
|
||||
panic_handler: panic_handler,
|
||||
}
|
||||
})
|
||||
.map_err(ServerError::from)
|
||||
}
|
||||
|
||||
/// Set callback for panics.
|
||||
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
|
||||
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns address that this server is bound to.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
self.server.as_ref()
|
||||
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
|
||||
.addrs()
|
||||
.first()
|
||||
.expect("You cannot start the server without binding to at least one address; qed")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.server.take().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapp Server startup error
|
||||
#[derive(Debug)]
|
||||
pub enum ServerError {
|
||||
/// Wrapped `std::io::Error`
|
||||
IoError(std::io::Error),
|
||||
/// Other `hyper` error
|
||||
Other(hyper::error::Error),
|
||||
/// Fetch service initialization error
|
||||
FetchInitialization,
|
||||
}
|
||||
|
||||
impl From<hyper::error::Error> for ServerError {
|
||||
fn from(err: hyper::error::Error) -> Self {
|
||||
match err {
|
||||
hyper::error::Error::Io(e) => ServerError::IoError(e),
|
||||
e => ServerError::Other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl http::RequestMiddleware for Middleware {
|
||||
fn on_request(&self, req: &hyper::server::Request<hyper::net::HttpStream>, control: &hyper::Control) -> http::RequestMiddlewareAction {
|
||||
self.router.on_request(req, control)
|
||||
}
|
||||
}
|
||||
|
||||
fn special_endpoints(
|
||||
health: NodeHealth,
|
||||
content_fetcher: Arc<apps::fetcher::Fetcher>,
|
||||
remote: Remote,
|
||||
) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, None);
|
||||
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
|
||||
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(
|
||||
content_fetcher,
|
||||
health,
|
||||
remote,
|
||||
)));
|
||||
special
|
||||
}
|
||||
|
||||
fn address(host: &str, port: u16) -> String {
|
||||
format!("{}:{}", host, port)
|
||||
}
|
||||
|
||||
fn as_embeddable(
|
||||
ui_address: Option<(String, u16)>,
|
||||
extra_embed_on: Vec<(String, u16)>,
|
||||
extra_script_src: Vec<(String, u16)>,
|
||||
dapps_domain: &str,
|
||||
) -> Option<ParentFrameSettings> {
|
||||
ui_address.map(|(host, port)| ParentFrameSettings {
|
||||
host,
|
||||
port,
|
||||
extra_embed_on,
|
||||
extra_script_src,
|
||||
dapps_domain: dapps_domain.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Random filename
|
||||
fn random_filename() -> String {
|
||||
use ::rand::Rng;
|
||||
@@ -265,19 +395,40 @@ fn random_filename() -> String {
|
||||
rng.gen_ascii_chars().take(12).collect()
|
||||
}
|
||||
|
||||
type Embeddable = Option<ParentFrameSettings>;
|
||||
|
||||
/// Parent frame host and port allowed to embed the content.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParentFrameSettings {
|
||||
/// Hostname
|
||||
pub host: String,
|
||||
/// Port
|
||||
pub port: u16,
|
||||
/// Additional URLs the dapps can be embedded on.
|
||||
pub extra_embed_on: Vec<(String, u16)>,
|
||||
/// Additional URLs the dapp scripts can be loaded from.
|
||||
pub extra_script_src: Vec<(String, u16)>,
|
||||
/// Dapps Domain (web3.site)
|
||||
pub dapps_domain: String,
|
||||
fn address(address: (String, u16)) -> String {
|
||||
format!("{}:{}", address.0, address.1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod util_tests {
|
||||
use super::Server;
|
||||
|
||||
#[test]
|
||||
fn should_return_allowed_hosts() {
|
||||
// given
|
||||
let bind_address = "127.0.0.1".to_owned();
|
||||
|
||||
// when
|
||||
let all = Server::allowed_hosts(None, bind_address.clone());
|
||||
let address = Server::allowed_hosts(Some(Vec::new()), bind_address.clone());
|
||||
let some = Server::allowed_hosts(Some(vec!["ethcore.io".into()]), bind_address.clone());
|
||||
|
||||
// then
|
||||
assert_eq!(all, None);
|
||||
assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()]));
|
||||
assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_cors_domains() {
|
||||
// given
|
||||
|
||||
// when
|
||||
let none = Server::cors_domains(None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -18,7 +18,6 @@ use page::{handler, PageCache};
|
||||
use std::sync::Arc;
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use parity_dapps::{WebApp, File, Info};
|
||||
use Embeddable;
|
||||
|
||||
pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Content of the files
|
||||
@@ -26,9 +25,8 @@ pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Prefix to strip from the path (when `None` deducted from `app_id`)
|
||||
pub prefix: Option<String>,
|
||||
/// Safe to be loaded in frame by other origin. (use wisely!)
|
||||
safe_to_embed_on: Embeddable,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
info: EndpointInfo,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
@@ -40,20 +38,6 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `PageEndpoint` for builtin (compile time) Dapp.
|
||||
/// Instead of returning 404 this endpoint will always server index.html.
|
||||
pub fn with_fallback_to_index(app: T) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,21 +51,19 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new `PageEndpoint` which can be safely used in iframe
|
||||
/// even from different origin. It might be dangerous (clickjacking).
|
||||
/// Use wisely!
|
||||
pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
|
||||
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +76,7 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
Box::new(handler::PageHandler {
|
||||
app: BuiltinDapp::new(self.app.clone(), self.fallback_to_index_html),
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||
@@ -118,14 +100,12 @@ impl From<Info> for EndpointInfo {
|
||||
|
||||
struct BuiltinDapp<T: WebApp + 'static> {
|
||||
app: Arc<T>,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
||||
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
|
||||
fn new(app: Arc<T>) -> Self {
|
||||
BuiltinDapp {
|
||||
app: app,
|
||||
fallback_to_index_html: fallback_to_index_html,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,19 +114,13 @@ impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
|
||||
type DappFile = BuiltinDappFile<T>;
|
||||
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
||||
let file = |path| self.app.file(path).map(|_| {
|
||||
self.app.file(path).map(|_| {
|
||||
BuiltinDappFile {
|
||||
app: self.app.clone(),
|
||||
path: path.into(),
|
||||
write_pos: 0,
|
||||
}
|
||||
});
|
||||
let res = file(path);
|
||||
if self.fallback_to_index_html {
|
||||
res.or_else(|| file("index.html"))
|
||||
} else {
|
||||
res
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -24,7 +24,6 @@ use hyper::status::StatusCode;
|
||||
use hyper::{Decoder, Encoder, Next};
|
||||
use endpoint::EndpointPath;
|
||||
use handlers::{ContentHandler, add_security_headers};
|
||||
use {Embeddable};
|
||||
|
||||
/// Represents a file that can be sent to client.
|
||||
/// Implementation should keep track of bytes already sent internally.
|
||||
@@ -60,7 +59,7 @@ pub enum ServedFile<T: Dapp> {
|
||||
}
|
||||
|
||||
impl<T: Dapp> ServedFile<T> {
|
||||
pub fn new(embeddable_on: Embeddable) -> Self {
|
||||
pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
|
||||
ServedFile::Error(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
@@ -103,7 +102,7 @@ pub struct PageHandler<T: Dapp> {
|
||||
/// Requested path.
|
||||
pub path: EndpointPath,
|
||||
/// Flag indicating if the file can be safely embeded (put in iframe).
|
||||
pub safe_to_embed_on: Embeddable,
|
||||
pub safe_to_embed_on: Option<(String, u16)>,
|
||||
/// Cache settings for this page.
|
||||
pub cache: PageCache,
|
||||
}
|
||||
@@ -175,7 +174,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
}
|
||||
|
||||
// Security headers:
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
@@ -253,7 +252,6 @@ fn should_extract_path_with_appid() {
|
||||
prefix: None,
|
||||
path: EndpointPath {
|
||||
app_id: "app".to_owned(),
|
||||
app_params: vec![],
|
||||
host: "".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -21,7 +21,6 @@ use std::path::{Path, PathBuf};
|
||||
use page::handler::{self, PageCache, PageHandlerWaiting};
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use mime::Mime;
|
||||
use Embeddable;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalPageEndpoint {
|
||||
@@ -29,11 +28,11 @@ pub struct LocalPageEndpoint {
|
||||
mime: Option<Mime>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_on: Embeddable,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl LocalPageEndpoint {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: None,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -18,36 +18,36 @@
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::HOME_PAGE;
|
||||
use {address, Embeddable};
|
||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
use address;
|
||||
|
||||
pub struct ProxyPac {
|
||||
embeddable: Embeddable,
|
||||
dapps_domain: String,
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac { embeddable, dapps_domain })
|
||||
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac {
|
||||
signer_address: signer_address
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for ProxyPac {
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
let ui = self.embeddable
|
||||
.as_ref()
|
||||
.map(|ref parent| address(&parent.host, parent.port))
|
||||
let signer = self.signer_address.clone()
|
||||
.map(address)
|
||||
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
|
||||
|
||||
let content = format!(
|
||||
r#"
|
||||
function FindProxyForURL(url, host) {{
|
||||
if (shExpMatch(host, "{0}.{1}"))
|
||||
if (shExpMatch(host, "{0}{1}"))
|
||||
{{
|
||||
return "PROXY {4}";
|
||||
}}
|
||||
|
||||
if (shExpMatch(host, "*.{1}"))
|
||||
if (shExpMatch(host, "*{1}"))
|
||||
{{
|
||||
return "PROXY {2}:{3}";
|
||||
}}
|
||||
@@ -55,7 +55,7 @@ function FindProxyForURL(url, host) {{
|
||||
return "DIRECT";
|
||||
}}
|
||||
"#,
|
||||
HOME_PAGE, self.dapps_domain, path.host, path.port, ui);
|
||||
HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer);
|
||||
|
||||
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||
}
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Router implementation
|
||||
//! Dispatch requests to proper application.
|
||||
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, header, Control};
|
||||
use hyper::net::HttpStream;
|
||||
use jsonrpc_http_server as http;
|
||||
|
||||
use apps;
|
||||
use apps::fetcher::Fetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
|
||||
use handlers;
|
||||
use Embeddable;
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||
pub enum SpecialEndpoint {
|
||||
Rpc,
|
||||
Api,
|
||||
Utils,
|
||||
Home,
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Router {
|
||||
endpoints: Option<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
embeddable_on: Embeddable,
|
||||
dapps_domain: String,
|
||||
}
|
||||
|
||||
impl http::RequestMiddleware for Router {
|
||||
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = handlers::extract_url(req);
|
||||
let endpoint = extract_endpoint(&url, &self.dapps_domain);
|
||||
let referer = extract_referer_endpoint(req, &self.dapps_domain);
|
||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
||||
let is_origin_set = req.headers().get::<header::Origin>().is_some();
|
||||
let is_get_request = *req.method() == hyper::Method::Get;
|
||||
let is_head_request = *req.method() == hyper::Method::Head;
|
||||
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
let control = control.clone();
|
||||
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
|
||||
let handler: Option<Box<Handler>> = match (endpoint.0, endpoint.1, referer) {
|
||||
// Handle invalid web requests that we can recover from
|
||||
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
||||
if referer.app_id == apps::WEB_PATH
|
||||
&& self.endpoints.as_ref().map(|ep| ep.contains_key(apps::WEB_PATH)).unwrap_or(false)
|
||||
&& !is_web_endpoint(path)
|
||||
=>
|
||||
{
|
||||
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
|
||||
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
|
||||
let base = referer_url.path[..len].join("/");
|
||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
||||
Some(handlers::Redirection::boxed(&format!("/{}/{}", base, requested)))
|
||||
},
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
|
||||
trace!(target: "dapps", "Resolving to special endpoint.");
|
||||
self.special.get(endpoint)
|
||||
.expect("special known to contain key; qed")
|
||||
.as_ref()
|
||||
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _, _) if self.endpoints.as_ref().map(|ep| ep.contains_key(&path.app_id)).unwrap_or(false) => {
|
||||
trace!(target: "dapps", "Resolving to local/builtin dapp.");
|
||||
Some(self.endpoints
|
||||
.as_ref()
|
||||
.expect("endpoints known to be set; qed")
|
||||
.get(&path.app_id)
|
||||
.expect("endpoints known to contain key; qed")
|
||||
.to_async_handler(path.clone(), control))
|
||||
},
|
||||
// Try to resolve and fetch the dapp
|
||||
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||
Some(self.fetch.to_async_handler(path.clone(), control))
|
||||
},
|
||||
// 404 for non-existent content (only if serving endpoints and not homepage)
|
||||
(Some(ref path), _, _)
|
||||
if (is_get_request || is_head_request)
|
||||
&& self.endpoints.is_some()
|
||||
&& path.app_id != apps::HOME_PAGE
|
||||
=>
|
||||
{
|
||||
trace!(target: "dapps", "Resolving to 404.");
|
||||
Some(Box::new(handlers::ContentHandler::error(
|
||||
hyper::StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested content was not found.",
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
)))
|
||||
},
|
||||
// Any other GET|HEAD requests to home page.
|
||||
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
|
||||
self.special.get(&SpecialEndpoint::Home)
|
||||
.expect("special known to contain key; qed")
|
||||
.as_ref()
|
||||
.map(|special| special.to_async_handler(Default::default(), control))
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
trace!(target: "dapps", "Resolving to RPC call.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
match handler {
|
||||
Some(handler) => http::RequestMiddlewareAction::Respond {
|
||||
should_validate_hosts: !is_utils,
|
||||
handler: handler,
|
||||
},
|
||||
None => http::RequestMiddlewareAction::Proceed {
|
||||
should_continue_on_invalid_cors: !is_origin_set,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Router {
|
||||
pub fn new(
|
||||
content_fetcher: Arc<Fetcher>,
|
||||
endpoints: Option<Endpoints>,
|
||||
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
|
||||
embeddable_on: Embeddable,
|
||||
dapps_domain: String,
|
||||
) -> Self {
|
||||
Router {
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
embeddable_on: embeddable_on,
|
||||
dapps_domain: format!(".{}", dapps_domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
|
||||
match *path {
|
||||
Some(ref path) if path.app_id == apps::WEB_PATH => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
|
||||
let referer = req.headers().get::<header::Referer>();
|
||||
|
||||
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
||||
url.and_then(|url| {
|
||||
let option = Some(url);
|
||||
extract_url_referer_endpoint(&option, dapps_domain).or_else(|| {
|
||||
extract_endpoint(&option, dapps_domain).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
|
||||
let query = url.as_ref().and_then(|url| url.query.as_ref());
|
||||
match (url, query) {
|
||||
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
|
||||
let referer_url = format!("http://{}:{}/{}", url.host, url.port, &query[apps::URL_REFERER.len()..]);
|
||||
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
|
||||
|
||||
let referer_url = Url::parse(&referer_url).ok();
|
||||
extract_endpoint(&referer_url, dapps_domain).0.map(|endpoint| {
|
||||
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>, dapps_domain: &str) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
return SpecialEndpoint::None;
|
||||
}
|
||||
|
||||
match url.path[0].as_ref() {
|
||||
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
||||
apps::API_PATH => SpecialEndpoint::Api,
|
||||
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
||||
apps::HOME_PAGE => SpecialEndpoint::Home,
|
||||
_ => SpecialEndpoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
match *url {
|
||||
Some(ref url) => match url.host {
|
||||
Host::Domain(ref domain) if domain.ends_with(dapps_domain) => {
|
||||
let id = &domain[0..(domain.len() - dapps_domain.len())];
|
||||
let (id, params) = if let Some(split) = id.rfind('.') {
|
||||
let (params, id) = id.split_at(split);
|
||||
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
|
||||
} else {
|
||||
(id.to_owned(), url.path.clone())
|
||||
};
|
||||
|
||||
(Some(EndpointPath {
|
||||
app_id: id,
|
||||
app_params: params,
|
||||
host: domain.clone(),
|
||||
port: url.port,
|
||||
using_dapps_domains: true,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ if url.path.len() > 1 => {
|
||||
let id = url.path[0].to_owned();
|
||||
(Some(EndpointPath {
|
||||
app_id: id,
|
||||
app_params: url.path[1..].to_vec(),
|
||||
host: format!("{}", url.host),
|
||||
port: url.port,
|
||||
using_dapps_domains: false,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ => (None, special_endpoint(url)),
|
||||
},
|
||||
_ => (None, SpecialEndpoint::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_endpoint() {
|
||||
let dapps_domain = ".web3.site";
|
||||
assert_eq!(extract_endpoint(&None, dapps_domain), (None, SpecialEndpoint::None));
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["index.html".to_owned()],
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "rpc".to_owned(),
|
||||
app_params: vec!["".to_owned()],
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
|
||||
host: "my.status.web3.site".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Utils)
|
||||
);
|
||||
|
||||
// By Subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["test.html".to_owned()],
|
||||
host: "status.web3.site".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// RPC by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
|
||||
host: "my.status.web3.site".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
// API by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok(), dapps_domain),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
app_params: vec!["my".to_owned(), "api".into(), "".into()],
|
||||
host: "my.status.web3.site".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Api)
|
||||
);
|
||||
}
|
||||
106
dapps/src/router/auth.rs
Normal file
106
dapps/src/router/auth.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! HTTP Authorization implementations
|
||||
|
||||
use std::collections::HashMap;
|
||||
use hyper::{server, net, header, status};
|
||||
use endpoint::Handler;
|
||||
use handlers::{AuthRequiredHandler, ContentHandler};
|
||||
|
||||
/// Authorization result
|
||||
pub enum Authorized {
|
||||
/// Authorization was successful.
|
||||
Yes,
|
||||
/// Unsuccessful authorization. Handler for further work is returned.
|
||||
No(Box<Handler>),
|
||||
}
|
||||
|
||||
/// Authorization interface
|
||||
pub trait Authorization : Send + Sync {
|
||||
/// Checks if authorization is valid.
|
||||
fn is_authorized(&self, req: &server::Request<net::HttpStream>)-> Authorized;
|
||||
}
|
||||
|
||||
/// HTTP Basic Authorization handler
|
||||
pub struct HttpBasicAuth {
|
||||
users: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// No-authorization implementation (authorization disabled)
|
||||
pub struct NoAuth;
|
||||
|
||||
impl Authorization for NoAuth {
|
||||
fn is_authorized(&self, _req: &server::Request<net::HttpStream>)-> Authorized {
|
||||
Authorized::Yes
|
||||
}
|
||||
}
|
||||
|
||||
impl Authorization for HttpBasicAuth {
|
||||
fn is_authorized(&self, req: &server::Request<net::HttpStream>) -> Authorized {
|
||||
let auth = self.check_auth(&req);
|
||||
|
||||
match auth {
|
||||
Access::Denied => {
|
||||
Authorized::No(Box::new(ContentHandler::error(
|
||||
status::StatusCode::Unauthorized,
|
||||
"Unauthorized",
|
||||
"You need to provide valid credentials to access this page.",
|
||||
None,
|
||||
None,
|
||||
)))
|
||||
},
|
||||
Access::AuthRequired => {
|
||||
Authorized::No(Box::new(AuthRequiredHandler))
|
||||
},
|
||||
Access::Granted => {
|
||||
Authorized::Yes
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Access {
|
||||
Granted,
|
||||
Denied,
|
||||
AuthRequired,
|
||||
}
|
||||
|
||||
impl HttpBasicAuth {
|
||||
/// Creates `HttpBasicAuth` instance with only one user.
|
||||
pub fn single_user(username: &str, password: &str) -> Self {
|
||||
let mut users = HashMap::new();
|
||||
users.insert(username.to_owned(), password.to_owned());
|
||||
HttpBasicAuth {
|
||||
users: users
|
||||
}
|
||||
}
|
||||
|
||||
fn is_authorized(&self, username: &str, password: &str) -> bool {
|
||||
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
|
||||
}
|
||||
|
||||
fn check_auth(&self, req: &server::Request<net::HttpStream>) -> Access {
|
||||
match req.headers().get::<header::Authorization<header::Basic>>() {
|
||||
Some(&header::Authorization(
|
||||
header::Basic { ref username, password: Some(ref password) }
|
||||
)) if self.is_authorized(username, password) => Access::Granted,
|
||||
Some(_) => Access::Denied,
|
||||
None => Access::AuthRequired,
|
||||
}
|
||||
}
|
||||
}
|
||||
47
dapps/src/router/host_validation.rs
Normal file
47
dapps/src/router/host_validation.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use apps::DAPPS_DOMAIN;
|
||||
use hyper::{server, header, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
use jsonrpc_http_server::{is_host_header_valid};
|
||||
use handlers::ContentHandler;
|
||||
|
||||
pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String], endpoints: Vec<String>) -> bool {
|
||||
let mut endpoints = endpoints.iter()
|
||||
.map(|endpoint| format!("{}{}", endpoint, DAPPS_DOMAIN))
|
||||
.collect::<Vec<String>>();
|
||||
endpoints.extend_from_slice(allowed_hosts);
|
||||
|
||||
let header_valid = is_host_header_valid(request, &endpoints);
|
||||
|
||||
match (header_valid, request.headers().get::<header::Host>()) {
|
||||
(true, _) => true,
|
||||
(_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
|
||||
Box::new(ContentHandler::error(StatusCode::Forbidden,
|
||||
"Current Host Is Disallowed",
|
||||
"You are trying to access your node using incorrect address.",
|
||||
Some("Use allowed URL or specify different <code>hosts</code> CLI options."),
|
||||
None,
|
||||
))
|
||||
}
|
||||
358
dapps/src/router/mod.rs
Normal file
358
dapps/src/router/mod.rs
Normal file
@@ -0,0 +1,358 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Router implementation
|
||||
//! Processes request handling authorization and dispatching it to proper application.
|
||||
|
||||
pub mod auth;
|
||||
mod host_validation;
|
||||
|
||||
use address;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
use apps::{self, DAPPS_DOMAIN};
|
||||
use apps::fetcher::Fetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use handlers::{self, Redirection, ContentHandler};
|
||||
use self::auth::{Authorization, Authorized};
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||
pub enum SpecialEndpoint {
|
||||
Rpc,
|
||||
Api,
|
||||
Utils,
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Router<A: Authorization + 'static> {
|
||||
control: Option<Control>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
handler: Box<server::Handler<HttpStream> + Send>,
|
||||
}
|
||||
|
||||
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = handlers::extract_url(&req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
let referer = extract_referer_endpoint(&req);
|
||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
||||
let is_get_request = *req.method() == hyper::Method::Get;
|
||||
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
// Validate Host header
|
||||
if let Some(ref hosts) = self.allowed_hosts {
|
||||
trace!(target: "dapps", "Validating host headers against: {:?}", hosts);
|
||||
let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect());
|
||||
if !is_valid {
|
||||
debug!(target: "dapps", "Rejecting invalid host header.");
|
||||
self.handler = host_validation::host_invalid_response();
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "dapps", "Checking authorization.");
|
||||
// Check authorization
|
||||
let auth = self.authorization.is_authorized(&req);
|
||||
if let Authorized::No(handler) = auth {
|
||||
debug!(target: "dapps", "Authorization denied.");
|
||||
self.handler = handler;
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
|
||||
|
||||
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
|
||||
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
|
||||
self.handler = match (endpoint.0, endpoint.1, referer) {
|
||||
// Handle invalid web requests that we can recover from
|
||||
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
||||
if referer.app_id == apps::WEB_PATH
|
||||
&& self.endpoints.contains_key(apps::WEB_PATH)
|
||||
&& !is_web_endpoint(path)
|
||||
=>
|
||||
{
|
||||
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
|
||||
// TODO [ToDr] Some nice util for this!
|
||||
let using_domain = if referer.using_dapps_domains { 0 } else { 1 };
|
||||
let len = cmp::min(referer_url.path.len(), using_domain + 3); // token + protocol + hostname
|
||||
let base = referer_url.path[..len].join("/");
|
||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
||||
Redirection::boxed(&format!("/{}/{}", base, requested))
|
||||
},
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
|
||||
trace!(target: "dapps", "Resolving to special endpoint.");
|
||||
self.special.get(endpoint)
|
||||
.expect("special known to contain key; qed")
|
||||
.to_async_handler(path.clone().unwrap_or_default(), control)
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to local/builtin dapp.");
|
||||
self.endpoints.get(&path.app_id)
|
||||
.expect("endpoints known to contain key; qed")
|
||||
.to_async_handler(path.clone(), control)
|
||||
},
|
||||
// Try to resolve and fetch the dapp
|
||||
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||
self.fetch.to_async_handler(path.clone(), control)
|
||||
},
|
||||
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached
|
||||
// (in the past we used 301 instead of 302)
|
||||
// It should be safe to remove it in (near) future.
|
||||
//
|
||||
// 404 for non-existent content
|
||||
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
|
||||
trace!(target: "dapps", "Resolving to 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested content was not found.",
|
||||
None,
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
},
|
||||
// Redirect any other GET request to signer.
|
||||
_ if is_get_request => {
|
||||
if let Some(signer_address) = self.signer_address.clone() {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
Redirection::boxed(&format!("http://{}", address(signer_address)))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
trace!(target: "dapps", "Resolving to RPC call.");
|
||||
self.special.get(&SpecialEndpoint::Rpc)
|
||||
.expect("RPC endpoint always stored; qed")
|
||||
.to_async_handler(EndpointPath::default(), control)
|
||||
}
|
||||
};
|
||||
|
||||
// Delegate on_request to proper handler
|
||||
self.handler.on_request(req)
|
||||
}
|
||||
|
||||
/// This event occurs each time the `Request` is ready to be read from.
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
self.handler.on_request_readable(decoder)
|
||||
}
|
||||
|
||||
/// This event occurs after the first time this handled signals `Next::write()`.
|
||||
fn on_response(&mut self, response: &mut server::Response) -> Next {
|
||||
self.handler.on_response(response)
|
||||
}
|
||||
|
||||
/// This event occurs each time the `Response` is ready to be written to.
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
self.handler.on_response_writable(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Authorization> Router<A> {
|
||||
pub fn new(
|
||||
control: Control,
|
||||
signer_address: Option<(String, u16)>,
|
||||
content_fetcher: Arc<Fetcher>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
) -> Self {
|
||||
|
||||
let handler = special.get(&SpecialEndpoint::Utils)
|
||||
.expect("Utils endpoint always stored; qed")
|
||||
.to_handler(EndpointPath::default());
|
||||
Router {
|
||||
control: Some(control),
|
||||
signer_address: signer_address,
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
authorization: authorization,
|
||||
allowed_hosts: allowed_hosts,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
|
||||
match *path {
|
||||
Some(ref path) if path.app_id == apps::WEB_PATH => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>) -> Option<(EndpointPath, Url)> {
|
||||
let referer = req.headers().get::<header::Referer>();
|
||||
|
||||
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
||||
url.and_then(|url| {
|
||||
let option = Some(url);
|
||||
extract_url_referer_endpoint(&option).or_else(|| {
|
||||
extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)> {
|
||||
let query = url.as_ref().and_then(|url| url.query.as_ref());
|
||||
match (url, query) {
|
||||
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
|
||||
let referer_url = format!("http://{}:{}/{}", url.host, url.port, &query[apps::URL_REFERER.len()..]);
|
||||
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
|
||||
|
||||
let referer_url = Url::parse(&referer_url).ok();
|
||||
extract_endpoint(&referer_url).0.map(|endpoint| {
|
||||
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
return SpecialEndpoint::None;
|
||||
}
|
||||
|
||||
match url.path[0].as_ref() {
|
||||
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
||||
apps::API_PATH => SpecialEndpoint::Api,
|
||||
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
||||
_ => SpecialEndpoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
match *url {
|
||||
Some(ref url) => match url.host {
|
||||
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
||||
let len = domain.len() - DAPPS_DOMAIN.len();
|
||||
let id = domain[0..len].to_owned();
|
||||
|
||||
(Some(EndpointPath {
|
||||
app_id: id,
|
||||
host: domain.clone(),
|
||||
port: url.port,
|
||||
using_dapps_domains: true,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ if url.path.len() > 1 => {
|
||||
let id = url.path[0].clone();
|
||||
(Some(EndpointPath {
|
||||
app_id: id.clone(),
|
||||
host: format!("{}", url.host),
|
||||
port: url.port,
|
||||
using_dapps_domains: false,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ => (None, special_endpoint(url)),
|
||||
},
|
||||
_ => (None, SpecialEndpoint::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_endpoint() {
|
||||
assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None));
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "rpc".to_owned(),
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Utils)
|
||||
);
|
||||
|
||||
// By Subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// RPC by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
// API by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Api)
|
||||
);
|
||||
}
|
||||
144
dapps/src/rpc.rs
Normal file
144
dapps/src/rpc.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use hyper;
|
||||
|
||||
use jsonrpc_core::{IoHandler, ResponseHandler, Request, Response};
|
||||
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin, RpcHandler};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
|
||||
pub fn rpc(
|
||||
handler: Arc<IoHandler>,
|
||||
cors_domains: Vec<String>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: Arc::new(RpcMiddleware::new(handler)),
|
||||
panic_handler: panic_handler,
|
||||
cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
|
||||
allowed_hosts: None,
|
||||
})
|
||||
}
|
||||
|
||||
struct RpcEndpoint {
|
||||
handler: Arc<RpcMiddleware>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Endpoint for RpcEndpoint {
|
||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
||||
Box::new(ServerHandler::new(
|
||||
self.handler.clone(),
|
||||
self.cors_domain.clone(),
|
||||
self.allowed_hosts.clone(),
|
||||
panic_handler,
|
||||
control,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct RpcMiddleware {
|
||||
handler: Arc<IoHandler>,
|
||||
methods: Vec<String>,
|
||||
}
|
||||
|
||||
impl RpcMiddleware {
|
||||
fn new(handler: Arc<IoHandler>) -> Self {
|
||||
RpcMiddleware {
|
||||
handler: handler,
|
||||
methods: vec![
|
||||
"eth_accounts".into(),
|
||||
"eth_coinbase".into(),
|
||||
"parity_accountsInfo".into(),
|
||||
"parity_defaultAccount".into(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends additional parameter for specific calls.
|
||||
fn augment_request(&self, request: &mut Request, meta: Option<Meta>) {
|
||||
use jsonrpc_core::{Call, Params, to_value};
|
||||
|
||||
fn augment_call(call: &mut Call, meta: Option<&Meta>, methods: &Vec<String>) {
|
||||
match (call, meta) {
|
||||
(&mut Call::MethodCall(ref mut method_call), Some(meta)) if methods.contains(&method_call.method) => {
|
||||
let session = to_value(&meta.app_id);
|
||||
|
||||
let params = match method_call.params {
|
||||
Some(Params::Array(ref vec)) if vec.len() == 0 => Some(Params::Array(vec![session])),
|
||||
// invalid params otherwise
|
||||
_ => None,
|
||||
};
|
||||
|
||||
method_call.params = params;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match *request {
|
||||
Request::Single(ref mut call) => augment_call(call, meta.as_ref(), &self.methods),
|
||||
Request::Batch(ref mut vec) => {
|
||||
for mut call in vec {
|
||||
augment_call(call, meta.as_ref(), &self.methods)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Meta {
|
||||
app_id: String,
|
||||
}
|
||||
|
||||
impl RpcHandler for RpcMiddleware {
|
||||
type Metadata = Meta;
|
||||
|
||||
fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Option<Self::Metadata> {
|
||||
request.headers().get::<hyper::header::Referer>()
|
||||
.and_then(|referer| hyper::Url::parse(referer).ok())
|
||||
.and_then(|url| {
|
||||
url.path_segments()
|
||||
.and_then(|mut split| split.next())
|
||||
.map(|app_id| Meta {
|
||||
app_id: app_id.to_owned(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_request<H>(&self, request_str: &str, response_handler: H, meta: Option<Self::Metadata>) where
|
||||
H: ResponseHandler<Option<String>, Option<String>> + 'static
|
||||
{
|
||||
let handler = IoHandler::convert_handler(response_handler);
|
||||
let request = IoHandler::read_request(request_str);
|
||||
trace!(target: "rpc", "Request metadata: {:?}", meta);
|
||||
|
||||
match request {
|
||||
Ok(mut request) => {
|
||||
self.augment_request(&mut request, meta);
|
||||
self.handler.request_handler().handle_request(request, handler, None)
|
||||
},
|
||||
Err(error) => handler.send(Some(Response::from(error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -33,12 +33,35 @@ fn should_return_error() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#));
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_apps() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/apps HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert!(response.body.contains("Parity UI"), response.body);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_ping() {
|
||||
// given
|
||||
@@ -56,8 +79,8 @@ fn should_handle_ping() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert_eq!(response.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -79,7 +102,59 @@ fn should_try_to_resolve_dapp() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://127.0.0.1:18180\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"),
|
||||
"CORS header for signer missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers_for_home_parity() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"),
|
||||
"CORS header for home.parity missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
80
dapps/src/tests/authorization.rs
Normal file
80
dapps/src/tests/authorization.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_require_authorization() {
|
||||
// given
|
||||
let server = serve_with_auth("test", "test");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "WWW-Authenticate: Basic realm=\"Parity\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_reject_on_invalid_auth() {
|
||||
// given
|
||||
let server = serve_with_auth("test", "test");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
|
||||
assert!(response.body.contains("Unauthorized"), response.body);
|
||||
assert_eq!(response.headers_raw.contains("WWW-Authenticate"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_on_valid_auth() {
|
||||
// given
|
||||
let server = serve_with_auth("Aladdin", "OpenSesame");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /ui/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use devtools::http_client;
|
||||
use rustc_hex::FromHex;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use tests::helpers::{
|
||||
serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch,
|
||||
serve_with_registrar_and_fetch, serve_with_registrar_and_fetch_and_threads,
|
||||
@@ -31,7 +31,7 @@ fn should_resolve_dapp() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -52,7 +52,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -60,7 +60,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 503 Service Unavailable");
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ fn should_return_502_on_hash_mismatch() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.web3.site\r\n\
|
||||
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -112,7 +112,7 @@ fn should_return_error_for_invalid_dapp_zip() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -144,7 +144,7 @@ fn should_return_fetched_dapp_content() {
|
||||
let response1 = http_client::request(server.addr(),
|
||||
"\
|
||||
GET /index.html HTTP/1.1\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -152,7 +152,7 @@ fn should_return_fetched_dapp_content() {
|
||||
let response2 = http_client::request(server.addr(),
|
||||
"\
|
||||
GET /manifest.json HTTP/1.1\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -207,7 +207,7 @@ fn should_return_fetched_content() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -234,7 +234,7 @@ fn should_cache_content() {
|
||||
);
|
||||
let request_str = "\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
";
|
||||
@@ -265,7 +265,7 @@ fn should_not_request_content_twice() {
|
||||
);
|
||||
let request_str = "\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
";
|
||||
@@ -298,27 +298,16 @@ fn should_not_request_content_twice() {
|
||||
response2.assert_status("HTTP/1.1 200 OK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_and_decode_base32() {
|
||||
use base32;
|
||||
|
||||
let encoded = base32::encode(base32::Alphabet::Crockford, "token+https://parity.io".as_bytes());
|
||||
assert_eq!("EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY", &encoded);
|
||||
|
||||
let data = base32::decode(base32::Alphabet::Crockford, "EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY").unwrap();
|
||||
assert_eq!("token+https://parity.io", &String::from_utf8(data).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_stream_web_content() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
GET /web/token/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -332,108 +321,16 @@ fn should_stream_web_content() {
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_support_base32_encoded_web_urls() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /styles.css?test=123 HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_requested("https://parity.io/styles.css?test=123");
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_correctly_handle_long_label_when_splitted() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL", "https://contribution.melonport.com");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /styles.css?test=123 HTTP/1.1\r\n\
|
||||
Host: f1qprwk775k6am35a5wmpk3e9gnpgx3me1sk.mbsfcdqpwx3jd5h7ax39dxq2wvb5dhqpww3fe9t2wrvfdm.web.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_requested("https://contribution.melonport.com/styles.css?test=123");
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_support_base32_encoded_web_urls_as_path() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css?test=123 HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_requested("https://parity.io/styles.css?test=123");
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_on_non_whitelisted_domain() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://ethcore.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 400 Bad Request");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_on_invalid_token() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("test", "https://parity.io");
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
GET /web/invalidtoken/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
@@ -449,7 +346,7 @@ fn should_return_error_on_invalid_token() {
|
||||
#[test]
|
||||
fn should_return_error_on_invalid_protocol() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "ftp://parity.io");
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
@@ -469,15 +366,37 @@ fn should_return_error_on_invalid_protocol() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_non_get_requests() {
|
||||
fn should_redirect_if_trailing_slash_is_missing() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
GET /web/token/https/parity.io HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
response.assert_header("Location", "/web/token/https/parity.io/");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_non_get_requests() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /token/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: web.parity\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
@@ -496,7 +415,7 @@ fn should_disallow_non_get_requests() {
|
||||
#[test]
|
||||
fn should_fix_absolute_requests_based_on_referer() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
@@ -504,37 +423,14 @@ fn should_fix_absolute_requests_based_on_referer() {
|
||||
GET /styles.css HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Referer: http://localhost:8080/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
|
||||
Referer: http://localhost:8080/web/token/https/parity.io/\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fix_absolute_requests_based_on_referer_in_url() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /styles.css HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Referer: http://localhost:8080/?__referer=web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
|
||||
response.assert_header("Location", "/web/token/https/parity.io/styles.css");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -114,7 +114,7 @@ impl Fetch for FakeFetch {
|
||||
|
||||
let data = response.lock().take().unwrap_or(b"Some content");
|
||||
let cursor = io::Cursor::new(data);
|
||||
tx.send(fetch::Response::from_reader(cursor)).unwrap();
|
||||
tx.complete(fetch::Response::from_reader(cursor));
|
||||
});
|
||||
|
||||
rx.map_err(|_| fetch::Error::Aborted).boxed()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,21 +16,15 @@
|
||||
|
||||
use std::env;
|
||||
use std::str;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use env_logger::LogBuilder;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use jsonrpc_http_server::{self as http, Host, DomainsValidation};
|
||||
|
||||
use ServerBuilder;
|
||||
use Server;
|
||||
use fetch::Fetch;
|
||||
use devtools::http_client;
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use fetch::{Fetch, Client as FetchClient};
|
||||
use node_health::{NodeHealth, TimeChecker, CpuPool};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use {Middleware, SyncStatus, WebProxyTokens};
|
||||
|
||||
mod registrar;
|
||||
mod fetch;
|
||||
|
||||
@@ -39,13 +33,6 @@ use self::fetch::FakeFetch;
|
||||
|
||||
const SIGNER_PORT: u16 = 18180;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FakeSync(bool);
|
||||
impl SyncStatus for FakeSync {
|
||||
fn is_major_importing(&self) -> bool { self.0 }
|
||||
fn peers(&self) -> (usize, usize) { (0, 5) }
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
// Initialize logger
|
||||
if let Ok(log) = env::var("RUST_LOG") {
|
||||
@@ -55,7 +42,7 @@ fn init_logger() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
pub fn init_server<F, B>(hosts: Option<Vec<String>>, process: F, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||
B: Fetch,
|
||||
{
|
||||
@@ -63,35 +50,39 @@ pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server,
|
||||
let registrar = Arc::new(FakeRegistrar::new());
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
|
||||
let server = process(ServerBuilder::new(
|
||||
&dapps_path, registrar.clone(), remote,
|
||||
))
|
||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
|
||||
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap();
|
||||
(
|
||||
server,
|
||||
registrar,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serve_with_rpc(io: IoHandler) -> Server {
|
||||
init_server(|builder| builder, io, Remote::new_sync()).0
|
||||
pub fn serve_with_auth(user: &str, pass: &str) -> Server {
|
||||
init_logger();
|
||||
let registrar = Arc::new(FakeRegistrar::new());
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
ServerBuilder::new(&dapps_path, registrar.clone(), Remote::new_sync())
|
||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
|
||||
}
|
||||
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
|
||||
let hosts = hosts.map(|hosts| hosts.into_iter().map(Into::into).collect());
|
||||
init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0
|
||||
init_server(hosts, |builder| builder, Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync())
|
||||
init_server(None, |builder| builder, Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| {
|
||||
builder.sync_status(Arc::new(FakeSync(true)))
|
||||
}, Default::default(), Remote::new_sync())
|
||||
init_server(None, |builder| {
|
||||
builder.sync_status(Arc::new(|| true))
|
||||
}, Remote::new_sync())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
@@ -101,29 +92,27 @@ pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar
|
||||
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, reg) = init_server(move |builder| {
|
||||
let (server, reg) = init_server(None, move |builder| {
|
||||
builder.fetch(f.clone())
|
||||
}, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
|
||||
}, if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
|
||||
|
||||
(server, fetch, reg)
|
||||
}
|
||||
|
||||
pub fn serve_with_fetch(web_token: &'static str, domain: &'static str) -> (Server, FakeFetch) {
|
||||
pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, _) = init_server(move |builder| {
|
||||
let (server, _) = init_server(None, move |builder| {
|
||||
builder
|
||||
.fetch(f.clone())
|
||||
.web_proxy_tokens(Arc::new(move |token| {
|
||||
if &token == web_token { Some(domain.into()) } else { None }
|
||||
}))
|
||||
}, Default::default(), Remote::new_sync());
|
||||
.web_proxy_tokens(Arc::new(move |token| &token == web_token))
|
||||
}, Remote::new_sync());
|
||||
|
||||
(server, fetch)
|
||||
}
|
||||
|
||||
pub fn serve() -> Server {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync()).0
|
||||
init_server(None, |builder| builder, Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
@@ -136,173 +125,3 @@ pub fn assert_security_headers(headers: &[String]) {
|
||||
pub fn assert_security_headers_for_embed(headers: &[String]) {
|
||||
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
|
||||
}
|
||||
|
||||
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||
dapps_path: PathBuf,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
allowed_hosts: DomainsValidation<Host>,
|
||||
remote: Remote,
|
||||
fetch: Option<T>,
|
||||
}
|
||||
|
||||
impl ServerBuilder {
|
||||
/// Construct new dapps server
|
||||
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path.as_ref().to_owned(),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(FakeSync(false)),
|
||||
web_proxy_tokens: Arc::new(|_| None),
|
||||
signer_address: None,
|
||||
allowed_hosts: DomainsValidation::Disabled,
|
||||
remote: remote,
|
||||
fetch: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Fetch> ServerBuilder<T> {
|
||||
/// Set a fetch client to use.
|
||||
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
|
||||
ServerBuilder {
|
||||
dapps_path: self.dapps_path,
|
||||
registrar: self.registrar,
|
||||
sync_status: self.sync_status,
|
||||
web_proxy_tokens: self.web_proxy_tokens,
|
||||
signer_address: self.signer_address,
|
||||
allowed_hosts: self.allowed_hosts,
|
||||
remote: self.remote,
|
||||
fetch: Some(fetch),
|
||||
}
|
||||
}
|
||||
|
||||
/// Change default sync status.
|
||||
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
|
||||
self.sync_status = status;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change default web proxy tokens validator.
|
||||
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
|
||||
self.web_proxy_tokens = tokens;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change default signer port.
|
||||
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
|
||||
self.signer_address = signer_address;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change allowed hosts.
|
||||
/// `None` - All hosts are allowed
|
||||
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
|
||||
pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation<Host>) -> Self {
|
||||
self.allowed_hosts = allowed_hosts;
|
||||
self
|
||||
}
|
||||
|
||||
/// Asynchronously start server with no authentication,
|
||||
/// returns result with `Server` handle on success or an error.
|
||||
pub fn start_unsecured_http(self, addr: &SocketAddr, io: IoHandler) -> Result<Server, http::Error> {
|
||||
let fetch = self.fetch_client();
|
||||
Server::start_http(
|
||||
addr,
|
||||
io,
|
||||
self.allowed_hosts,
|
||||
self.signer_address,
|
||||
self.dapps_path,
|
||||
vec![],
|
||||
self.registrar,
|
||||
self.sync_status,
|
||||
self.web_proxy_tokens,
|
||||
self.remote,
|
||||
fetch,
|
||||
)
|
||||
}
|
||||
|
||||
fn fetch_client(&self) -> T {
|
||||
match self.fetch.clone() {
|
||||
Some(fetch) => fetch,
|
||||
None => T::new().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DAPPS_DOMAIN: &'static str = "web3.site";
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
server: Option<http::Server>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn start_http<F: Fetch>(
|
||||
addr: &SocketAddr,
|
||||
io: IoHandler,
|
||||
allowed_hosts: DomainsValidation<Host>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Result<Server, http::Error> {
|
||||
let health = NodeHealth::new(
|
||||
sync_status.clone(),
|
||||
TimeChecker::new::<String>(&[], CpuPool::new(1)),
|
||||
remote.clone(),
|
||||
);
|
||||
let middleware = Middleware::dapps(
|
||||
health,
|
||||
remote,
|
||||
signer_address,
|
||||
vec![],
|
||||
vec![],
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
DAPPS_DOMAIN.into(),
|
||||
registrar,
|
||||
sync_status,
|
||||
web_proxy_tokens,
|
||||
fetch,
|
||||
);
|
||||
|
||||
let mut allowed_hosts: Option<Vec<Host>> = allowed_hosts.into();
|
||||
allowed_hosts.as_mut().map(|mut hosts| {
|
||||
hosts.push(format!("http://*.{}:*", DAPPS_DOMAIN).into());
|
||||
hosts.push(format!("http://*.{}", DAPPS_DOMAIN).into());
|
||||
});
|
||||
|
||||
http::ServerBuilder::new(io)
|
||||
.request_middleware(middleware)
|
||||
.allowed_hosts(allowed_hosts.into())
|
||||
.cors(http::DomainsValidation::Disabled)
|
||||
.start_http(addr)
|
||||
.map(|server| Server {
|
||||
server: Some(server),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns address that this server is bound to.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
self.server.as_ref()
|
||||
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
|
||||
.addrs()
|
||||
.first()
|
||||
.expect("You cannot start the server without binding to at least one address; qed")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.server.take().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -17,7 +17,7 @@
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use rustc_hex::FromHex;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use util::{Bytes, Address, Mutex, H256, ToPretty};
|
||||
@@ -64,10 +64,9 @@ impl ContractClient for FakeRegistrar {
|
||||
Ok(REGISTRAR.parse().unwrap())
|
||||
}
|
||||
|
||||
fn call(&self, address: Address, data: Bytes) -> ::futures::BoxFuture<Bytes, String> {
|
||||
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||
let call = (address.to_hex(), data.to_hex());
|
||||
self.calls.lock().push(call.clone());
|
||||
let res = self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call));
|
||||
Box::new(::futures::future::done(res))
|
||||
self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -19,8 +19,8 @@
|
||||
mod helpers;
|
||||
|
||||
mod api;
|
||||
mod authorization;
|
||||
mod fetch;
|
||||
mod redirection;
|
||||
mod rpc;
|
||||
mod validation;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -32,27 +32,7 @@ fn should_redirect_to_home() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_with_domain() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: home.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
@@ -72,7 +52,27 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_for_users_with_cached_redirection() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /home/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ fn should_display_404_on_invalid_dapp() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
@@ -105,14 +105,14 @@ fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: invaliddapp.web3.site\r\n\
|
||||
Host: invaliddapp.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ fn should_serve_rpc() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -156,8 +156,8 @@ fn should_serve_rpc_at_slash_rpc() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
}
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@ fn should_serve_proxy_pac() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, "DB\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ fn should_serve_utils() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body.contains("function(){"), true);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use jsonrpc_core::{IoHandler, Value};
|
||||
|
||||
use tests::helpers::{serve_with_rpc, request};
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc() {
|
||||
// given
|
||||
let mut io = IoHandler::default();
|
||||
io.add_method("rpc_test", |_| {
|
||||
Ok(Value::String("Hello World!".into()))
|
||||
});
|
||||
let server = serve_with_rpc(io);
|
||||
|
||||
// when
|
||||
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
|
||||
let response = request(server, &format!(
|
||||
"\
|
||||
POST /rpc/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
\r\n\
|
||||
{}\r\n\
|
||||
",
|
||||
req.as_bytes().len(),
|
||||
req,
|
||||
));
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -34,7 +34,7 @@ fn should_reject_invalid_host() {
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
|
||||
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
|
||||
assert!(response.body.contains("Current Host Is Disallowed"), response.body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -66,7 +66,7 @@ fn should_serve_dapps_domains() {
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: ui.web3.site\r\n\
|
||||
Host: ui.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
@@ -97,3 +97,31 @@ fn should_allow_parity_utils_even_on_invalid_domain() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_return_cors_headers_for_rpc() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /rpc HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: null\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
!response.headers_raw.contains("Access-Control-Allow-Origin"),
|
||||
"CORS headers were not expected: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
108
dapps/src/web.rs
108
dapps/src/web.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -20,7 +20,6 @@ use std::sync::Arc;
|
||||
use fetch::{self, Fetch};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use base32;
|
||||
use hyper::{self, server, net, Next, Encoder, Decoder};
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
@@ -28,10 +27,12 @@ use apps;
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::{
|
||||
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
|
||||
StreamingHandler, extract_url,
|
||||
StreamingHandler, Redirection, extract_url,
|
||||
};
|
||||
use url::Url;
|
||||
use {Embeddable, WebProxyTokens};
|
||||
use WebProxyTokens;
|
||||
|
||||
pub type Embeddable = Option<(String, u16)>;
|
||||
|
||||
pub struct Web<F> {
|
||||
embeddable_on: Embeddable,
|
||||
@@ -41,17 +42,12 @@ pub struct Web<F> {
|
||||
}
|
||||
|
||||
impl<F: Fetch> Web<F> {
|
||||
pub fn boxed(
|
||||
embeddable_on: Embeddable,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Box<Endpoint> {
|
||||
pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc<WebProxyTokens>, remote: Remote, fetch: F) -> Box<Endpoint> {
|
||||
Box::new(Web {
|
||||
embeddable_on,
|
||||
web_proxy_tokens,
|
||||
remote,
|
||||
fetch,
|
||||
embeddable_on: embeddable_on,
|
||||
web_proxy_tokens: web_proxy_tokens,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -90,10 +86,9 @@ impl ContentValidator for WebInstaller {
|
||||
);
|
||||
if is_html {
|
||||
handler.set_initial_content(&format!(
|
||||
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
|
||||
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}")</script>"#,
|
||||
apps::UTILS_PATH,
|
||||
apps::URL_REFERER,
|
||||
apps::WEB_PATH,
|
||||
&self.referer,
|
||||
));
|
||||
}
|
||||
@@ -104,6 +99,7 @@ impl ContentValidator for WebInstaller {
|
||||
enum State<F: Fetch> {
|
||||
Initial,
|
||||
Error(ContentHandler),
|
||||
Redirecting(Redirection),
|
||||
Fetching(ContentFetcherHandler<WebInstaller, F>),
|
||||
}
|
||||
|
||||
@@ -118,36 +114,36 @@ struct WebHandler<F: Fetch> {
|
||||
}
|
||||
|
||||
impl<F: Fetch> WebHandler<F> {
|
||||
fn extract_target_url(&self, url: Option<Url>) -> Result<String, State<F>> {
|
||||
let token_and_url = self.path.app_params.get(0)
|
||||
.map(|encoded| encoded.replace('.', ""))
|
||||
.and_then(|encoded| base32::decode(base32::Alphabet::Crockford, &encoded.to_uppercase()))
|
||||
.and_then(|data| String::from_utf8(data).ok())
|
||||
.ok_or_else(|| State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest,
|
||||
"Invalid parameter",
|
||||
"Couldn't parse given parameter:",
|
||||
self.path.app_params.get(0).map(String::as_str),
|
||||
self.embeddable_on.clone()
|
||||
)))?;
|
||||
fn extract_target_url(&self, url: Option<Url>) -> Result<(String, String), State<F>> {
|
||||
let (path, query) = match url {
|
||||
Some(url) => (url.path, url.query),
|
||||
None => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut token_it = token_and_url.split('+');
|
||||
let token = token_it.next();
|
||||
let target_url = token_it.next();
|
||||
// Support domain based routing.
|
||||
let idx = match path.get(0).map(|m| m.as_ref()) {
|
||||
Some(apps::WEB_PATH) => 1,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// Check if token supplied in URL is correct.
|
||||
let domain = match token.and_then(|token| self.web_proxy_tokens.domain(token)) {
|
||||
Some(domain) => domain,
|
||||
match path.get(idx) {
|
||||
Some(ref token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Validate protocol
|
||||
let mut target_url = match target_url {
|
||||
Some(url) if url.starts_with("http://") || url.starts_with("https://") => url.to_owned(),
|
||||
let protocol = match path.get(idx + 1).map(|a| a.as_str()) {
|
||||
Some("http") => "http",
|
||||
Some("https") => "https",
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
|
||||
@@ -155,41 +151,28 @@ impl<F: Fetch> WebHandler<F> {
|
||||
}
|
||||
};
|
||||
|
||||
if !target_url.starts_with(&*domain) {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Domain", "Dapp attempted to access invalid domain.", Some(&target_url), self.embeddable_on.clone(),
|
||||
)));
|
||||
// Redirect if address to main page does not end with /
|
||||
if let None = path.get(idx + 3) {
|
||||
return Err(State::Redirecting(
|
||||
Redirection::new(&format!("/{}/", path.join("/")))
|
||||
));
|
||||
}
|
||||
|
||||
if !target_url.ends_with("/") {
|
||||
target_url = format!("{}/", target_url);
|
||||
}
|
||||
|
||||
// TODO [ToDr] Should just use `path.app_params`
|
||||
let (path, query) = match (&url, self.path.using_dapps_domains) {
|
||||
(&Some(ref url), true) => (&url.path[..], &url.query),
|
||||
(&Some(ref url), false) => (&url.path[2..], &url.query),
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let query = match *query {
|
||||
Some(ref query) => format!("?{}", query),
|
||||
let query = match query {
|
||||
Some(query) => format!("?{}", query),
|
||||
None => "".into(),
|
||||
};
|
||||
|
||||
Ok(format!("{}{}{}", target_url, path.join("/"), query))
|
||||
Ok((format!("{}://{}{}", protocol, path[idx + 2..].join("/"), query), path[0..].join("/")))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
let url = extract_url(&request);
|
||||
|
||||
// First extract the URL (reject invalid URLs)
|
||||
let target_url = match self.extract_target_url(url) {
|
||||
let (target_url, referer) = match self.extract_target_url(url) {
|
||||
Ok(url) => url,
|
||||
Err(error) => {
|
||||
self.state = error;
|
||||
@@ -203,9 +186,7 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
self.control.clone(),
|
||||
WebInstaller {
|
||||
embeddable_on: self.embeddable_on.clone(),
|
||||
referer: self.path.app_params.get(0)
|
||||
.expect("`target_url` is valid; app_params is not empty;qed")
|
||||
.to_owned(),
|
||||
referer: referer,
|
||||
},
|
||||
self.embeddable_on.clone(),
|
||||
self.remote.clone(),
|
||||
@@ -221,6 +202,7 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_request_readable(decoder),
|
||||
State::Redirecting(ref mut handler) => handler.on_request_readable(decoder),
|
||||
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
|
||||
}
|
||||
}
|
||||
@@ -229,6 +211,7 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_response(res),
|
||||
State::Redirecting(ref mut handler) => handler.on_response(res),
|
||||
State::Fetching(ref mut handler) => handler.on_response(res),
|
||||
}
|
||||
}
|
||||
@@ -237,6 +220,7 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
State::Redirecting(ref mut handler) => handler.on_response_writable(encoder),
|
||||
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ description = "Ethcore Parity UI"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "parity-ui"
|
||||
version = "1.7.0"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[build-dependencies]
|
||||
@@ -11,8 +11,7 @@ rustc_version = "0.1"
|
||||
|
||||
[dependencies]
|
||||
parity-ui-dev = { path = "../../js", optional = true }
|
||||
# This is managed by the js/scripts/release.sh script on CI - keep it in a single line
|
||||
parity-ui-precompiled = { git = "https://github.com/paritytech/js-precompiled.git", optional = true, branch = "beta" }
|
||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
25
db/Cargo.toml
Normal file
25
db/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
description = "Ethcore Database"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-db"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
ethcore-ipc-codegen = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
semver = "0.5"
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
crossbeam = "0.2"
|
||||
ethcore-util = { path = "../util" }
|
||||
|
||||
[features]
|
||||
dev = ["clippy"]
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,20 +14,25 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Dapps Service
|
||||
extern crate ethcore_ipc_codegen as codegen;
|
||||
|
||||
use v1::types::LocalDapp;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
/// Dapps Server service.
|
||||
pub trait DappsService: Send + Sync + 'static {
|
||||
/// List available local dapps.
|
||||
fn list_dapps(&self) -> Vec<LocalDapp>;
|
||||
}
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
impl<F> DappsService for F where
|
||||
F: Fn() -> Vec<LocalDapp> + Send + Sync + 'static
|
||||
{
|
||||
fn list_dapps(&self) -> Vec<LocalDapp> {
|
||||
(*self)()
|
||||
// ipc pass
|
||||
{
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
codegen::expand(&src, &dst);
|
||||
}
|
||||
|
||||
// binary serialization pass
|
||||
{
|
||||
let src = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
codegen::expand(&src, &dst);
|
||||
}
|
||||
}
|
||||
565
db/src/database.rs
Normal file
565
db/src/database.rs
Normal file
@@ -0,0 +1,565 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore rocksdb ipc service
|
||||
|
||||
use traits::*;
|
||||
use rocksdb::{DB, Writable, WriteBatch, IteratorMode, DBIterator, IndexType, Options, DBCompactionStyle, BlockBasedOptions, Direction};
|
||||
use std::sync::{RwLock, Arc};
|
||||
use std::convert::From;
|
||||
use ipc::IpcConfig;
|
||||
use std::mem;
|
||||
use ipc::binary::BinaryConvertError;
|
||||
use std::collections::{VecDeque, HashMap, BTreeMap};
|
||||
|
||||
enum WriteCacheEntry {
|
||||
Remove,
|
||||
Write(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct WriteCache {
|
||||
entries: HashMap<Vec<u8>, WriteCacheEntry>,
|
||||
preferred_len: usize,
|
||||
}
|
||||
|
||||
const FLUSH_BATCH_SIZE: usize = 4096;
|
||||
|
||||
impl WriteCache {
|
||||
fn new(cache_len: usize) -> WriteCache {
|
||||
WriteCache {
|
||||
entries: HashMap::new(),
|
||||
preferred_len: cache_len,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, key: Vec<u8>, val: Vec<u8>) {
|
||||
self.entries.insert(key, WriteCacheEntry::Write(val));
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: Vec<u8>) {
|
||||
self.entries.insert(key, WriteCacheEntry::Remove);
|
||||
}
|
||||
|
||||
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.entries.get(key).and_then(
|
||||
|vec_ref| match vec_ref {
|
||||
&WriteCacheEntry::Write(ref val) => Some(val.clone()),
|
||||
&WriteCacheEntry::Remove => None
|
||||
})
|
||||
}
|
||||
|
||||
/// WriteCache should be locked for this
|
||||
fn flush(&mut self, db: &DB, amount: usize) -> Result<(), Error> {
|
||||
let batch = WriteBatch::new();
|
||||
let mut removed_so_far = 0;
|
||||
while removed_so_far < amount {
|
||||
if self.entries.len() == 0 { break; }
|
||||
let removed_key = {
|
||||
let (key, cache_entry) = self.entries.iter().nth(0)
|
||||
.expect("if entries.len == 0, we should have break in the loop, still we got here somehow");
|
||||
|
||||
match *cache_entry {
|
||||
WriteCacheEntry::Write(ref val) => {
|
||||
batch.put(&key, val)?;
|
||||
},
|
||||
WriteCacheEntry::Remove => {
|
||||
batch.delete(&key)?;
|
||||
},
|
||||
}
|
||||
key.clone()
|
||||
};
|
||||
|
||||
self.entries.remove(&removed_key);
|
||||
|
||||
removed_so_far = removed_so_far + 1;
|
||||
}
|
||||
if removed_so_far > 0 {
|
||||
db.write(batch)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// flushes until cache is empty
|
||||
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
|
||||
while !self.is_empty() { self.flush(db, FLUSH_BATCH_SIZE)?; }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
fn try_shrink(&mut self, db: &DB) -> Result<(), Error> {
|
||||
if self.entries.len() > self.preferred_len {
|
||||
self.flush(db, FLUSH_BATCH_SIZE)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
db: RwLock<Option<DB>>,
|
||||
/// Iterators - dont't use between threads!
|
||||
iterators: RwLock<BTreeMap<IteratorHandle, DBIterator>>,
|
||||
write_cache: RwLock<WriteCache>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Database {}
|
||||
unsafe impl Sync for Database {}
|
||||
|
||||
impl Database {
|
||||
pub fn new() -> Database {
|
||||
Database {
|
||||
db: RwLock::new(None),
|
||||
iterators: RwLock::new(BTreeMap::new()),
|
||||
write_cache: RwLock::new(WriteCache::new(DEFAULT_CACHE_LEN)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&self) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
let db_lock = self.db.read();
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().unwrap();
|
||||
|
||||
cache_lock.try_shrink(&db)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn flush_all(&self) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
let db_lock = self.db.read();
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step");
|
||||
|
||||
cache_lock.flush_all(&db)?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Database {
|
||||
fn drop(&mut self) {
|
||||
self.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[ipc]
|
||||
impl DatabaseService for Database {
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error> {
|
||||
let mut db = self.db.write();
|
||||
if db.is_some() { return Err(Error::AlreadyOpen); }
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.set_max_open_files(256);
|
||||
opts.create_if_missing(true);
|
||||
opts.set_use_fsync(false);
|
||||
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
|
||||
if let Some(size) = config.prefix_size {
|
||||
let mut block_opts = BlockBasedOptions::new();
|
||||
block_opts.set_index_type(IndexType::HashSearch);
|
||||
opts.set_block_based_table_factory(&block_opts);
|
||||
opts.set_prefix_extractor_fixed_size(size);
|
||||
}
|
||||
*db = Some(DB::open(&opts, &path)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Opens database in the specified path with the default config
|
||||
fn open_default(&self, path: String) -> Result<(), Error> {
|
||||
self.open(DatabaseConfig::default(), path)
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
self.flush_all()?;
|
||||
|
||||
let mut db = self.db.write();
|
||||
if db.is_none() { return Err(Error::IsClosed); }
|
||||
|
||||
*db = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
cache_lock.write(key.to_vec(), value.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete(&self, key: &[u8]) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
cache_lock.remove(key.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
|
||||
let mut writes = transaction.writes.borrow_mut();
|
||||
for kv in writes.drain(..) {
|
||||
cache_lock.write(kv.key, kv.value);
|
||||
}
|
||||
|
||||
let mut removes = transaction.removes.borrow_mut();
|
||||
for k in removes.drain(..) {
|
||||
cache_lock.remove(k);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
{
|
||||
let key_vec = key.to_vec();
|
||||
let cache_hit = self.write_cache.read().get(&key_vec);
|
||||
|
||||
if cache_hit.is_some() {
|
||||
return Ok(Some(cache_hit.expect("cache_hit.is_some() = true, still there is none somehow here")))
|
||||
}
|
||||
}
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
|
||||
match db.get(key)? {
|
||||
Some(db_vec) => {
|
||||
Ok(Some(db_vec.to_vec()))
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
|
||||
let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward));
|
||||
match iter.next() {
|
||||
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
|
||||
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Ok(Some(v.to_vec())) } else { Ok(None) },
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> Result<bool, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
|
||||
Ok(db.iterator(IteratorMode::Start).next().is_none())
|
||||
}
|
||||
|
||||
fn iter(&self) -> Result<IteratorHandle, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
|
||||
let mut iterators = self.iterators.write();
|
||||
let next_iterator = iterators.keys().last().unwrap_or(&0) + 1;
|
||||
iterators.insert(next_iterator, db.iterator(IteratorMode::Start));
|
||||
Ok(next_iterator)
|
||||
}
|
||||
|
||||
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue> {
|
||||
let mut iterators = self.iterators.write();
|
||||
let mut iterator = match iterators.get_mut(&handle) {
|
||||
Some(some_iterator) => some_iterator,
|
||||
None => { return None; },
|
||||
};
|
||||
|
||||
iterator.next().and_then(|(some_key, some_val)| {
|
||||
Some(KeyValue {
|
||||
key: some_key.to_vec(),
|
||||
value: some_val.to_vec(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error> {
|
||||
let mut iterators = self.iterators.write();
|
||||
iterators.remove(&handle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : put proper at compile-time
|
||||
impl IpcConfig for Database {}
|
||||
|
||||
/// Database iterator
|
||||
pub struct DatabaseIterator {
|
||||
client: Arc<DatabaseClient<::nanomsg::Socket>>,
|
||||
handle: IteratorHandle,
|
||||
}
|
||||
|
||||
impl Iterator for DatabaseIterator {
|
||||
type Item = (Vec<u8>, Vec<u8>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.client.iter_next(self.handle).and_then(|kv| Some((kv.key, kv.value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DatabaseIterator {
|
||||
fn drop(&mut self) {
|
||||
self.client.dispose_iter(self.handle).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::Database;
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
|
||||
#[test]
|
||||
fn can_be_created() {
|
||||
let db = Database::new();
|
||||
assert!(db.is_empty().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_open_empty() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
assert!(db.is_empty().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_store_key() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.flush_all().unwrap();
|
||||
assert!(!db.is_empty().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_retrieve() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.close().unwrap();
|
||||
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(db.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod write_cache_tests {
|
||||
use super::Database;
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
|
||||
#[test]
|
||||
fn cache_write_flush() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
db.put("100500".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.delete("100500".as_bytes()).unwrap();
|
||||
db.flush_all().unwrap();
|
||||
|
||||
let val = db.get("100500".as_bytes()).unwrap();
|
||||
assert!(val.is_none());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod client_tests {
|
||||
use super::{DatabaseClient, Database};
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
use nanoipc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{Ordering, AtomicBool};
|
||||
use crossbeam;
|
||||
use run_worker;
|
||||
|
||||
fn init_worker(addr: &str) -> nanoipc::Worker<Database> {
|
||||
let mut worker = nanoipc::Worker::<Database>::new(&Arc::new(Database::new()));
|
||||
worker.add_duplex(addr).unwrap();
|
||||
worker
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_call_handshake() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-10.ipc";
|
||||
let worker_should_exit = Arc::new(AtomicBool::new(false));
|
||||
let worker_is_ready = Arc::new(AtomicBool::new(false));
|
||||
let c_worker_should_exit = worker_should_exit.clone();
|
||||
let c_worker_is_ready = worker_is_ready.clone();
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
let mut worker = init_worker(url);
|
||||
while !c_worker_should_exit.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
c_worker_is_ready.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
while !worker_is_ready.load(Ordering::Relaxed) { }
|
||||
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
let hs = client.handshake();
|
||||
|
||||
worker_should_exit.store(true, Ordering::Relaxed);
|
||||
assert!(hs.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_open_db() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-20.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
let worker_should_exit = Arc::new(AtomicBool::new(false));
|
||||
let worker_is_ready = Arc::new(AtomicBool::new(false));
|
||||
let c_worker_should_exit = worker_should_exit.clone();
|
||||
let c_worker_is_ready = worker_is_ready.clone();
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
let mut worker = init_worker(url);
|
||||
while !c_worker_should_exit.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
c_worker_is_ready.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
while !worker_is_ready.load(Ordering::Relaxed) { }
|
||||
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert!(client.is_empty().unwrap());
|
||||
worker_should_exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_put() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-30.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
client.close().unwrap();
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_put_and_read() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-40.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_empty() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-45.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert!(client.get("xxx".as_bytes()).unwrap().is_none());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn can_commit_client_transaction() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-60.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
let transaction = DBTransaction::new();
|
||||
transaction.put("xxx".as_bytes(), "1".as_bytes());
|
||||
client.write(transaction).unwrap();
|
||||
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_write_read_ipc() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-70.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(|scope| {
|
||||
let stop = StopGuard::new();
|
||||
run_worker(&scope, stop.share(), url);
|
||||
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
let mut batch = Vec::new();
|
||||
for _ in 0..100 {
|
||||
batch.push((random_str(256).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
|
||||
batch.push((random_str(256).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
|
||||
batch.push((random_str(2048).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
|
||||
batch.push((random_str(2048).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
|
||||
}
|
||||
|
||||
for &(ref k, ref v) in batch.iter() {
|
||||
client.put(k, v).unwrap();
|
||||
}
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
for &(ref k, ref v) in batch.iter() {
|
||||
assert_eq!(v, &client.get(k).unwrap().unwrap());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -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/>.
|
||||
|
||||
//! Types used in the public api
|
||||
//! Database ipc service
|
||||
|
||||
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs.in"));
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
89
db/src/lib.rs.in
Normal file
89
db/src/lib.rs.in
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethcore_ipc as ipc;
|
||||
extern crate rocksdb;
|
||||
extern crate ethcore_devtools as devtools;
|
||||
extern crate semver;
|
||||
extern crate ethcore_ipc_nano as nanoipc;
|
||||
extern crate nanomsg;
|
||||
extern crate crossbeam;
|
||||
extern crate ethcore_util as util;
|
||||
|
||||
pub mod database;
|
||||
pub mod traits;
|
||||
|
||||
pub use traits::{DatabaseService, DBTransaction, Error};
|
||||
pub use database::{Database, DatabaseClient, DatabaseIterator};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type DatabaseNanoClient = DatabaseClient<::nanomsg::Socket>;
|
||||
pub type DatabaseConnection = nanoipc::GuardedSocket<DatabaseNanoClient>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServiceError {
|
||||
Io(std::io::Error),
|
||||
Socket(nanoipc::SocketError),
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for ServiceError {
|
||||
fn from(io_error: std::io::Error) -> ServiceError { ServiceError::Io(io_error) }
|
||||
}
|
||||
|
||||
impl std::convert::From<nanoipc::SocketError> for ServiceError {
|
||||
fn from(socket_error: nanoipc::SocketError) -> ServiceError { ServiceError::Socket(socket_error) }
|
||||
}
|
||||
|
||||
pub fn blocks_service_url(db_path: &str) -> Result<String, std::io::Error> {
|
||||
let mut path = PathBuf::from(db_path);
|
||||
::std::fs::create_dir_all(db_path)?;
|
||||
path.push("blocks.ipc");
|
||||
Ok(format!("ipc://{}", path.to_str().unwrap()))
|
||||
}
|
||||
|
||||
pub fn extras_service_url(db_path: &str) -> Result<String, ::std::io::Error> {
|
||||
let mut path = PathBuf::from(db_path);
|
||||
::std::fs::create_dir_all(db_path)?;
|
||||
path.push("extras.ipc");
|
||||
Ok(format!("ipc://{}", path.to_str().unwrap()))
|
||||
}
|
||||
|
||||
pub fn blocks_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = blocks_service_url(db_path)?;
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = extras_service_url(db_path)?;
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
// for tests
|
||||
pub fn run_worker(scope: &crossbeam::Scope, stop: Arc<AtomicBool>, socket_path: &str) {
|
||||
let socket_path = socket_path.to_owned();
|
||||
scope.spawn(move || {
|
||||
let mut worker = nanoipc::Worker::new(&Arc::new(Database::new()));
|
||||
worker.add_reqrep(&socket_path).unwrap();
|
||||
while !stop.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
}
|
||||
});
|
||||
}
|
||||
135
db/src/traits.rs
Normal file
135
db/src/traits.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore database trait
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub type IteratorHandle = u32;
|
||||
|
||||
pub const DEFAULT_CACHE_LEN: usize = 12288;
|
||||
|
||||
#[derive(Binary)]
|
||||
pub struct KeyValue {
|
||||
pub key: Vec<u8>,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Binary)]
|
||||
pub enum Error {
|
||||
AlreadyOpen,
|
||||
IsClosed,
|
||||
RocksDb(String),
|
||||
TransactionUnknown,
|
||||
IteratorUnknown,
|
||||
UncommitedTransactions,
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Error {
|
||||
Error::RocksDb(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Database configuration
|
||||
#[derive(Binary)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Optional prefix size in bytes. Allows lookup by partial key.
|
||||
pub prefix_size: Option<usize>,
|
||||
/// write cache length
|
||||
pub cache: usize,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: None,
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
fn with_prefix(prefix: usize) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: Some(prefix),
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatabaseService : Sized {
|
||||
/// Opens database in the specified path
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Opens database in the specified path with the default config
|
||||
fn open_default(&self, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Closes database
|
||||
fn close(&self) -> Result<(), Error>;
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten.
|
||||
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Delete value by key.
|
||||
fn delete(&self, key: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Get value by key.
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Get value by partial key. Prefix size should match configured prefix size.
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Check if there is anything in the database.
|
||||
fn is_empty(&self) -> Result<bool, Error>;
|
||||
|
||||
/// Get handle to iterate through keys
|
||||
fn iter(&self) -> Result<IteratorHandle, Error>;
|
||||
|
||||
/// Next key-value for the the given iterator
|
||||
fn iter_next(&self, iterator: IteratorHandle) -> Option<KeyValue>;
|
||||
|
||||
/// Dispose iteration that is no longer needed
|
||||
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error>;
|
||||
|
||||
/// Write client transaction
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Binary)]
|
||||
pub struct DBTransaction {
|
||||
pub writes: RefCell<Vec<KeyValue>>,
|
||||
pub removes: RefCell<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl DBTransaction {
|
||||
pub fn new() -> DBTransaction {
|
||||
DBTransaction {
|
||||
writes: RefCell::new(Vec::new()),
|
||||
removes: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &[u8], value: &[u8]) {
|
||||
let mut brw = self.writes.borrow_mut();
|
||||
brw.push(KeyValue { key: key.to_vec(), value: value.to_vec() });
|
||||
}
|
||||
|
||||
pub fn delete(&self, key: &[u8]) {
|
||||
let mut brw = self.removes.borrow_mut();
|
||||
brw.push(key.to_vec());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-devtools"
|
||||
version = "1.7.0"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -87,7 +87,7 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
|
||||
let _ = req.read_to_string(&mut response);
|
||||
|
||||
let mut lines = response.lines();
|
||||
let status = lines.next().expect("Expected a response").to_owned();
|
||||
let status = lines.next().unwrap().to_owned();
|
||||
let headers_raw = read_block(&mut lines, false);
|
||||
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
|
||||
let body = read_block(&mut lines, true);
|
||||
@@ -102,7 +102,12 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
|
||||
|
||||
/// Check if all required security headers are present
|
||||
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
|
||||
if let None = port {
|
||||
if let Some(port) = port {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(),
|
||||
"X-Frame-Options: ALLOW-FROM missing: {:?}", headers
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
|
||||
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers
|
||||
@@ -116,8 +121,4 @@ pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
|
||||
headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(),
|
||||
"X-Content-Type-Options missing: {:?}", headers
|
||||
);
|
||||
assert!(
|
||||
headers.iter().find(|header| header.starts_with("Content-Security-Policy: ")).is_some(),
|
||||
"Content-Security-Policy missing: {:?}", headers
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -96,7 +96,7 @@ impl Drop for RandomTempPath {
|
||||
|
||||
pub struct GuardedTempResult<T> {
|
||||
pub result: Option<T>,
|
||||
pub _temp: RandomTempPath,
|
||||
pub _temp: RandomTempPath
|
||||
}
|
||||
|
||||
impl<T> GuardedTempResult<T> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Usage
|
||||
|
||||
```docker build -f docker/ubuntu/Dockerfile --tag ethcore/parity:branch_or_tag_name .```
|
||||
@@ -1,32 +1,29 @@
|
||||
FROM centos:latest
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN yum -y update&& \
|
||||
yum install -y git make gcc-c++ gcc file binutils
|
||||
|
||||
# install rustup
|
||||
RUN curl -sSf https://static.rust-lang.org/rustup.sh -o rustup.sh &&\
|
||||
ls&&\
|
||||
sh rustup.sh --disable-sudo
|
||||
|
||||
sh rustup.sh -s -- --disable-sudo
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# set compiler
|
||||
ENV CXX g++
|
||||
ENV CC gcc
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity&&\
|
||||
cargo build --release --verbose && \
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity&&\
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
ls -a&&\
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Parity Technologies <devops@parity.io>
|
||||
WORKDIR /build
|
||||
#ENV for build TAG
|
||||
ARG BUILD_TAG
|
||||
ENV BUILD_TAG ${BUILD_TAG:-master}
|
||||
RUN echo "Build tag:" $BUILD_TAG
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
@@ -48,10 +44,10 @@ RUN apt-get update && \
|
||||
# show backtraces
|
||||
RUST_BACKTRACE=1 && \
|
||||
# build parity
|
||||
cd /build&&git clone https://github.com/paritytech/parity && \
|
||||
cd /build&&git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git pull&& \
|
||||
git checkout $BUILD_TAG && \
|
||||
git checkout $CI_BUILD_REF_NAME && \
|
||||
cargo build --verbose --release --features final && \
|
||||
#ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity && \
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
@@ -25,11 +24,14 @@ RUN rustup target add aarch64-unknown-linux-gnu
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && cargo -V
|
||||
RUN rustc -vV && \
|
||||
cargo -V
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.aarch64-unknown-linux-gnu]\n\
|
||||
linker = "aarch64-linux-gnu-gcc"\n'\
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
@@ -24,12 +23,16 @@ RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && cargo -V
|
||||
RUN rustc -vV && \
|
||||
cargo -V
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.armv7-unknown-linux-gnueabihf]\n\
|
||||
linker = "arm-linux-gnueabihf-gcc"\n'\
|
||||
|
||||
37
docker/ubuntu-dev/Dockerfile
Normal file
37
docker/ubuntu-dev/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
g++ \
|
||||
wget \
|
||||
git \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
|
||||
# cmake, llvm and rocksdb ppas. then update ppas
|
||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
|
||||
# install evmjit
|
||||
RUN git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
@@ -1,6 +1,5 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
@@ -46,8 +45,10 @@ gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
cargo build --release --features ethcore/jit --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
40
docker/ubuntu-stable/Dockerfile
Normal file
40
docker/ubuntu-stable/Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout stable && \
|
||||
git pull && \
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
@@ -1,18 +1,19 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
libudev-dev
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends g++ gcc libc6 libc6-dev \
|
||||
python binutils curl git make file ca-certificates zip dpkg-dev rhash openssl build-essential pkg-config libssl-dev
|
||||
|
||||
# install AWS CLI
|
||||
RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
|
||||
python get-pip.py && \
|
||||
pip install awscli
|
||||
|
||||
# install nodejs
|
||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
apt-get clean
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -30,9 +31,11 @@ gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
cargo build --release --verbose && \
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
cargo build --features final --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ethash"
|
||||
version = "1.7.0"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
@@ -9,4 +9,4 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
log = "0.3"
|
||||
sha3 = { path = "../util/sha3" }
|
||||
primal = "0.2.3"
|
||||
parking_lot = "0.4"
|
||||
parking_lot = "0.3"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -25,7 +25,7 @@ use std::mem;
|
||||
use std::ptr;
|
||||
use sha3;
|
||||
use std::slice;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::fs::{self, File};
|
||||
|
||||
@@ -86,7 +86,6 @@ impl Node {
|
||||
pub type H256 = [u8; 32];
|
||||
|
||||
pub struct Light {
|
||||
cache_dir: PathBuf,
|
||||
block_number: u64,
|
||||
cache: Vec<Node>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
@@ -95,8 +94,8 @@ pub struct Light {
|
||||
/// Light cache structure
|
||||
impl Light {
|
||||
/// Create a new light cache for a given block number
|
||||
pub fn new<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> Light {
|
||||
light_new(cache_dir, block_number)
|
||||
pub fn new(block_number: u64) -> Light {
|
||||
light_new(block_number)
|
||||
}
|
||||
|
||||
/// Calculate the light boundary data
|
||||
@@ -106,15 +105,17 @@ impl Light {
|
||||
light_compute(self, header_hash, nonce)
|
||||
}
|
||||
|
||||
pub fn file_path<T: AsRef<Path>>(cache_dir: T, seed_hash: H256) -> PathBuf {
|
||||
let mut cache_dir = cache_dir.as_ref().to_path_buf();
|
||||
cache_dir.push(to_hex(&seed_hash));
|
||||
cache_dir
|
||||
pub fn file_path(seed_hash: H256) -> PathBuf {
|
||||
let mut home = ::std::env::home_dir().unwrap();
|
||||
home.push(".ethash");
|
||||
home.push("light");
|
||||
home.push(to_hex(&seed_hash));
|
||||
home
|
||||
}
|
||||
|
||||
pub fn from_file<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> io::Result<Light> {
|
||||
pub fn from_file(block_number: u64) -> io::Result<Light> {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let path = Light::file_path(&cache_dir, seed_compute.get_seedhash(block_number));
|
||||
let path = Light::file_path(seed_compute.get_seedhash(block_number));
|
||||
let mut file = File::open(path)?;
|
||||
|
||||
let cache_size = get_cache_size(block_number);
|
||||
@@ -122,27 +123,24 @@ impl Light {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Cache file size mismatch"));
|
||||
}
|
||||
let num_nodes = cache_size / NODE_BYTES;
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(num_nodes);
|
||||
let mut nodes: Vec<Node> = Vec::new();
|
||||
nodes.resize(num_nodes, unsafe { mem::uninitialized() });
|
||||
let buf = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut u8, cache_size) };
|
||||
file.read_exact(buf)?;
|
||||
Ok(Light {
|
||||
block_number,
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache: nodes,
|
||||
block_number: block_number,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_file(&self) -> io::Result<PathBuf> {
|
||||
let seed_compute = self.seed_compute.lock();
|
||||
let path = Light::file_path(&self.cache_dir, seed_compute.get_seedhash(self.block_number));
|
||||
let path = Light::file_path(seed_compute.get_seedhash(self.block_number));
|
||||
|
||||
if self.block_number >= ETHASH_EPOCH_LENGTH * 2 {
|
||||
let deprecated = Light::file_path(
|
||||
&self.cache_dir,
|
||||
seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2)
|
||||
);
|
||||
seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2));
|
||||
|
||||
if deprecated.exists() {
|
||||
debug!(target: "ethash", "removing: {:?}", &deprecated);
|
||||
@@ -343,12 +341,15 @@ fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
}
|
||||
}
|
||||
|
||||
fn light_new<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> Light {
|
||||
fn light_new(block_number: u64) -> Light {
|
||||
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let seedhash = seed_compute.get_seedhash(block_number);
|
||||
let cache_size = get_cache_size(block_number);
|
||||
|
||||
assert!(cache_size % NODE_BYTES == 0, "Unaligned cache size");
|
||||
if cache_size % NODE_BYTES != 0 {
|
||||
panic!("Unaligned cache size");
|
||||
}
|
||||
let num_nodes = cache_size / NODE_BYTES;
|
||||
|
||||
let mut nodes = Vec::with_capacity(num_nodes);
|
||||
@@ -372,9 +373,8 @@ fn light_new<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> Light {
|
||||
}
|
||||
|
||||
Light {
|
||||
block_number,
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache: nodes,
|
||||
block_number: block_number,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
}
|
||||
}
|
||||
@@ -433,7 +433,7 @@ fn test_light_compute() {
|
||||
let boundary = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
let light = Light::new(&::std::env::temp_dir(), 486382);
|
||||
let light = Light::new(486382);
|
||||
let result = light_compute(&light, &hash, nonce);
|
||||
assert_eq!(result.mix_hash[..], mix_hash[..]);
|
||||
assert_eq!(result.value[..], boundary[..]);
|
||||
@@ -472,16 +472,15 @@ fn test_seed_compute_after_newer() {
|
||||
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let path = ::std::env::temp_dir();
|
||||
let first = Light::new(&path, 0).to_file().unwrap();
|
||||
let first = Light::new(0).to_file().unwrap();
|
||||
|
||||
let second = Light::new(&path, ETHASH_EPOCH_LENGTH).to_file().unwrap();
|
||||
let second = Light::new(ETHASH_EPOCH_LENGTH).to_file().unwrap();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
let _ = Light::new(ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
let _ = Light::new(ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -25,7 +25,6 @@ extern crate log;
|
||||
mod compute;
|
||||
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use compute::Light;
|
||||
pub use compute::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty, slow_get_seedhash};
|
||||
|
||||
@@ -42,14 +41,12 @@ struct LightCache {
|
||||
/// Light/Full cache manager.
|
||||
pub struct EthashManager {
|
||||
cache: Mutex<LightCache>,
|
||||
cache_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl EthashManager {
|
||||
/// Create a new new instance of ethash manager
|
||||
pub fn new<T: AsRef<Path>>(cache_dir: T) -> EthashManager {
|
||||
pub fn new() -> EthashManager {
|
||||
EthashManager {
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache: Mutex::new(LightCache {
|
||||
recent_epoch: None,
|
||||
recent: None,
|
||||
@@ -91,11 +88,11 @@ impl EthashManager {
|
||||
};
|
||||
match light {
|
||||
None => {
|
||||
let light = match Light::from_file(&self.cache_dir, block_number) {
|
||||
let light = match Light::from_file(block_number) {
|
||||
Ok(light) => Arc::new(light),
|
||||
Err(e) => {
|
||||
debug!("Light cache file not found for {}:{}", block_number, e);
|
||||
let light = Light::new(&self.cache_dir, block_number);
|
||||
let light = Light::new(block_number);
|
||||
if let Err(e) = light.to_file() {
|
||||
warn!("Light cache file write error: {}", e);
|
||||
}
|
||||
@@ -115,7 +112,7 @@ impl EthashManager {
|
||||
|
||||
#[test]
|
||||
fn test_lru() {
|
||||
let ethash = EthashManager::new(&::std::env::temp_dir());
|
||||
let ethash = EthashManager::new();
|
||||
let hash = [0u8; 32];
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
ethash.compute_light(50000, &hash, 1);
|
||||
|
||||
@@ -3,7 +3,7 @@ description = "Ethcore library"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore"
|
||||
version = "1.7.0"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
@@ -11,55 +11,45 @@ build = "build.rs"
|
||||
"ethcore-ipc-codegen" = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
bit-set = "0.4"
|
||||
bloomchain = "0.1"
|
||||
bn = { git = "https://github.com/paritytech/bn" }
|
||||
byteorder = "1.0"
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
common-types = { path = "types" }
|
||||
crossbeam = "0.2.9"
|
||||
env_logger = "0.4"
|
||||
ethabi = "2.0"
|
||||
ethash = { path = "../ethash" }
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-io = { path = "../util/io" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
ethcore-logger = { path = "../logger" }
|
||||
ethcore-stratum = { path = "../stratum" }
|
||||
ethcore-util = { path = "../util" }
|
||||
ethjson = { path = "../json" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethstore = { path = "../ethstore" }
|
||||
evm = { path = "evm" }
|
||||
futures = "0.1"
|
||||
hardware-wallet = { path = "../hw" }
|
||||
hyper = { git = "https://github.com/paritytech/hyper", default-features = false }
|
||||
itertools = "0.5"
|
||||
lazy_static = "0.2"
|
||||
linked-hash-map = "0.3.0"
|
||||
log = "0.3"
|
||||
lru-cache = "0.1.0"
|
||||
native-contracts = { path = "native_contracts" }
|
||||
num = "0.1"
|
||||
num_cpus = "1.2"
|
||||
rayon = "0.8"
|
||||
rand = "0.3"
|
||||
rlp = { path = "../util/rlp" }
|
||||
rust-crypto = "0.2.34"
|
||||
rustc-hex = "1.0"
|
||||
env_logger = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
semver = "0.6"
|
||||
stats = { path = "../util/stats" }
|
||||
heapsize = "0.3"
|
||||
rust-crypto = "0.2.34"
|
||||
num_cpus = "0.2"
|
||||
crossbeam = "0.2.9"
|
||||
lazy_static = "0.2"
|
||||
bloomchain = "0.1"
|
||||
rayon = "0.4.2"
|
||||
semver = "0.5"
|
||||
bit-set = "0.4"
|
||||
time = "0.1"
|
||||
transient-hashmap = "0.4"
|
||||
rand = "0.3"
|
||||
byteorder = "0.5"
|
||||
transient-hashmap = "0.1"
|
||||
linked-hash-map = "0.3.0"
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
ethash = { path = "../ethash" }
|
||||
ethcore-util = { path = "../util" }
|
||||
ethcore-io = { path = "../util/io" }
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethjson = { path = "../json" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
ethstore = { path = "../ethstore" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
lru-cache = "0.1.0"
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
ethabi = "0.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
native-contracts = { path = "native_contracts", features = ["test_contracts"] }
|
||||
[dependencies.hyper]
|
||||
git = "https://github.com/ethcore/hyper"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
jit = ["evm/jit"]
|
||||
jit = ["evmjit"]
|
||||
evm-debug = ["slow-blocks"]
|
||||
evm-debug-tests = ["evm-debug"]
|
||||
slow-blocks = [] # Use SLOW_TX_DURATION="50" (compile time!) to track transactions over 50ms
|
||||
@@ -69,3 +59,5 @@ dev = ["clippy"]
|
||||
default = []
|
||||
benches = []
|
||||
ipc = []
|
||||
ethkey-cli = ["ethkey/cli"]
|
||||
ethstore-cli = ["ethstore/cli"]
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate rand;
|
||||
extern crate bn;
|
||||
extern crate crypto;
|
||||
extern crate rustc_serialize;
|
||||
extern crate ethkey;
|
||||
|
||||
use self::test::{Bencher};
|
||||
use rand::{StdRng};
|
||||
|
||||
|
||||
#[bench]
|
||||
fn bn_128_pairing(b: &mut Bencher) {
|
||||
use bn::{pairing, G1, G2, Fr, Group};
|
||||
|
||||
let rng = &mut ::rand::thread_rng();
|
||||
|
||||
let sk0 = Fr::random(rng);
|
||||
let sk1 = Fr::random(rng);
|
||||
|
||||
let pk0 = G1::one() * sk0;
|
||||
let pk1 = G2::one() * sk1;
|
||||
|
||||
b.iter(|| {
|
||||
let _ = pairing(pk0, pk1);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bn_128_mul(b: &mut Bencher) {
|
||||
use bn::{AffineG1, G1, Fr, Group};
|
||||
|
||||
let mut rng = StdRng::new().unwrap();
|
||||
let p: G1 = G1::random(&mut rng);
|
||||
let fr = Fr::random(&mut rng);
|
||||
|
||||
b.iter(|| {
|
||||
let _ = AffineG1::from_jacobian(p * fr);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn sha256(b: &mut Bencher) {
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
|
||||
let mut input: [u8; 256] = [0; 256];
|
||||
let mut out = [0; 32];
|
||||
|
||||
b.iter(|| {
|
||||
let mut sha = Sha256::new();
|
||||
sha.input(&input);
|
||||
sha.result(&mut input[0..32]);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ecrecover(b: &mut Bencher) {
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use ethkey::{Signature, recover as ec_recover};
|
||||
use util::H256;
|
||||
let input = FromHex::from_hex("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap();
|
||||
let hash = H256::from_slice(&input[0..32]);
|
||||
let v = H256::from_slice(&input[32..64]);
|
||||
let r = H256::from_slice(&input[64..96]);
|
||||
let s = H256::from_slice(&input[96..128]);
|
||||
|
||||
let bit = match v[31] {
|
||||
27 | 28 if &v.0[..31] == &[0; 31] => v[31] - 27,
|
||||
_ => { return; },
|
||||
};
|
||||
|
||||
let s = Signature::from_rsv(&r, &s, bit);
|
||||
b.iter(|| {
|
||||
let _ = ec_recover(&s, &hash);
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user