Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4d98001a | ||
|
|
2e4672ea13 | ||
|
|
7f7fe33bde | ||
|
|
3d313d592a | ||
|
|
abc6970d08 | ||
|
|
fa636d402d | ||
|
|
73d6666294 | ||
|
|
f16ccacd22 | ||
|
|
ba3de5d3b9 | ||
|
|
f16de5501a | ||
|
|
e298ab30e4 | ||
|
|
63d3a4dd59 | ||
|
|
0e55b6c6c9 | ||
|
|
4d115987cb | ||
|
|
2d623f14db | ||
|
|
1d1a3b9d02 | ||
|
|
1d69b0e124 | ||
|
|
3c59475be6 | ||
|
|
facab31551 | ||
|
|
c9cfcd2728 | ||
|
|
557dbb73b8 | ||
|
|
6f772a28e2 | ||
|
|
16f3119547 | ||
|
|
c3741640f7 | ||
|
|
decca7f698 | ||
|
|
fc53726c7f | ||
|
|
e2b81bc08d | ||
|
|
4d2d1eac1f | ||
|
|
5572e3dd1d | ||
|
|
0db1c0d336 | ||
|
|
905f04dc9d | ||
|
|
f560cf360c | ||
|
|
f5d48cbf2a | ||
|
|
d59e2ecbc6 | ||
|
|
a97ca5aaf4 | ||
|
|
df61b1b328 | ||
|
|
52a69d19e6 | ||
|
|
33084aaa07 | ||
|
|
2a82fa0a47 | ||
|
|
1ccc90108c | ||
|
|
e60c5b59a6 | ||
|
|
b199fbb6c6 | ||
|
|
d9d506db57 | ||
|
|
425e1ce04c | ||
|
|
8741d0d155 | ||
|
|
dfb2ddfdc2 | ||
|
|
52ac5a00f5 | ||
|
|
6336bdecf3 |
@@ -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
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -19,10 +19,6 @@
|
||||
# mac stuff
|
||||
.DS_Store
|
||||
|
||||
# npm stuff
|
||||
npm-debug.log
|
||||
node_modules
|
||||
|
||||
# gdb files
|
||||
.gdb_history
|
||||
|
||||
@@ -34,7 +30,3 @@ node_modules
|
||||
|
||||
# Build artifacts
|
||||
out/
|
||||
|
||||
.vscode
|
||||
|
||||
/parity.*
|
||||
|
||||
626
.gitlab-ci.yml
626
.gitlab-ci.yml
@@ -1,156 +1,69 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- push-release
|
||||
- build
|
||||
- test
|
||||
variables:
|
||||
GIT_DEPTH: "3"
|
||||
SIMPLECOV: "true"
|
||||
SIMPLECOV: "true"
|
||||
RUST_BACKTRACE: "1"
|
||||
RUSTFLAGS: ""
|
||||
CARGOFLAGS: ""
|
||||
CI_SERVER_NAME: "GitLab CI"
|
||||
cache:
|
||||
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:stable
|
||||
only:
|
||||
- master
|
||||
- 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
|
||||
- cargo build --release --verbose
|
||||
- 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
|
||||
- md5sum target/release/parity >> checksum
|
||||
- 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 s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
- 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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-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:
|
||||
linux-stable-14.04:
|
||||
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-14.04:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore-cli
|
||||
- cargo build -j $(nproc) --release -p ethkey-cli
|
||||
- cargo build --release --verbose
|
||||
- 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
|
||||
- md5sum target/release/parity >> checksum
|
||||
- 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 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
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.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-debian-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-debian-gnu
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-debian
|
||||
- rust-14.04
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
||||
name: "stable-x86_64-unknown-ubuntu_14_04-gnu_parity"
|
||||
linux-beta:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:beta
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- rustup default beta
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- cargo build --release --verbose
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
@@ -162,15 +75,14 @@ linux-beta:
|
||||
allow_failure: true
|
||||
linux-nightly:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
image: ethcore/rust:nightly
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- rustup default nightly
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- cargo build --release --verbose
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
@@ -182,43 +94,22 @@ linux-nightly:
|
||||
allow_failure: true
|
||||
linux-centos:
|
||||
stage: build
|
||||
image: parity/rust-centos:gitlab-ci
|
||||
image: ethcore/rust-centos:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CXX="g++"
|
||||
- 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
|
||||
- cargo build --release --verbose
|
||||
- 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
|
||||
- md5sum target/release/parity >> checksum
|
||||
- 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 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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-centos
|
||||
@@ -226,106 +117,27 @@ linux-centos:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: parity/rust-i686:gitlab-ci
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export HOST_CC=gcc
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.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-i686
|
||||
artifacts:
|
||||
paths:
|
||||
- target/i686-unknown-linux-gnu/release/parity
|
||||
name: "i686-unknown-linux-gnu"
|
||||
allow_failure: true
|
||||
linux-armv7:
|
||||
stage: build
|
||||
image: parity/rust-armv7:gitlab-ci
|
||||
image: ethcore/rust-armv7:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=armv7-unknown-linux-gnueabihf
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config
|
||||
- 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)
|
||||
- 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
|
||||
- cargo build --target armv7-unknown-linux-gnueabihf --release --verbose
|
||||
- arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
- md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> checksum
|
||||
- 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 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
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -336,51 +148,25 @@ linux-armv7:
|
||||
allow_failure: true
|
||||
linux-arm:
|
||||
stage: build
|
||||
image: parity/rust-arm:gitlab-ci
|
||||
image: ethcore/rust-arm:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=arm-unknown-linux-gnueabihf
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config
|
||||
- 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)
|
||||
- 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
|
||||
- cargo build --target arm-unknown-linux-gnueabihf --release --verbose
|
||||
- arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity
|
||||
- md5sum target/arm-unknown-linux-gnueabihf/release/parity >> checksum
|
||||
- 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 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
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -389,52 +175,56 @@ linux-arm:
|
||||
- target/arm-unknown-linux-gnueabihf/release/parity
|
||||
name: "arm-unknown-linux-gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-aarch64:
|
||||
linux-armv6:
|
||||
stage: build
|
||||
image: parity/rust-arm64:gitlab-ci
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=aarch64-linux-gnu-gcc
|
||||
- export CXX=aarch64-linux-gnu-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=aarch64-unknown-linux-gnu
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build --target arm-unknown-linux-gnueabi --release --verbose
|
||||
- arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity
|
||||
- md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/checksum --body checksum
|
||||
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: ethcore/rust-aarch64:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config
|
||||
- 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)
|
||||
- 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
|
||||
- cargo build --target aarch64-unknown-linux-gnu --release --verbose
|
||||
- aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity
|
||||
- md5sum target/aarch64-unknown-linux-gnu/release/parity >> checksum
|
||||
- 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 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"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/checksum --body checksum
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -446,40 +236,17 @@ linux-aarch64:
|
||||
darwin:
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
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
|
||||
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"
|
||||
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 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"
|
||||
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
|
||||
script:
|
||||
- cargo build --release --verbose
|
||||
- md5sum target/release/parity >> checksum
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/checksum --body checksum
|
||||
tags:
|
||||
- osx
|
||||
artifacts:
|
||||
@@ -487,214 +254,39 @@ darwin:
|
||||
- target/release/parity
|
||||
name: "x86_64-apple-darwin_parity"
|
||||
windows:
|
||||
cache:
|
||||
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
|
||||
untracked: true
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- set PLATFORM=x86_64-pc-windows-msvc
|
||||
- set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- makensis.exe installer.nsi
|
||||
- copy installer.exe InstallParity.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
|
||||
- md5sums InstallParity.exe > InstallParity.exe.md5
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
- md5sums win-installer.zip > win-installer.zip.md5
|
||||
- cd ..\target\release\
|
||||
- md5sums parity.exe > parity.exe.md5
|
||||
- zip parity.zip parity.exe parity.md5
|
||||
- md5sums parity.zip > parity.zip.md5
|
||||
- cd ..\..
|
||||
- rustup default 1.11.0-x86_64-pc-windows-msvc
|
||||
- cargo build --release --verbose
|
||||
- md5sum target\release\parity >> checksum
|
||||
- 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 %S3_BUCKET%
|
||||
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&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.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity --body target\release\parity.exe
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity --body target\release\parity.pdb
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/checksum --body checksum
|
||||
tags:
|
||||
- rust-windows
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity.exe
|
||||
- target/release/parity.pdb
|
||||
- nsis/InstallParity.exe
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
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
|
||||
tags:
|
||||
- docker
|
||||
test-coverage:
|
||||
test-linux:
|
||||
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)
|
||||
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
|
||||
- ./test.sh --verbose
|
||||
tags:
|
||||
- osx
|
||||
allow_failure: true
|
||||
test-windows:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
before_script:
|
||||
- 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
|
||||
tags:
|
||||
- rust-windows
|
||||
allow_failure: true
|
||||
test-rust-stable:
|
||||
stage: test
|
||||
image: parity/rust:gitlab-ci
|
||||
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:
|
||||
- rust
|
||||
- rust-stable
|
||||
js-test:
|
||||
stage: test
|
||||
image: parity/rust:gitlab-ci
|
||||
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)
|
||||
- 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:
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
test-rust-beta:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
- master
|
||||
image: parity/rust:gitlab-ci
|
||||
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:
|
||||
- rust
|
||||
- rust-beta
|
||||
allow_failure: true
|
||||
test-rust-nightly:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
- master
|
||||
image: parity/rust:gitlab-ci
|
||||
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:
|
||||
- rust
|
||||
- rust-nightly
|
||||
allow_failure: true
|
||||
js-release:
|
||||
stage: js-build
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- stable
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
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:
|
||||
- javascript
|
||||
push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
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:
|
||||
- curl
|
||||
- rust-test
|
||||
dependencies:
|
||||
- linux-stable
|
||||
|
||||
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
|
||||
|
||||
77
.travis.yml
Normal file
77
.travis.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: rust
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^beta-.*$/
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
env: RUN_TESTS="true"
|
||||
- rust: beta
|
||||
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"
|
||||
# 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
|
||||
- $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
|
||||
)
|
||||
|
||||
script:
|
||||
- if [ "$RUN_TESTS" = "true" ]; then ./test.sh --verbose; 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
2845
Cargo.lock
generated
2845
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
109
Cargo.toml
109
Cargo.toml
@@ -1,69 +1,47 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
description = "Ethcore client."
|
||||
name = "parity"
|
||||
version = "1.7.2"
|
||||
version = "1.3.3"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.1"
|
||||
syntex = "*"
|
||||
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.2"
|
||||
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"
|
||||
app_dirs = "1.1.1"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
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" }
|
||||
ethsync = { path = "sync" }
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
fdlimit = { path = "util/fdlimit" }
|
||||
ethcore = { path = "ethcore" }
|
||||
ethcore-util = { path = "util" }
|
||||
ethsync = { path = "sync" }
|
||||
ethcore-io = { path = "util/io" }
|
||||
ethcore-devtools = { path = "devtools" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-rpc = { path = "rpc" }
|
||||
ethcore-signer = { path = "signer" }
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
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" }
|
||||
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" }
|
||||
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"
|
||||
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
clippy = { version = "0.0.80", optional = true}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
@@ -71,40 +49,25 @@ winapi = "0.2"
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
daemonize = "0.2"
|
||||
|
||||
[dependencies.hyper]
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
ui = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui",
|
||||
]
|
||||
ui-precompiled = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui-precompiled",
|
||||
]
|
||||
ui-enabled = ["dapps"]
|
||||
dapps = ["parity-dapps"]
|
||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||
default = ["ui", "use-precompiled-js"]
|
||||
ui = ["dapps", "ethcore-signer/ui"]
|
||||
use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"]
|
||||
dapps = ["ethcore-dapps"]
|
||||
ipc = ["ethcore/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"]
|
||||
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"]
|
||||
|
||||
100
README.md
100
README.md
@@ -1,62 +1,56 @@
|
||||
# [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)
|
||||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url]
|
||||
|
||||
- [Download the latest release here.](https://github.com/paritytech/parity/releases)
|
||||
[Internal Documentation][doc-url]
|
||||
|
||||
### Join the chat!
|
||||
Be sure to check out [our wiki][wiki-url] for more information.
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
[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]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
[doc-url]: http://ethcore.github.io/parity/ethcore/index.html
|
||||
[wiki-url]: https://github.com/ethcore/parity/wiki
|
||||
|
||||
----
|
||||
|
||||
## 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'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:
|
||||
By default, Parity will run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
|
||||
of RPC APIs.
|
||||
|
||||
- create and manage your Ethereum accounts;
|
||||
- manage your Ether and any Ethereum tokens;
|
||||
- create and register your own tokens;
|
||||
- and much more.
|
||||
Parity also runs a server for running decentralized apps, or "Dapps", on `http://127.0.0.1:8080`.
|
||||
This includes a few useful Dapps, including Ethereum Wallet, Maker OTC, and a node status page.
|
||||
In a near-future release, it will be easy to install Dapps and use them through this web interface.
|
||||
|
||||
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][gitter-url] 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](https://gitter.im/paritytech/parity) 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.2. 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:
|
||||
|
||||
- Linux:
|
||||
- Linux and OSX:
|
||||
```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.
|
||||
- OSX:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
||||
- Windows
|
||||
|
||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
|
||||
@@ -69,23 +63,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,34 +83,10 @@ $ 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
|
||||
|
||||
```bash
|
||||
bash <(curl https://get.parity.io -Lk)
|
||||
```
|
||||
|
||||
## Start Parity
|
||||
### Manually
|
||||
To start Parity manually, just run
|
||||
To get started, just run
|
||||
```bash
|
||||
$ ./target/release/parity
|
||||
```
|
||||
|
||||
and Parity will begin syncing the Ethereum blockchain.
|
||||
|
||||
### Using systemd service file
|
||||
To start Parity as a regular user using systemd init:
|
||||
|
||||
1. Copy `parity/scripts/parity.service` to your
|
||||
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"`.
|
||||
and parity will begin syncing the Ethereum blockchain.
|
||||
|
||||
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.
|
||||
49
appveyor.yml
Normal file
49
appveyor.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
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
|
||||
|
||||
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.10.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.10.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 }
|
||||
- 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 Ethcore (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,46 @@
|
||||
[package]
|
||||
description = "Parity Dapps crate"
|
||||
name = "parity-dapps"
|
||||
version = "1.7.0"
|
||||
name = "ethcore-dapps"
|
||||
version = "1.3.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = ["Ethcore <admin@ethcore.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"
|
||||
jsonrpc-core = "2.1"
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" }
|
||||
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||
unicase = "1.3"
|
||||
url = "1.0"
|
||||
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" }
|
||||
|
||||
rustc-serialize = "0.3"
|
||||
serde = "0.7.0"
|
||||
serde_json = "0.7.0"
|
||||
serde_macros = { version = "0.7.0", optional = true }
|
||||
ethcore-rpc = { path = "../rpc" }
|
||||
ethcore-util = { path = "../util" }
|
||||
fetch = { path = "../util/fetch" }
|
||||
node-health = { path = "./node-health" }
|
||||
parity-hash-fetch = { path = "../hash-fetch" }
|
||||
parity-reactor = { path = "../util/reactor" }
|
||||
parity-ui = { path = "./ui" }
|
||||
parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
|
||||
# List of apps
|
||||
parity-dapps-status = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
|
||||
parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
|
||||
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true }
|
||||
mime_guess = { version = "1.6.1" }
|
||||
clippy = { version = "0.0.80", optional = true}
|
||||
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.7.0", optional = true }
|
||||
syntex = "*"
|
||||
|
||||
[features]
|
||||
dev = ["clippy", "ethcore-util/dev"]
|
||||
default = ["serde_codegen", "extra-dapps"]
|
||||
extra-dapps = ["parity-dapps-wallet"]
|
||||
nightly = ["serde_macros"]
|
||||
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
|
||||
|
||||
ui = ["parity-ui/no-precompiled-js"]
|
||||
ui-precompiled = ["parity-ui/use-precompiled-js"]
|
||||
use-precompiled-js = [
|
||||
"parity-dapps-status/use-precompiled-js",
|
||||
"parity-dapps-home/use-precompiled-js",
|
||||
"parity-dapps-wallet/use-precompiled-js"
|
||||
]
|
||||
|
||||
45
dapps/build.rs
Normal file
45
dapps/build.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
mod inner {
|
||||
extern crate syntex;
|
||||
extern crate serde_codegen;
|
||||
|
||||
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");
|
||||
|
||||
let mut registry = syntex::Registry::new();
|
||||
|
||||
serde_codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[package]
|
||||
description = "Base Package for all Parity built-in dapps"
|
||||
name = "parity-dapps-glue"
|
||||
version = "1.7.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 }
|
||||
|
||||
[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 }
|
||||
clippy = { version = "0.0.90", optional = true }
|
||||
|
||||
[features]
|
||||
dev = ["clippy"]
|
||||
default = ["with-syntex"]
|
||||
nightly = ["quasi_macros"]
|
||||
nightly-testing = ["clippy"]
|
||||
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
|
||||
use-precompiled-js = []
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Parity Dapps (JS-glue)
|
||||
|
||||
Code generator to simplify creating a built-in Parity Dapp
|
||||
|
||||
# How to create new builtin Dapp.
|
||||
1. Clone this repository.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity.git
|
||||
```
|
||||
|
||||
1. Create a new directory for your Dapp. (`./myapp`)
|
||||
|
||||
```bash
|
||||
$ mkdir -p ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Copy your frontend files to `./dapps/myapp/src/web` (bundled ones)
|
||||
|
||||
```bash
|
||||
$ cp -r ./myapp-src/* ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Instead of creating `web3` in your app. Load (as the first script tag in `head`):
|
||||
|
||||
```html
|
||||
<script src="/parity-utils/inject.js"></script>
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity-ui.git
|
||||
$ cd ./parity-ui/
|
||||
$ cp ./home/Cargo.toml ../parity/dapps/myapp/Cargo.toml
|
||||
$ cp ./home/build.rs ../parity/dapps/myapp/build.rs
|
||||
$ cp ./home/src/lib.rs ../parity/dapps/myapp/src/lib.rs
|
||||
$ cp ./home/src/lib.rs.in ../parity/dapps/myapp/src/lib.rs.in
|
||||
# And edit the details of your app
|
||||
$ vim ../parity/dapps/myapp/Cargo.toml # Edit the details
|
||||
$ vim ./parity/dapps/myapp/src/lib.rs.in # Edit the details
|
||||
```
|
||||
# How to include your Dapp into `Parity`?
|
||||
1. Edit `dapps/Cargo.toml` and add dependency to your application (it can be optional)
|
||||
|
||||
```toml
|
||||
# Use git repo and version
|
||||
parity-dapps-myapp = { path="./myapp" }
|
||||
```
|
||||
|
||||
1. Edit `dapps/src/apps.rs` and add your application to `all_pages` (if it's optional you need to specify two functions - see `parity-dapps-wallet` example)
|
||||
|
||||
1. Compile parity.
|
||||
|
||||
```bash
|
||||
$ cargo build --release # While inside `parity`
|
||||
```
|
||||
|
||||
1. Commit the results.
|
||||
|
||||
```bash
|
||||
$ git add myapp && git commit -am "My first Parity Dapp".
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
pub mod inner {
|
||||
use syntex;
|
||||
use codegen;
|
||||
use syntax::{ast, fold};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
fn strip_attributes(krate: ast::Crate) -> ast::Crate {
|
||||
/// Helper folder that strips the serde attributes after the extensions have been expanded.
|
||||
struct StripAttributeFolder;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Some(attr)
|
||||
}
|
||||
|
||||
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
|
||||
fold::noop_fold_mac(mac, self)
|
||||
}
|
||||
}
|
||||
|
||||
fold::Folder::fold_crate(&mut StripAttributeFolder, krate)
|
||||
}
|
||||
|
||||
pub fn register(reg: &mut syntex::Registry) {
|
||||
reg.add_attr("feature(custom_derive)");
|
||||
reg.add_attr("feature(custom_attribute)");
|
||||
|
||||
reg.add_decorator("derive_WebAppFiles", codegen::expand_webapp_implementation);
|
||||
reg.add_post_expansion_pass(strip_attributes);
|
||||
}
|
||||
|
||||
pub fn generate() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut registry = syntex::Registry::new();
|
||||
register(&mut registry);
|
||||
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
pub mod inner {
|
||||
use codegen;
|
||||
|
||||
pub fn register(reg: &mut rustc_plugin::Registry) {
|
||||
reg.register_syntax_extension(
|
||||
syntax::parse::token::intern("derive_WebAppFiles"),
|
||||
syntax::ext::base::MultiDecorator(
|
||||
Box::new(codegen::expand_webapp_implementation)));
|
||||
|
||||
reg.register_attribute("webapp".to_owned(), AttributeType::Normal);
|
||||
}
|
||||
|
||||
pub fn generate() {}
|
||||
}
|
||||
@@ -1,193 +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/>.
|
||||
|
||||
extern crate aster;
|
||||
extern crate glob;
|
||||
extern crate mime_guess;
|
||||
|
||||
use self::mime_guess::guess_mime_type;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::ops::Deref;
|
||||
|
||||
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;
|
||||
|
||||
pub fn expand_webapp_implementation(
|
||||
cx: &mut ExtCtxt,
|
||||
span: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotatable: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)
|
||||
) {
|
||||
let item = match *annotatable {
|
||||
Annotatable::Item(ref item) => item,
|
||||
_ => {
|
||||
cx.span_err(meta_item.span, "`#[derive(WebAppFiles)]` may only be applied to struct implementations");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let builder = aster::AstBuilder::new().span(span);
|
||||
implement_webapp(cx, &builder, item, push);
|
||||
}
|
||||
|
||||
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
|
||||
let static_files_dir = extract_path(cx, item);
|
||||
|
||||
let src = Path::new("src");
|
||||
let static_files = {
|
||||
let mut buf = src.to_path_buf();
|
||||
buf.push(static_files_dir.deref());
|
||||
buf
|
||||
};
|
||||
|
||||
let search_location = {
|
||||
let mut buf = static_files.to_path_buf();
|
||||
buf.push("**");
|
||||
buf.push("*");
|
||||
buf
|
||||
};
|
||||
|
||||
let files = glob::glob(search_location.to_str().expect("Valid UTF8 path"))
|
||||
.expect("The sources directory is missing.")
|
||||
.collect::<Result<Vec<PathBuf>, glob::GlobError>>()
|
||||
.expect("There should be no error when reading a list of files.");
|
||||
|
||||
let statements = files
|
||||
.iter()
|
||||
.filter(|path_buf| path_buf.is_file())
|
||||
.map(|path_buf| {
|
||||
let path = path_buf.as_path();
|
||||
let filename = path.file_name().and_then(|s| s.to_str()).expect("Only UTF8 paths.");
|
||||
let mime_type = guess_mime_type(filename).to_string();
|
||||
let file_path = as_uri(path.strip_prefix(&static_files).ok().expect("Prefix is always there, cause it's absolute path;qed"));
|
||||
let file_path_in_source = path.to_str().expect("Only UTF8 paths.");
|
||||
|
||||
let path_lit = builder.expr().str(file_path.as_str());
|
||||
let mime_lit = builder.expr().str(mime_type.as_str());
|
||||
let web_path_lit = builder.expr().str(file_path_in_source);
|
||||
let separator_lit = builder.expr().str(path::MAIN_SEPARATOR.to_string().as_str());
|
||||
let concat_id = builder.id("concat!");
|
||||
let env_id = builder.id("env!");
|
||||
let macro_id = builder.id("include_bytes!");
|
||||
|
||||
let content = quote_expr!(
|
||||
cx,
|
||||
$macro_id($concat_id($env_id("CARGO_MANIFEST_DIR"), $separator_lit, $web_path_lit))
|
||||
);
|
||||
quote_stmt!(
|
||||
cx,
|
||||
files.insert($path_lit, File { path: $path_lit, content_type: $mime_lit, content: $content });
|
||||
).expect("The statement is always ok, because it just uses literals.")
|
||||
}).collect::<Vec<ast::Stmt>>();
|
||||
|
||||
let type_name = item.ident;
|
||||
|
||||
let files_impl = quote_item!(cx,
|
||||
impl $type_name {
|
||||
fn files() -> ::std::collections::HashMap<&'static str, File> {
|
||||
let mut files = ::std::collections::HashMap::new();
|
||||
$statements
|
||||
files
|
||||
}
|
||||
}
|
||||
).unwrap();
|
||||
|
||||
push(Annotatable::Item(files_impl));
|
||||
}
|
||||
|
||||
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
|
||||
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) {
|
||||
return s.deref().to_owned();
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
"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> {
|
||||
match lit.node {
|
||||
ast::LitKind::Str(ref s, _) => Some(s.clone().as_str()),
|
||||
_ => {
|
||||
cx.span_err(
|
||||
lit.span,
|
||||
&format!("webapp annotation path must be a string, not `{}`",
|
||||
lit_to_string(lit)
|
||||
)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_uri(path: &Path) -> String {
|
||||
let mut s = String::new();
|
||||
for component in path.iter() {
|
||||
s.push_str(component.to_str().expect("Only UTF-8 filenames are supported."));
|
||||
s.push('/');
|
||||
}
|
||||
s[0..s.len()-1].into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_convert_path_separators_on_all_platforms() {
|
||||
// given
|
||||
let p = {
|
||||
let mut p = PathBuf::new();
|
||||
p.push("web");
|
||||
p.push("src");
|
||||
p.push("index.html");
|
||||
p
|
||||
};
|
||||
|
||||
// when
|
||||
let path = as_uri(&p);
|
||||
|
||||
// then
|
||||
assert_eq!(path, "web/src/index.html".to_owned());
|
||||
}
|
||||
@@ -1,89 +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/>.
|
||||
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(dead_code))]
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(unused_imports))]
|
||||
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
mod platform {
|
||||
use std::process::Command;
|
||||
|
||||
pub static NPM_CMD: &'static str = "npm";
|
||||
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod platform {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub static NPM_CMD: &'static str = "npm.cmd";
|
||||
// NOTE [ToDr] For some reason on windows
|
||||
// We cannot have any file descriptors open when running a child process
|
||||
// during build phase.
|
||||
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
}
|
||||
}
|
||||
|
||||
fn die<T : fmt::Debug>(s: &'static str, e: T) -> ! {
|
||||
panic!("Error: {}: {:?}", s, e);
|
||||
}
|
||||
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
pub fn test(_path: &str) {
|
||||
}
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
pub fn build(_path: &str, _dest: &str) {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use-precompiled-js"))]
|
||||
pub fn build(path: &str, dest: &str) {
|
||||
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
|
||||
.arg("install")
|
||||
.arg("--no-progress")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Installing node.js dependencies with npm", e));
|
||||
assert!(child.success(), "There was an error installing dependencies.");
|
||||
|
||||
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
|
||||
.arg("run")
|
||||
.arg("build")
|
||||
.env("NODE_ENV", "production")
|
||||
.env("BUILD_DEST", dest)
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Building JS code", e));
|
||||
assert!(child.success(), "There was an error build JS code.");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use-precompiled-js"))]
|
||||
pub fn test(path: &str) {
|
||||
let child = Command::new(platform::NPM_CMD)
|
||||
.arg("run")
|
||||
.arg("test")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Running test command", e));
|
||||
assert!(child.success(), "There was an error while running JS tests.");
|
||||
}
|
||||
@@ -1,38 +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/>.
|
||||
|
||||
|
||||
#![cfg_attr(not(feature = "with-syntex"), feature(rustc_private, plugin))]
|
||||
#![cfg_attr(not(feature = "with-syntex"), plugin(quasi_macros))]
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
extern crate syntex;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
extern crate syntex_syntax as syntax;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
#[macro_use]
|
||||
extern crate syntax;
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
extern crate rustc_plugin;
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
include!("lib.rs.in");
|
||||
@@ -1,46 +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/>.
|
||||
|
||||
extern crate quasi;
|
||||
|
||||
mod codegen;
|
||||
mod build;
|
||||
pub mod js;
|
||||
pub use build::inner::generate;
|
||||
|
||||
use std::default::Default;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File {
|
||||
pub path: &'static str,
|
||||
pub content: &'static [u8],
|
||||
// TODO: use strongly-typed MIME.
|
||||
pub content_type: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Info {
|
||||
pub name: &'static str,
|
||||
pub version: &'static str,
|
||||
pub author: &'static str,
|
||||
pub description: &'static str,
|
||||
pub icon_url: &'static str,
|
||||
}
|
||||
|
||||
pub trait WebApp : Default + Send + Sync {
|
||||
fn file(&self, path: &str) -> Option<&File>;
|
||||
fn info(&self) -> Info;
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,126 +15,67 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||
use hyper::method::Method;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use api::{response, types};
|
||||
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 hyper::{server, net, Decoder, Encoder, Next};
|
||||
use api::types::{App, ApiError};
|
||||
use api::response::{as_json, as_json_error, ping_response};
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
local_domain: String,
|
||||
endpoints: Arc<Endpoints>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
remote: Remote,
|
||||
) -> Box<Endpoint> {
|
||||
pub fn new(local_domain: String, endpoints: Arc<Endpoints>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
fetcher,
|
||||
health,
|
||||
remote,
|
||||
local_domain: local_domain,
|
||||
endpoints: endpoints,
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
|
||||
Box::new(RestApiRouter {
|
||||
api: self.clone(),
|
||||
handler: as_json_error(&ApiError {
|
||||
code: "404".into(),
|
||||
title: "Not Found".into(),
|
||||
detail: "Resource you requested has not been found.".into(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct RestApiRouter {
|
||||
api: RestApi,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
}
|
||||
|
||||
impl RestApiRouter {
|
||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||
RestApiRouter {
|
||||
path: Some(path),
|
||||
control: Some(control),
|
||||
api: api,
|
||||
handler: Box::new(response::as_json_error(StatusCode::NotFound, &types::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))
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
if let Method::Options = *request.method() {
|
||||
self.handler = response::empty();
|
||||
return Next::write();
|
||||
}
|
||||
|
||||
// TODO [ToDr] Consider using `path.app_params` instead
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
let url = extract_url(&request);
|
||||
if url.is_none() {
|
||||
// Just return 404 if we can't parse URL
|
||||
return Next::write();
|
||||
}
|
||||
|
||||
let url = url.expect("Check for None early-exists above; qed");
|
||||
let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed");
|
||||
let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed");
|
||||
|
||||
let url = url.expect("Check for None is above; qed");
|
||||
let endpoint = url.path.get(1).map(|v| v.as_str());
|
||||
let hash = url.path.get(2).map(|v| v.as_str());
|
||||
// at this point path.app_id contains 'api', adjust it to the hash properly, otherwise
|
||||
// we will try and retrieve 'api' as the hash when doing the /api/content route
|
||||
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
|
||||
|
||||
let handler = endpoint.and_then(|v| match v {
|
||||
"ping" => Some(response::ping()),
|
||||
"health" => Some(self.health(control)),
|
||||
"content" => self.resolve_content(hash, path, control),
|
||||
_ => None
|
||||
"apps" => Some(as_json(&self.api.list_apps())),
|
||||
"ping" => Some(ping_response(&self.api.local_domain)),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// Overwrite default
|
||||
@@ -156,4 +97,5 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
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 Ethcore (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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,27 +16,21 @@
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use endpoint::Handler;
|
||||
use handlers::{ContentHandler, EchoHandler};
|
||||
|
||||
pub fn empty() -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
|
||||
pub fn as_json<T : Serialize>(val: &T) -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
ContentHandler::new(status, json, mime!(Application/Json))
|
||||
pub fn as_json_error<T : Serialize>(val: &T) -> Box<Handler> {
|
||||
Box::new(ContentHandler::not_found(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
|
||||
}
|
||||
|
||||
pub fn as_json_error<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
ContentHandler::new(status, json, mime!(Application/Json))
|
||||
}
|
||||
|
||||
pub fn ping() -> Box<Handler> {
|
||||
Box::new(EchoHandler::default())
|
||||
pub fn ping_response(local_domain: &str) -> Box<Handler> {
|
||||
Box::new(EchoHandler::cors(vec![
|
||||
format!("http://{}", local_domain),
|
||||
// Allow CORS calls also for localhost
|
||||
format!("http://{}", local_domain.replace("127.0.0.1", "localhost")),
|
||||
]))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
use endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct App {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@@ -42,14 +41,10 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<EndpointInfo> for App {
|
||||
fn into(self) -> EndpointInfo {
|
||||
EndpointInfo {
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
version: self.version,
|
||||
author: self.author,
|
||||
icon_url: self.icon_url,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ApiError {
|
||||
pub code: String,
|
||||
pub title: String,
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
@@ -1,128 +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/>.
|
||||
|
||||
//! Fetchable Dapps support.
|
||||
|
||||
use std::fs;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use page::LocalPageEndpoint;
|
||||
use handlers::FetchControl;
|
||||
|
||||
pub enum ContentStatus {
|
||||
Fetching(FetchControl),
|
||||
Ready(LocalPageEndpoint),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContentCache {
|
||||
cache: LinkedHashMap<String, ContentStatus>,
|
||||
}
|
||||
|
||||
impl ContentCache {
|
||||
pub fn insert(&mut self, content_id: String, status: ContentStatus) -> Option<ContentStatus> {
|
||||
self.cache.insert(content_id, status)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, content_id: &str) -> Option<ContentStatus> {
|
||||
self.cache.remove(content_id)
|
||||
}
|
||||
|
||||
pub fn get(&mut self, content_id: &str) -> Option<&mut ContentStatus> {
|
||||
self.cache.get_refresh(content_id)
|
||||
}
|
||||
|
||||
pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> {
|
||||
let len = self.cache.len();
|
||||
|
||||
if len <= expected_size {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut removed = Vec::with_capacity(len - expected_size);
|
||||
|
||||
while self.cache.len() > expected_size {
|
||||
let entry = self.cache.pop_front().expect("expected_size bounded at 0, len is greater; qed");
|
||||
|
||||
match entry.1 {
|
||||
ContentStatus::Fetching(ref fetch) => {
|
||||
trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
|
||||
// Mark as aborted
|
||||
fetch.abort()
|
||||
},
|
||||
ContentStatus::Ready(ref endpoint) => {
|
||||
trace!(target: "dapps", "Removing {} because of limit.", entry.0);
|
||||
// Remove path (dir or file)
|
||||
let res = fs::remove_dir_all(&endpoint.path()).or_else(|_| fs::remove_file(&endpoint.path()));
|
||||
if let Err(e) = res {
|
||||
warn!(target: "dapps", "Unable to remove dapp/content from cache: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removed.push(entry);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn only_keys(data: Vec<(String, ContentStatus)>) -> Vec<String> {
|
||||
data.into_iter().map(|x| x.0).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_least_recently_used() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["a"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_update_lru_if_accessed() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
cache.get("a");
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["b"]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,256 +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 zip;
|
||||
use std::{fs, fmt};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use fetch::{self, Mime};
|
||||
use util::H256;
|
||||
|
||||
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>;
|
||||
|
||||
fn write_response_and_check_hash(
|
||||
id: &str,
|
||||
mut content_path: PathBuf,
|
||||
filename: &str,
|
||||
response: fetch::Response
|
||||
) -> Result<(fs::File, PathBuf), ValidationError> {
|
||||
// try to parse id
|
||||
let id = id.parse().map_err(|_| ValidationError::InvalidContentId)?;
|
||||
|
||||
// check if content exists
|
||||
if content_path.exists() {
|
||||
warn!(target: "dapps", "Overwriting existing content at 0x{:?}", id);
|
||||
fs::remove_dir_all(&content_path)?
|
||||
}
|
||||
|
||||
// create directory
|
||||
fs::create_dir_all(&content_path)?;
|
||||
|
||||
// append filename
|
||||
content_path.push(filename);
|
||||
|
||||
// Now write the response
|
||||
let mut file = io::BufWriter::new(fs::File::create(&content_path)?);
|
||||
let mut reader = io::BufReader::new(response);
|
||||
io::copy(&mut reader, &mut file)?;
|
||||
file.flush()?;
|
||||
|
||||
// Validate hash
|
||||
// TODO [ToDr] calculate sha3 in-flight while reading the response
|
||||
let mut file = io::BufReader::new(fs::File::open(&content_path)?);
|
||||
let hash = sha3(&mut file)?;
|
||||
if id == hash {
|
||||
Ok((file.into_inner(), content_path))
|
||||
} else {
|
||||
Err(ValidationError::HashMismatch {
|
||||
expected: id,
|
||||
got: hash,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Content {
|
||||
id: String,
|
||||
mime: Mime,
|
||||
content_path: PathBuf,
|
||||
on_done: OnDone,
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn new(id: String, mime: Mime, content_path: PathBuf, on_done: OnDone) -> Self {
|
||||
Content {
|
||||
id: id,
|
||||
mime: mime,
|
||||
content_path: content_path,
|
||||
on_done: on_done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentValidator for Content {
|
||||
type Error = ValidationError;
|
||||
|
||||
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
|
||||
let validate = |content_path: PathBuf| {
|
||||
// Create dir
|
||||
let (_, content_path) = write_response_and_check_hash(self.id.as_str(), content_path.clone(), self.id.as_str(), response)?;
|
||||
|
||||
Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))
|
||||
};
|
||||
|
||||
// Prepare path for a file
|
||||
let content_path = self.content_path.join(&self.id);
|
||||
// Make sure to always call on_done (even in case of errors)!
|
||||
let result = validate(content_path.clone());
|
||||
// remove the file if there was an error
|
||||
if result.is_err() {
|
||||
// Ignore errors since the file might not exist
|
||||
let _ = fs::remove_dir_all(&content_path);
|
||||
}
|
||||
(self.on_done)(result.as_ref().ok().cloned());
|
||||
result.map(ValidatorResponse::Local)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dapp {
|
||||
id: String,
|
||||
dapps_path: PathBuf,
|
||||
on_done: OnDone,
|
||||
embeddable_on: Embeddable,
|
||||
}
|
||||
|
||||
impl Dapp {
|
||||
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
|
||||
Dapp {
|
||||
id,
|
||||
dapps_path,
|
||||
on_done,
|
||||
embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_manifest(zip: &mut zip::ZipArchive<fs::File>) -> Result<(Manifest, PathBuf), ValidationError> {
|
||||
for i in 0..zip.len() {
|
||||
let mut file = zip.by_index(i)?;
|
||||
|
||||
if !file.name().ends_with(MANIFEST_FILENAME) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to read manifest
|
||||
let mut manifest = String::new();
|
||||
let manifest = file
|
||||
.read_to_string(&mut manifest).ok()
|
||||
.and_then(|_| deserialize_manifest(manifest).ok());
|
||||
|
||||
if let Some(manifest) = manifest {
|
||||
let mut manifest_location = PathBuf::from(file.name());
|
||||
manifest_location.pop(); // get rid of filename
|
||||
return Ok((manifest, manifest_location));
|
||||
}
|
||||
}
|
||||
|
||||
Err(ValidationError::ManifestNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentValidator for Dapp {
|
||||
type Error = ValidationError;
|
||||
|
||||
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
|
||||
let validate = |dapp_path: PathBuf| {
|
||||
let (file, zip_path) = write_response_and_check_hash(self.id.as_str(), dapp_path.clone(), &format!("{}.zip", self.id), response)?;
|
||||
trace!(target: "dapps", "Opening dapp bundle at {:?}", zip_path);
|
||||
// Unpack archive
|
||||
let mut zip = zip::ZipArchive::new(file)?;
|
||||
// First find manifest file
|
||||
let (mut manifest, manifest_dir) = Self::find_manifest(&mut zip)?;
|
||||
// Overwrite id to match hash
|
||||
manifest.id = self.id.clone();
|
||||
|
||||
// Unpack zip
|
||||
for i in 0..zip.len() {
|
||||
let mut file = zip.by_index(i)?;
|
||||
let is_dir = file.name().chars().rev().next() == Some('/');
|
||||
|
||||
let file_path = PathBuf::from(file.name());
|
||||
let location_in_manifest_base = file_path.strip_prefix(&manifest_dir);
|
||||
// Create files that are inside manifest directory
|
||||
if let Ok(location_in_manifest_base) = location_in_manifest_base {
|
||||
let p = dapp_path.join(location_in_manifest_base);
|
||||
// Check if it's a directory
|
||||
if is_dir {
|
||||
fs::create_dir_all(p)?;
|
||||
} else {
|
||||
let mut target = fs::File::create(p)?;
|
||||
io::copy(&mut file, &mut target)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove zip
|
||||
fs::remove_file(&zip_path)?;
|
||||
|
||||
// Write manifest
|
||||
let manifest_str = serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)?;
|
||||
let manifest_path = dapp_path.join(MANIFEST_FILENAME);
|
||||
let mut manifest_file = fs::File::create(manifest_path)?;
|
||||
manifest_file.write_all(manifest_str.as_bytes())?;
|
||||
// Create endpoint
|
||||
let endpoint = LocalPageEndpoint::new(dapp_path, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
|
||||
Ok(endpoint)
|
||||
};
|
||||
|
||||
// Prepare directory for dapp
|
||||
let target = self.dapps_path.join(&self.id);
|
||||
// Validate the dapp
|
||||
let result = validate(target.clone());
|
||||
// remove the file if there was an error
|
||||
if result.is_err() {
|
||||
// Ignore errors since the file might not exist
|
||||
let _ = fs::remove_dir_all(&target);
|
||||
}
|
||||
(self.on_done)(result.as_ref().ok().cloned());
|
||||
result.map(ValidatorResponse::Local)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ValidationError {
|
||||
Io(io::Error),
|
||||
Zip(zip::result::ZipError),
|
||||
InvalidContentId,
|
||||
ManifestNotFound,
|
||||
ManifestSerialization(String),
|
||||
HashMismatch { expected: H256, got: H256, },
|
||||
}
|
||||
|
||||
impl fmt::Display for ValidationError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io),
|
||||
ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip),
|
||||
ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."),
|
||||
ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."),
|
||||
ValidationError::ManifestSerialization(ref err) => {
|
||||
write!(f, "There was an error during Dapp Manifest serialization: {:?}", err)
|
||||
},
|
||||
ValidationError::HashMismatch { ref expected, ref got } => {
|
||||
write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ValidationError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
ValidationError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<zip::result::ZipError> for ValidationError {
|
||||
fn from(err: zip::result::ZipError) -> Self {
|
||||
ValidationError::Zip(err)
|
||||
}
|
||||
}
|
||||
@@ -1,314 +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/>.
|
||||
|
||||
//! Fetchable Dapps support.
|
||||
//! Manages downloaded (cached) Dapps and downloads them when necessary.
|
||||
//! Uses `URLHint` to resolve addresses into Dapps bundle file location.
|
||||
|
||||
mod installers;
|
||||
|
||||
use std::{fs, env};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use rustc_hex::FromHex;
|
||||
use fetch::{Client as FetchClient, Fetch};
|
||||
use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use hyper;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use {Embeddable, SyncStatus, random_filename};
|
||||
use util::Mutex;
|
||||
use page::LocalPageEndpoint;
|
||||
use handlers::{ContentHandler, ContentFetcherHandler};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
use apps::cache::{ContentCache, ContentStatus};
|
||||
|
||||
/// Limit of cached dapps/content
|
||||
const MAX_CACHED_DAPPS: usize = 20;
|
||||
|
||||
pub trait Fetcher: Send + Sync + 'static {
|
||||
fn contains(&self, content_id: &str) -> bool;
|
||||
|
||||
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,
|
||||
resolver: R,
|
||||
cache: Arc<Mutex<ContentCache>>,
|
||||
sync: Arc<SyncStatus>,
|
||||
embeddable_on: Embeddable,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
only_content: bool,
|
||||
}
|
||||
|
||||
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
fn drop(&mut self) {
|
||||
// Clear cache path
|
||||
let _ = fs::remove_dir_all(&self.cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint + '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());
|
||||
|
||||
ContentFetcher {
|
||||
cache_path: cache_path,
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_on: None,
|
||||
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> {
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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> {
|
||||
fn contains(&self, content_id: &str) -> bool {
|
||||
{
|
||||
let mut cache = self.cache.lock();
|
||||
// Check if we already have the app
|
||||
if cache.get(content_id).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// fallback to resolver
|
||||
if let Ok(content_id) = content_id.from_hex() {
|
||||
// if there is content or we are syncing return true
|
||||
self.sync.is_major_importing() || self.resolve(content_id).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
let mut cache = self.cache.lock();
|
||||
let content_id = path.app_id.clone();
|
||||
|
||||
let (new_status, handler) = {
|
||||
let status = cache.get(&content_id);
|
||||
match status {
|
||||
// Just serve the content
|
||||
Some(&mut ContentStatus::Ready(ref endpoint)) => {
|
||||
(None, endpoint.to_async_handler(path, control))
|
||||
},
|
||||
// Content is already being fetched
|
||||
Some(&mut ContentStatus::Fetching(ref fetch_control)) if !fetch_control.is_deadline_reached() => {
|
||||
trace!(target: "dapps", "Content fetching in progress. Waiting...");
|
||||
(None, fetch_control.to_async_handler(path, control))
|
||||
},
|
||||
// We need to start fetching the content
|
||||
_ => {
|
||||
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 cache = self.cache.clone();
|
||||
let id = content_id.clone();
|
||||
let on_done = move |result: Option<LocalPageEndpoint>| {
|
||||
let mut cache = cache.lock();
|
||||
match result {
|
||||
Some(endpoint) => cache.insert(id.clone(), ContentStatus::Ready(endpoint)),
|
||||
// In case of error
|
||||
None => cache.remove(&id),
|
||||
};
|
||||
};
|
||||
|
||||
match content {
|
||||
// Don't serve dapps if we are still syncing (but serve content)
|
||||
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(),
|
||||
path,
|
||||
control,
|
||||
installers::Dapp::new(
|
||||
content_id.clone(),
|
||||
self.cache_path.clone(),
|
||||
Box::new(on_done),
|
||||
self.embeddable_on.clone(),
|
||||
),
|
||||
self.embeddable_on.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch.clone(),
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
|
||||
},
|
||||
Some(URLHintResult::Content(content)) => {
|
||||
let handler = ContentFetcherHandler::new(
|
||||
content.url,
|
||||
path,
|
||||
control,
|
||||
installers::Content::new(
|
||||
content_id.clone(),
|
||||
content.mime,
|
||||
self.cache_path.clone(),
|
||||
Box::new(on_done),
|
||||
),
|
||||
self.embeddable_on.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch.clone(),
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
|
||||
},
|
||||
None if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
||||
},
|
||||
None => {
|
||||
// This may happen when sync status changes in between
|
||||
// `contains` and `to_handler`
|
||||
(None, Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"Resource Not Found",
|
||||
"Requested resource was not found.",
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
)) as Box<Handler>)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(status) = new_status {
|
||||
cache.clear_garbage(MAX_CACHED_DAPPS);
|
||||
cache.insert(content_id, status);
|
||||
}
|
||||
|
||||
handler
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env;
|
||||
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;
|
||||
|
||||
use apps::cache::ContentStatus;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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 handler = LocalPageEndpoint::new(path, EndpointInfo {
|
||||
name: "fake".into(),
|
||||
description: "".into(),
|
||||
version: "".into(),
|
||||
author: "".into(),
|
||||
icon_url: "".into(),
|
||||
}, Default::default(), None);
|
||||
|
||||
// when
|
||||
fetcher.set_status("test", ContentStatus::Ready(handler));
|
||||
fetcher.set_status("test2", ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// then
|
||||
assert_eq!(fetcher.contains("test"), true);
|
||||
assert_eq!(fetcher.contains("test2"), true);
|
||||
assert_eq!(fetcher.contains("test3"), false);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,15 +14,14 @@
|
||||
// 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 serde_json;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use endpoint::{Endpoint, EndpointInfo};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
use Embeddable;
|
||||
use std::path::PathBuf;
|
||||
use page::LocalPageEndpoint;
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use api::App;
|
||||
|
||||
struct LocalDapp {
|
||||
id: String,
|
||||
@@ -30,86 +29,17 @@ struct LocalDapp {
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
|
||||
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
|
||||
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
|
||||
path.push(MANIFEST_FILENAME);
|
||||
|
||||
fs::File::open(path.clone())
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
.and_then(|mut f| {
|
||||
// Reat file
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
|
||||
// Try to deserialize manifest
|
||||
deserialize_manifest(s)
|
||||
})
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
|
||||
|
||||
EndpointInfo {
|
||||
name: name.into(),
|
||||
description: name.into(),
|
||||
version: "0.0.0".into(),
|
||||
author: "?".into(),
|
||||
icon_url: "icon.png".into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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>)> {
|
||||
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())
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
|
||||
// try to get manifest file
|
||||
let info = read_manifest(&name, path.clone());
|
||||
LocalDapp {
|
||||
id: name,
|
||||
path: path,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
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()))
|
||||
);
|
||||
}
|
||||
pages
|
||||
}
|
||||
|
||||
|
||||
fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
|
||||
let files = fs::read_dir(dapps_path);
|
||||
fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
|
||||
let files = fs::read_dir(dapps_path.as_str());
|
||||
if let Err(e) = files {
|
||||
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path.display(), e);
|
||||
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let files = files.expect("Check is done earlier");
|
||||
files.map(|dir| {
|
||||
let entry = dir?;
|
||||
let file_type = entry.file_type()?;
|
||||
let entry = try!(dir);
|
||||
let file_type = try!(entry.file_type());
|
||||
|
||||
// skip files
|
||||
if file_type.is_file() {
|
||||
@@ -130,6 +60,57 @@ fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
|
||||
}
|
||||
m.ok()
|
||||
})
|
||||
.map(|(name, path)| local_dapp(name, path))
|
||||
.map(|(name, path)| {
|
||||
// try to get manifest file
|
||||
let info = read_manifest(&name, path.clone());
|
||||
LocalDapp {
|
||||
id: name,
|
||||
path: path,
|
||||
info: info,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
|
||||
path.push("manifest.json");
|
||||
|
||||
fs::File::open(path.clone())
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
.and_then(|mut f| {
|
||||
// Reat file
|
||||
let mut s = String::new();
|
||||
try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e)));
|
||||
// Try to deserialize manifest
|
||||
serde_json::from_str::<App>(&s).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
.map(|app| EndpointInfo {
|
||||
name: app.name,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
author: app.author,
|
||||
icon_url: app.icon_url,
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
|
||||
|
||||
EndpointInfo {
|
||||
name: name.into(),
|
||||
description: name.into(),
|
||||
version: "0.0.0".into(),
|
||||
author: "?".into(),
|
||||
icon_url: "icon.png".into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn local_endpoints(dapps_path: String) -> Endpoints {
|
||||
let mut pages = Endpoints::new();
|
||||
for dapp in local_dapps(dapps_path) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info))
|
||||
);
|
||||
}
|
||||
pages
|
||||
}
|
||||
|
||||
@@ -1,29 +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 serde_json;
|
||||
pub use apps::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
pub fn deserialize_manifest(manifest: String) -> Result<Manifest, String> {
|
||||
serde_json::from_str::<Manifest>(&manifest).map_err(|e| format!("{:?}", e))
|
||||
// TODO [todr] Manifest validation (especialy: id (used as path))
|
||||
}
|
||||
|
||||
pub fn serialize_manifest(manifest: &Manifest) -> Result<String, String> {
|
||||
serde_json::to_string_pretty(manifest).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,84 +14,56 @@
|
||||
// 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;
|
||||
use web::Web;
|
||||
use fetch::Fetch;
|
||||
use parity_dapps::WebApp;
|
||||
use parity_reactor::Remote;
|
||||
use parity_ui;
|
||||
use {WebProxyTokens, ParentFrameSettings};
|
||||
|
||||
mod app;
|
||||
mod cache;
|
||||
mod fs;
|
||||
mod ui;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
|
||||
pub use self::app::App;
|
||||
extern crate parity_dapps_status;
|
||||
extern crate parity_dapps_home;
|
||||
|
||||
pub const HOME_PAGE: &'static str = "home";
|
||||
pub const RPC_PATH: &'static str = "rpc";
|
||||
pub const API_PATH: &'static str = "api";
|
||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
||||
pub const WEB_PATH: &'static str = "web";
|
||||
pub const URL_REFERER: &'static str = "__referer=";
|
||||
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";
|
||||
|
||||
pub fn main_page() -> &'static str {
|
||||
"/home/"
|
||||
}
|
||||
|
||||
pub fn utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
Box::new(PageEndpoint::with_prefix(parity_dapps_home::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>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Endpoints {
|
||||
pub fn all_endpoints(dapps_path: String) -> Endpoints {
|
||||
// fetch fs dapps at first to avoid overwriting builtins
|
||||
let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
|
||||
for path in extra_dapps {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
|
||||
pages.insert(id, endpoint);
|
||||
} else {
|
||||
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
|
||||
}
|
||||
}
|
||||
let mut pages = fs::local_endpoints(dapps_path);
|
||||
// Home page needs to be safe embed
|
||||
// because we use Cross-Origin LocalStorage.
|
||||
// TODO [ToDr] Account naming should be moved to parity.
|
||||
pages.insert("home".into(), Box::new(
|
||||
PageEndpoint::new_safe_to_embed(parity_dapps_home::App::default())
|
||||
));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed());
|
||||
insert::<parity_dapps_status::App>(&mut pages, "parity");
|
||||
insert::<parity_dapps_status::App>(&mut pages, "status");
|
||||
|
||||
// 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()));
|
||||
// Optional dapps
|
||||
wallet_page(&mut pages);
|
||||
|
||||
Arc::new(pages)
|
||||
pages
|
||||
}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpoint>>, 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()),
|
||||
}));
|
||||
#[cfg(feature = "parity-dapps-wallet")]
|
||||
fn wallet_page(pages: &mut Endpoints) {
|
||||
extern crate parity_dapps_wallet;
|
||||
insert::<parity_dapps_wallet::App>(pages, "wallet");
|
||||
}
|
||||
#[cfg(not(feature = "parity-dapps-wallet"))]
|
||||
fn wallet_page(_pages: &mut Endpoints) {}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<ParentFrameSettings>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
|
||||
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
|
||||
}
|
||||
|
||||
@@ -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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,18 +16,14 @@
|
||||
|
||||
//! URL Endpoint traits
|
||||
|
||||
use std::sync::Arc;
|
||||
use hyper::{server, net};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hyper::{self, server, net};
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -39,17 +35,11 @@ pub struct EndpointInfo {
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
pub type Endpoints = Arc<BTreeMap<String, Box<Endpoint>>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
pub trait Endpoint : Send + Sync {
|
||||
fn info(&self) -> Option<&EndpointInfo> { None }
|
||||
|
||||
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
|
||||
panic!("This Endpoint is asynchronous and requires Control object.");
|
||||
}
|
||||
|
||||
fn to_async_handler(&self, path: EndpointPath, _control: hyper::Control) -> Box<Handler> {
|
||||
self.to_handler(path)
|
||||
}
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<net::HttpStream> + Send>;
|
||||
}
|
||||
|
||||
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/parity-utils/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="parity-navbar">
|
||||
</div>
|
||||
<div class="parity-box">
|
||||
<h1>{title}</h1>
|
||||
<h3>{message}</h3>
|
||||
<p><code>{details}</code></p>
|
||||
</div>
|
||||
<div class="parity-status">
|
||||
<small>{version}</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,66 +16,52 @@
|
||||
|
||||
//! Simple Content Handler
|
||||
|
||||
use std::io::Write;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::mime::Mime;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use util::version;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
use Embeddable;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContentHandler {
|
||||
code: StatusCode,
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
mimetype: String,
|
||||
write_pos: usize,
|
||||
safe_to_embed_on: Embeddable,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
pub fn ok(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::Ok, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> 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 {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
version=version(),
|
||||
), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self {
|
||||
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 ok(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code,
|
||||
content,
|
||||
mimetype,
|
||||
code: StatusCode::Ok,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forbidden(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code: StatusCode::Forbidden,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_found(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code: StatusCode::NotFound,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(code: StatusCode, content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0,
|
||||
safe_to_embed_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,8 +77,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());
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap()));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -17,25 +17,82 @@
|
||||
//! Echo Handler
|
||||
|
||||
use std::io::Read;
|
||||
use hyper::{server, Decoder, Encoder, Next};
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::method::Method;
|
||||
use hyper::net::HttpStream;
|
||||
use unicase::UniCase;
|
||||
use super::ContentHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Type of Cross-Origin request
|
||||
enum Cors {
|
||||
/// Not a Cross-Origin request - no headers needed
|
||||
No,
|
||||
/// Cross-Origin request with valid Origin
|
||||
Allowed(String),
|
||||
/// Cross-Origin request with invalid Origin
|
||||
Forbidden,
|
||||
}
|
||||
|
||||
pub struct EchoHandler {
|
||||
safe_origins: Vec<String>,
|
||||
content: String,
|
||||
cors: Cors,
|
||||
handler: Option<ContentHandler>,
|
||||
}
|
||||
|
||||
impl EchoHandler {
|
||||
|
||||
pub fn cors(safe_origins: Vec<String>) -> Self {
|
||||
EchoHandler {
|
||||
safe_origins: safe_origins,
|
||||
content: String::new(),
|
||||
cors: Cors::Forbidden,
|
||||
handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn cors_header(&self, origin: Option<String>) -> Cors {
|
||||
fn origin_is_allowed(origin: &str, safe_origins: &[String]) -> bool {
|
||||
for safe in safe_origins {
|
||||
if origin.starts_with(safe) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
match origin {
|
||||
Some(ref origin) if origin_is_allowed(origin, &self.safe_origins) => {
|
||||
Cors::Allowed(origin.clone())
|
||||
},
|
||||
None => Cors::No,
|
||||
_ => Cors::Forbidden,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for EchoHandler {
|
||||
fn on_request(&mut self, _: server::Request<HttpStream>) -> Next {
|
||||
Next::read()
|
||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||
let origin = request.headers().get_raw("origin")
|
||||
.and_then(|list| list.get(0))
|
||||
.and_then(|origin| String::from_utf8(origin.clone()).ok());
|
||||
|
||||
self.cors = self.cors_header(origin);
|
||||
|
||||
// Don't even read the payload if origin is forbidden!
|
||||
if let Cors::Forbidden = self.cors {
|
||||
self.handler = Some(ContentHandler::ok(String::new(), "text/plain".into()));
|
||||
Next::write()
|
||||
} else {
|
||||
Next::read()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
match decoder.read_to_string(&mut self.content) {
|
||||
Ok(0) => {
|
||||
self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
|
||||
self.handler = Some(ContentHandler::ok(self.content.clone(), "application/json".into()));
|
||||
Next::write()
|
||||
},
|
||||
Ok(_) => Next::read(),
|
||||
@@ -47,14 +104,45 @@ impl server::Handler<HttpStream> for EchoHandler {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
self.handler.as_mut()
|
||||
.expect("handler always set in on_request, which is before now; qed")
|
||||
.on_response(res)
|
||||
if let Cors::Allowed(ref domain) = self.cors {
|
||||
let mut headers = res.headers_mut();
|
||||
headers.set(header::Allow(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()),
|
||||
]));
|
||||
headers.set(header::AccessControlAllowOrigin::Value(domain.clone()));
|
||||
}
|
||||
self.handler.as_mut().unwrap().on_response(res)
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
self.handler.as_mut()
|
||||
.expect("handler always set in on_request, which is before now; qed")
|
||||
.on_response_writable(encoder)
|
||||
self.handler.as_mut().unwrap().on_response_writable(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_correct_cors_value() {
|
||||
// given
|
||||
let safe_origins = vec!["chrome-extension://".to_owned(), "http://localhost:8080".to_owned()];
|
||||
let cut = EchoHandler {
|
||||
safe_origins: safe_origins,
|
||||
content: String::new(),
|
||||
cors: Cors::No,
|
||||
handler: None,
|
||||
};
|
||||
|
||||
// when
|
||||
let res1 = cut.cors_header(Some("http://ethcore.io".into()));
|
||||
let res2 = cut.cors_header(Some("http://localhost:8080".into()));
|
||||
let res3 = cut.cors_header(Some("chrome-extension://deadbeefcafe".into()));
|
||||
let res4 = cut.cors_header(None);
|
||||
|
||||
|
||||
// then
|
||||
assert_eq!(res1, Cors::Forbidden);
|
||||
assert_eq!(res2, Cors::Allowed("http://localhost:8080".into()));
|
||||
assert_eq!(res3, Cors::Allowed("chrome-extension://deadbeefcafe".into()));
|
||||
assert_eq!(res4, Cors::No);
|
||||
}
|
||||
|
||||
@@ -1,395 +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/>.
|
||||
|
||||
//! Hyper Server Handler that fetches a file during a request (proxy).
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Instant, Duration};
|
||||
use fetch::{self, Fetch};
|
||||
use futures::Future;
|
||||
use parity_reactor::Remote;
|
||||
use util::Mutex;
|
||||
|
||||
use hyper::{server, Decoder, Encoder, Next, Method, Control};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::uri::RequestUri;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use endpoint::EndpointPath;
|
||||
use handlers::{ContentHandler, StreamingHandler};
|
||||
use page::{LocalPageEndpoint, PageHandlerWaiting};
|
||||
use {Embeddable};
|
||||
|
||||
const FETCH_TIMEOUT: u64 = 300;
|
||||
|
||||
pub enum ValidatorResponse {
|
||||
Local(LocalPageEndpoint),
|
||||
Streaming(StreamingHandler<fetch::Response>),
|
||||
}
|
||||
|
||||
pub trait ContentValidator: Send + 'static {
|
||||
type Error: fmt::Debug + fmt::Display;
|
||||
|
||||
fn validate_and_install(&self, fetch::Response) -> Result<ValidatorResponse, Self::Error>;
|
||||
}
|
||||
|
||||
enum FetchState {
|
||||
Waiting,
|
||||
NotStarted(String),
|
||||
Error(ContentHandler),
|
||||
InProgress(mpsc::Receiver<FetchState>),
|
||||
Streaming(StreamingHandler<fetch::Response>),
|
||||
Done(LocalPageEndpoint, Box<PageHandlerWaiting>),
|
||||
}
|
||||
|
||||
enum WaitResult {
|
||||
Error(ContentHandler),
|
||||
Done(LocalPageEndpoint),
|
||||
NonAwaitable,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FetchControl {
|
||||
abort: Arc<AtomicBool>,
|
||||
listeners: Arc<Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>>,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
impl Default for FetchControl {
|
||||
fn default() -> Self {
|
||||
FetchControl {
|
||||
abort: Arc::new(AtomicBool::new(false)),
|
||||
listeners: Arc::new(Mutex::new(Vec::new())),
|
||||
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchControl {
|
||||
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
|
||||
let mut listeners = self.listeners.lock();
|
||||
for (control, sender) in listeners.drain(..) {
|
||||
trace!(target: "dapps", "Resuming request waiting for content...");
|
||||
if let Err(e) = sender.send(status()) {
|
||||
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
|
||||
} else {
|
||||
let _ = control.ready(Next::read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_status(&self, status: &FetchState) {
|
||||
match *status {
|
||||
FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())),
|
||||
FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())),
|
||||
FetchState::Streaming(_) => self.notify(|| WaitResult::NonAwaitable),
|
||||
FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_deadline_reached(&self) -> bool {
|
||||
self.deadline < Instant::now()
|
||||
}
|
||||
|
||||
pub fn abort(&self) {
|
||||
self.abort.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
self.listeners.lock().push((control, tx));
|
||||
|
||||
Box::new(WaitingHandler {
|
||||
receiver: rx,
|
||||
state: FetchState::Waiting,
|
||||
uri: RequestUri::default(),
|
||||
path: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitingHandler {
|
||||
receiver: mpsc::Receiver<WaitResult>,
|
||||
state: FetchState,
|
||||
uri: RequestUri,
|
||||
path: EndpointPath,
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for WaitingHandler {
|
||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||
self.uri = request.uri().clone();
|
||||
Next::wait()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
let result = self.receiver.try_recv().ok();
|
||||
self.state = match result {
|
||||
Some(WaitResult::Error(handler)) => FetchState::Error(handler),
|
||||
Some(WaitResult::Done(endpoint)) => {
|
||||
let mut page_handler = endpoint.to_page_handler(self.path.clone());
|
||||
page_handler.set_uri(&self.uri);
|
||||
FetchState::Done(endpoint, page_handler)
|
||||
},
|
||||
_ => {
|
||||
warn!("A result for waiting request was not received.");
|
||||
FetchState::Waiting
|
||||
},
|
||||
};
|
||||
|
||||
match self.state {
|
||||
FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder),
|
||||
FetchState::Streaming(ref mut handler) => handler.on_request_readable(decoder),
|
||||
FetchState::Error(ref mut handler) => handler.on_request_readable(decoder),
|
||||
_ => Next::write(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
match self.state {
|
||||
FetchState::Done(_, ref mut handler) => handler.on_response(res),
|
||||
FetchState::Streaming(ref mut handler) => handler.on_response(res),
|
||||
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.state {
|
||||
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
|
||||
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
|
||||
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Errors {
|
||||
embeddable_on: Embeddable,
|
||||
}
|
||||
|
||||
impl Errors {
|
||||
fn download_error<E: fmt::Debug>(&self, e: E) -> ContentHandler {
|
||||
ContentHandler::error(
|
||||
StatusCode::BadGateway,
|
||||
"Download Error",
|
||||
"There was an error when fetching the content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_on.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn invalid_content<E: fmt::Debug>(&self, e: E) -> ContentHandler {
|
||||
ContentHandler::error(
|
||||
StatusCode::BadGateway,
|
||||
"Invalid Dapp",
|
||||
"Downloaded bundle does not contain a valid content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_on.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn timeout_error(&self) -> ContentHandler {
|
||||
ContentHandler::error(
|
||||
StatusCode::GatewayTimeout,
|
||||
"Download Timeout",
|
||||
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn method_not_allowed(&self) -> ContentHandler {
|
||||
ContentHandler::error(
|
||||
StatusCode::MethodNotAllowed,
|
||||
"Method Not Allowed",
|
||||
"Only <code>GET</code> requests are allowed.",
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContentFetcherHandler<H: ContentValidator, F: Fetch> {
|
||||
fetch_control: FetchControl,
|
||||
control: Control,
|
||||
remote: Remote,
|
||||
status: FetchState,
|
||||
fetch: F,
|
||||
installer: Option<H>,
|
||||
path: EndpointPath,
|
||||
errors: Errors,
|
||||
}
|
||||
|
||||
impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
|
||||
pub fn new(
|
||||
url: String,
|
||||
path: EndpointPath,
|
||||
control: Control,
|
||||
installer: H,
|
||||
embeddable_on: Embeddable,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
ContentFetcherHandler {
|
||||
fetch_control: FetchControl::default(),
|
||||
control,
|
||||
remote,
|
||||
fetch,
|
||||
status: FetchState::NotStarted(url),
|
||||
installer: Some(installer),
|
||||
path,
|
||||
errors: Errors {
|
||||
embeddable_on,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_control(&self) -> FetchControl {
|
||||
self.fetch_control.clone()
|
||||
}
|
||||
|
||||
fn fetch_content(&self, uri: RequestUri, url: &str, installer: H) -> mpsc::Receiver<FetchState> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let abort = self.fetch_control.abort.clone();
|
||||
|
||||
let path = self.path.clone();
|
||||
let tx2 = tx.clone();
|
||||
let control = self.control.clone();
|
||||
let errors = self.errors.clone();
|
||||
|
||||
let future = self.fetch.fetch_with_abort(url, abort.into()).then(move |result| {
|
||||
trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result);
|
||||
let new_state = match result {
|
||||
Ok(response) => match installer.validate_and_install(response) {
|
||||
Ok(ValidatorResponse::Local(endpoint)) => {
|
||||
trace!(target: "dapps", "Validation OK. Returning response.");
|
||||
let mut handler = endpoint.to_page_handler(path);
|
||||
handler.set_uri(&uri);
|
||||
FetchState::Done(endpoint, handler)
|
||||
},
|
||||
Ok(ValidatorResponse::Streaming(handler)) => {
|
||||
trace!(target: "dapps", "Validation OK. Streaming response.");
|
||||
FetchState::Streaming(handler)
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(target: "dapps", "Error while validating content: {:?}", e);
|
||||
FetchState::Error(errors.invalid_content(e))
|
||||
},
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
|
||||
FetchState::Error(errors.download_error(e))
|
||||
},
|
||||
};
|
||||
// Content may be resolved when the connection is already dropped.
|
||||
let _ = tx2.send(new_state);
|
||||
// Ignoring control errors
|
||||
let _ = control.ready(Next::read());
|
||||
Ok(()) as Result<(), ()>
|
||||
});
|
||||
|
||||
// make sure to run within fetch thread pool.
|
||||
let future = self.fetch.process(future);
|
||||
// spawn to event loop
|
||||
let control = self.control.clone();
|
||||
let errors = self.errors.clone();
|
||||
self.remote.spawn_with_timeout(|| future, Duration::from_secs(FETCH_TIMEOUT), move || {
|
||||
// Notify about the timeout
|
||||
let _ = tx.send(FetchState::Error(errors.timeout_error()));
|
||||
// Ignoring control errors
|
||||
let _ = control.ready(Next::read());
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetcherHandler<H, F> {
|
||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||
let status = if let FetchState::NotStarted(ref url) = self.status {
|
||||
let uri = request.uri().clone();
|
||||
let installer = self.installer.take().expect("Installer always set initialy; installer used only in on_request; on_request invoked only once; qed");
|
||||
|
||||
Some(match *request.method() {
|
||||
// Start fetching content
|
||||
Method::Get => {
|
||||
trace!(target: "dapps", "Fetching content from: {:?}", url);
|
||||
let receiver = self.fetch_content(uri, url, installer);
|
||||
FetchState::InProgress(receiver)
|
||||
},
|
||||
// or return error
|
||||
_ => FetchState::Error(self.errors.method_not_allowed()),
|
||||
})
|
||||
} else { None };
|
||||
|
||||
if let Some(status) = status {
|
||||
self.fetch_control.set_status(&status);
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
Next::read()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
let (status, next) = match self.status {
|
||||
// Request may time out
|
||||
FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => {
|
||||
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
|
||||
(Some(FetchState::Error(self.errors.timeout_error())), Next::write())
|
||||
},
|
||||
FetchState::InProgress(ref receiver) => {
|
||||
// Check if there is an answer
|
||||
let rec = receiver.try_recv();
|
||||
match rec {
|
||||
// just return the new state
|
||||
Ok(state) => (Some(state), Next::write()),
|
||||
// wait some more
|
||||
_ => (None, Next::wait())
|
||||
}
|
||||
},
|
||||
FetchState::Error(ref mut handler) => (None, handler.on_request_readable(decoder)),
|
||||
_ => (None, Next::write()),
|
||||
};
|
||||
|
||||
if let Some(status) = status {
|
||||
self.fetch_control.set_status(&status);
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
match self.status {
|
||||
FetchState::Done(_, ref mut handler) => handler.on_response(res),
|
||||
FetchState::Streaming(ref mut handler) => handler.on_response(res),
|
||||
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.status {
|
||||
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
|
||||
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
|
||||
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -16,131 +16,32 @@
|
||||
|
||||
//! Hyper handlers implementations.
|
||||
|
||||
mod async;
|
||||
mod content;
|
||||
mod auth;
|
||||
mod echo;
|
||||
mod fetch;
|
||||
mod content;
|
||||
mod redirect;
|
||||
mod streaming;
|
||||
|
||||
pub use self::async::AsyncHandler;
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::auth::AuthRequiredHandler;
|
||||
pub use self::echo::EchoHandler;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
|
||||
pub use self::content::ContentHandler;
|
||||
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};
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
|
||||
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 {
|
||||
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(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/// Extracts URL part from the Request.
|
||||
pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> {
|
||||
convert_uri_to_url(req.uri(), req.headers().get::<header::Host>())
|
||||
}
|
||||
|
||||
/// Extracts URL given URI and Host header.
|
||||
pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) -> Option<Url> {
|
||||
match *uri {
|
||||
match *req.uri() {
|
||||
uri::RequestUri::AbsoluteUri(ref url) => {
|
||||
match Url::from_generic_url(url.clone()) {
|
||||
Ok(url) => Some(url),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
uri::RequestUri::AbsolutePath { ref path, ref query } => {
|
||||
let query = match *query {
|
||||
Some(ref query) => format!("?{}", query),
|
||||
None => "".into(),
|
||||
};
|
||||
uri::RequestUri::AbsolutePath(ref path) => {
|
||||
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
|
||||
let url_string = match host {
|
||||
let url_string = match req.headers().get::<header::Host>() {
|
||||
Some(ref host) => {
|
||||
format!("http://{}:{}{}{}", host.hostname, host.port.unwrap_or(80), path, query)
|
||||
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
@@ -153,3 +54,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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -20,20 +20,15 @@ use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Redirection {
|
||||
to_url: String
|
||||
to_url: &'static str
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new(url: &str) -> Self {
|
||||
Redirection {
|
||||
to_url: url.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boxed(url: &str) -> Box<Self> {
|
||||
Box::new(Self::new(url))
|
||||
pub fn new(url: &'static str) -> Box<Self> {
|
||||
Box::new(Redirection {
|
||||
to_url: url
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +42,7 @@ impl server::Handler<HttpStream> for Redirection {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
// Don't use `MovedPermanently` here to prevent browser from caching the redirections.
|
||||
res.set_status(StatusCode::Found);
|
||||
res.set_status(StatusCode::MovedPermanently);
|
||||
res.headers_mut().set(header::Location(self.to_url.to_owned()));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
@@ -1,102 +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/>.
|
||||
|
||||
//! Content Stream Response
|
||||
|
||||
use std::io::{self, Read};
|
||||
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::mime::Mime;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
use Embeddable;
|
||||
|
||||
const BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub struct StreamingHandler<R: io::Read> {
|
||||
buffer: [u8; BUFFER_SIZE],
|
||||
buffer_leftover: usize,
|
||||
status: StatusCode,
|
||||
content: io::BufReader<R>,
|
||||
mimetype: Mime,
|
||||
safe_to_embed_on: Embeddable,
|
||||
}
|
||||
|
||||
impl<R: io::Read> StreamingHandler<R> {
|
||||
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
|
||||
StreamingHandler {
|
||||
buffer: [0; BUFFER_SIZE],
|
||||
buffer_leftover: 0,
|
||||
status: status,
|
||||
content: io::BufReader::new(content),
|
||||
mimetype: mimetype,
|
||||
safe_to_embed_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_initial_content(&mut self, content: &str) {
|
||||
assert_eq!(self.buffer_leftover, 0);
|
||||
let bytes = content.as_bytes();
|
||||
self.buffer_leftover = bytes.len();
|
||||
self.buffer[0..self.buffer_leftover].copy_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
|
||||
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(self.status);
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
fn handle_error(e: io::Error) -> Next {
|
||||
match e.kind() {
|
||||
::std::io::ErrorKind::WouldBlock => Next::write(),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
|
||||
let write_pos = self.buffer_leftover;
|
||||
match self.content.read(&mut self.buffer[write_pos..]) {
|
||||
Err(e) => handle_error(e),
|
||||
Ok(read) => match encoder.write(&self.buffer[..write_pos + read]) {
|
||||
Err(e) => handle_error(e),
|
||||
Ok(0) => Next::end(),
|
||||
Ok(wrote) => {
|
||||
self.buffer_leftover = write_pos + read - wrote;
|
||||
if self.buffer_leftover > 0 {
|
||||
for i in self.buffer_leftover..write_pos + read {
|
||||
self.buffer.swap(i, i - self.buffer_leftover);
|
||||
}
|
||||
}
|
||||
Next::write()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
409
dapps/src/lib.rs
409
dapps/src/lib.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,269 +15,224 @@
|
||||
// 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 serde;
|
||||
extern crate serde_json;
|
||||
extern crate time;
|
||||
extern crate unicase;
|
||||
extern crate url as url_lib;
|
||||
extern crate zip;
|
||||
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
|
||||
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 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;
|
||||
extern crate url as url_lib;
|
||||
extern crate hyper;
|
||||
extern crate unicase;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
extern crate parity_dapps;
|
||||
extern crate ethcore_rpc;
|
||||
extern crate ethcore_util;
|
||||
extern crate mime_guess;
|
||||
|
||||
mod endpoint;
|
||||
mod apps;
|
||||
mod page;
|
||||
mod router;
|
||||
mod handlers;
|
||||
mod rpc;
|
||||
mod api;
|
||||
mod proxypac;
|
||||
mod url;
|
||||
mod web;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use jsonrpc_http_server::{self as http, hyper, Origin};
|
||||
use jsonrpc_core::{IoHandler, IoDelegate};
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use ethcore_rpc::Extendable;
|
||||
|
||||
use fetch::Fetch;
|
||||
use node_health::NodeHealth;
|
||||
use parity_reactor::Remote;
|
||||
static DAPPS_DOMAIN : &'static str = ".parity";
|
||||
|
||||
pub use hash_fetch::urlhint::ContractClient;
|
||||
pub use node_health::SyncStatus;
|
||||
|
||||
|
||||
/// 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>;
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder {
|
||||
dapps_path: String,
|
||||
handler: Arc<IoHandler>,
|
||||
}
|
||||
|
||||
impl<F> WebProxyTokens for F where F: Fn(String) -> Option<Origin> + Send + Sync {
|
||||
fn domain(&self, token: &str) -> Option<Origin> { self(token.to_owned()) }
|
||||
}
|
||||
|
||||
/// Current supported endpoints.
|
||||
pub struct Endpoints {
|
||||
endpoints: endpoint::Endpoints,
|
||||
}
|
||||
|
||||
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 Extendable for ServerBuilder {
|
||||
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 Middleware {
|
||||
/// Get local endpoints handle.
|
||||
pub fn endpoints(&self) -> Endpoints {
|
||||
Endpoints {
|
||||
endpoints: self.endpoints.clone(),
|
||||
impl ServerBuilder {
|
||||
/// Construct new dapps server
|
||||
pub fn new(dapps_path: String) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path,
|
||||
handler: Arc::new(IoHandler::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()));
|
||||
/// 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(),
|
||||
)
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
fn start_http<A: Authorization + 'static>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: String,
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path));
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Api, api::RestApi::new(format!("{}", addr), endpoints.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special
|
||||
};
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
None,
|
||||
special,
|
||||
None,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
});
|
||||
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: Default::default(),
|
||||
}
|
||||
try!(hyper::Server::http(addr))
|
||||
.handle(move |_| router::Router::new(
|
||||
apps::main_page(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
authorization.clone(),
|
||||
hosts.clone(),
|
||||
))
|
||||
.map(|(l, srv)| {
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
srv.run();
|
||||
});
|
||||
|
||||
Server {
|
||||
server: Some(l),
|
||||
panic_handler: panic_handler,
|
||||
}
|
||||
})
|
||||
.map_err(ServerError::from)
|
||||
}
|
||||
|
||||
/// 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)>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
dapps_domain: &str,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let embeddable = as_embeddable(ui_address, extra_embed_on, extra_script_src, dapps_domain);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status.clone(),
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
).embeddable_on(embeddable.clone()).allow_dapps(true));
|
||||
let endpoints = apps::all_endpoints(
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
dapps_domain,
|
||||
embeddable.clone(),
|
||||
web_proxy_tokens,
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
);
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
let special = {
|
||||
let mut special = special_endpoints(
|
||||
health,
|
||||
content_fetcher.clone(),
|
||||
remote.clone(),
|
||||
);
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Home,
|
||||
Some(apps::ui_redirection(embeddable.clone())),
|
||||
);
|
||||
special
|
||||
};
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.server.take().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
Some(endpoints.clone()),
|
||||
special,
|
||||
embeddable,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
/// Webapp Server startup error
|
||||
#[derive(Debug)]
|
||||
pub enum ServerError {
|
||||
/// Wrapped `std::io::Error`
|
||||
IoError(std::io::Error),
|
||||
/// Other `hyper` error
|
||||
Other(hyper::error::Error),
|
||||
}
|
||||
|
||||
Middleware {
|
||||
router: router,
|
||||
endpoints: endpoints,
|
||||
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)
|
||||
#[cfg(test)]
|
||||
mod 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()]));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
let mut rng = ::rand::OsRng::new().unwrap();
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,11 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use page::{handler, PageCache};
|
||||
use page::handler;
|
||||
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: bool,
|
||||
info: EndpointInfo,
|
||||
fallback_to_index_html: bool,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
@@ -38,22 +36,8 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
safe_to_embed: false,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,23 +49,21 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
safe_to_embed: false,
|
||||
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) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
safe_to_embed: true,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,12 +76,11 @@ 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()),
|
||||
cache: PageCache::Disabled,
|
||||
safe_to_embed_on: self.safe_to_embed_on.clone(),
|
||||
file: None,
|
||||
safe_to_embed: self.safe_to_embed,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,14 +99,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 +113,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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,8 +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/>.
|
||||
|
||||
use time::{self, Duration};
|
||||
|
||||
use std::io::Write;
|
||||
use hyper::header;
|
||||
use hyper::server;
|
||||
use hyper::uri::RequestUri;
|
||||
@@ -23,8 +22,6 @@ use hyper::net::HttpStream;
|
||||
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.
|
||||
@@ -51,76 +48,20 @@ pub trait Dapp: Send + 'static {
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile>;
|
||||
}
|
||||
|
||||
/// Currently served by `PageHandler` file
|
||||
pub enum ServedFile<T: Dapp> {
|
||||
/// File from dapp
|
||||
File(T::DappFile),
|
||||
/// Error (404)
|
||||
Error(ContentHandler),
|
||||
}
|
||||
|
||||
impl<T: Dapp> ServedFile<T> {
|
||||
pub fn new(embeddable_on: Embeddable) -> Self {
|
||||
ServedFile::Error(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested dapp resource was not found.",
|
||||
None,
|
||||
embeddable_on,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines what cache headers should be appended to returned resources.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PageCache {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl Default for PageCache {
|
||||
fn default() -> Self {
|
||||
PageCache::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic type for `PageHandler` allowing to set the URL.
|
||||
/// Used by dapps fetching to set the URL after the content was downloaded.
|
||||
pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send {
|
||||
fn set_uri(&mut self, uri: &RequestUri);
|
||||
}
|
||||
|
||||
/// A handler for a single webapp.
|
||||
/// Resolves correct paths and serves as a plumbing code between
|
||||
/// hyper server and dapp.
|
||||
pub struct PageHandler<T: Dapp> {
|
||||
/// A Dapp.
|
||||
pub app: T,
|
||||
/// File currently being served
|
||||
pub file: ServedFile<T>,
|
||||
/// File currently being served (or `None` if file does not exist).
|
||||
pub file: Option<T::DappFile>,
|
||||
/// Optional prefix to strip from path.
|
||||
pub prefix: Option<String>,
|
||||
/// Requested path.
|
||||
pub path: EndpointPath,
|
||||
/// Flag indicating if the file can be safely embeded (put in iframe).
|
||||
pub safe_to_embed_on: Embeddable,
|
||||
/// Cache settings for this page.
|
||||
pub cache: PageCache,
|
||||
}
|
||||
|
||||
impl<T: Dapp> PageHandlerWaiting for PageHandler<T> {
|
||||
fn set_uri(&mut self, uri: &RequestUri) {
|
||||
trace!(target: "dapps", "Setting URI: {:?}", uri);
|
||||
self.file = match *uri {
|
||||
RequestUri::AbsolutePath { ref path, .. } => {
|
||||
self.app.file(&self.extract_path(path))
|
||||
},
|
||||
RequestUri::AbsoluteUri(ref url) => {
|
||||
self.app.file(&self.extract_path(url.path()))
|
||||
},
|
||||
_ => None,
|
||||
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
|
||||
}
|
||||
pub safe_to_embed: bool,
|
||||
}
|
||||
|
||||
impl<T: Dapp> PageHandler<T> {
|
||||
@@ -146,7 +87,15 @@ impl<T: Dapp> PageHandler<T> {
|
||||
|
||||
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
self.set_uri(req.uri());
|
||||
self.file = match *req.uri() {
|
||||
RequestUri::AbsolutePath(ref path) => {
|
||||
self.app.file(&self.extract_path(path))
|
||||
},
|
||||
RequestUri::AbsoluteUri(ref url) => {
|
||||
self.app.file(&self.extract_path(url.path()))
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
Next::write()
|
||||
}
|
||||
|
||||
@@ -155,40 +104,24 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
match self.file {
|
||||
ServedFile::File(ref f) => {
|
||||
res.set_status(StatusCode::Ok);
|
||||
|
||||
if let PageCache::Enabled = self.cache {
|
||||
let mut headers = res.headers_mut();
|
||||
let validity = Duration::days(365);
|
||||
headers.set(header::CacheControl(vec![
|
||||
header::CacheDirective::Public,
|
||||
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
|
||||
]));
|
||||
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
|
||||
}
|
||||
|
||||
match f.content_type().parse() {
|
||||
Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
|
||||
Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
|
||||
}
|
||||
|
||||
// Security headers:
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
|
||||
Next::write()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
handler.on_response(res)
|
||||
if let Some(ref f) = self.file {
|
||||
res.set_status(StatusCode::Ok);
|
||||
res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
|
||||
if !self.safe_to_embed {
|
||||
res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
|
||||
}
|
||||
Next::write()
|
||||
} else {
|
||||
res.set_status(StatusCode::NotFound);
|
||||
Next::write()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.file {
|
||||
ServedFile::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
ServedFile::File(ref f) if f.is_drained() => Next::end(),
|
||||
ServedFile::File(ref mut f) => match encoder.write(f.next_chunk()) {
|
||||
None => Next::end(),
|
||||
Some(ref f) if f.is_drained() => Next::end(),
|
||||
Some(ref mut f) => match encoder.write(f.next_chunk()) {
|
||||
Ok(bytes) => {
|
||||
f.bytes_written(bytes);
|
||||
Next::write()
|
||||
@@ -253,14 +186,11 @@ 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,
|
||||
port: 8080
|
||||
},
|
||||
file: ServedFile::new(None),
|
||||
cache: Default::default(),
|
||||
safe_to_embed_on: None,
|
||||
file: None,
|
||||
safe_to_embed: true,
|
||||
};
|
||||
|
||||
// when
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -17,101 +17,37 @@
|
||||
use mime_guess;
|
||||
use std::io::{Seek, Read, SeekFrom};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::handler::{self, PageCache, PageHandlerWaiting};
|
||||
use std::path::PathBuf;
|
||||
use page::handler;
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use mime::Mime;
|
||||
use Embeddable;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalPageEndpoint {
|
||||
path: PathBuf,
|
||||
mime: Option<Mime>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_on: Embeddable,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
impl LocalPageEndpoint {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: None,
|
||||
info: Some(info),
|
||||
cache: cache,
|
||||
embeddable_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: Some(mime),
|
||||
info: None,
|
||||
cache: cache,
|
||||
embeddable_on: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn page_handler_with_mime(&self, path: EndpointPath, mime: &Mime) -> handler::PageHandler<LocalSingleFile> {
|
||||
handler::PageHandler {
|
||||
app: LocalSingleFile { path: self.path.clone(), mime: format!("{}", mime) },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_on: self.embeddable_on.clone(),
|
||||
cache: self.cache,
|
||||
}
|
||||
}
|
||||
|
||||
fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> {
|
||||
handler::PageHandler {
|
||||
app: LocalDapp { path: self.path.clone() },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_on: self.embeddable_on.clone(),
|
||||
cache: self.cache,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> {
|
||||
if let Some(ref mime) = self.mime {
|
||||
Box::new(self.page_handler_with_mime(path, mime))
|
||||
} else {
|
||||
Box::new(self.page_handler(path))
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for LocalPageEndpoint {
|
||||
fn info(&self) -> Option<&EndpointInfo> {
|
||||
self.info.as_ref()
|
||||
Some(&self.info)
|
||||
}
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
if let Some(ref mime) = self.mime {
|
||||
Box::new(self.page_handler_with_mime(path, mime))
|
||||
} else {
|
||||
Box::new(self.page_handler(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalSingleFile {
|
||||
path: PathBuf,
|
||||
mime: String,
|
||||
}
|
||||
|
||||
impl handler::Dapp for LocalSingleFile {
|
||||
type DappFile = LocalFile;
|
||||
|
||||
fn file(&self, _path: &str) -> Option<Self::DappFile> {
|
||||
LocalFile::from_path(&self.path, Some(&self.mime))
|
||||
Box::new(handler::PageHandler {
|
||||
app: LocalDapp::new(self.path.clone()),
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: None,
|
||||
safe_to_embed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +55,14 @@ struct LocalDapp {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl LocalDapp {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
LocalDapp {
|
||||
path: path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl handler::Dapp for LocalDapp {
|
||||
type DappFile = LocalFile;
|
||||
|
||||
@@ -127,7 +71,18 @@ impl handler::Dapp for LocalDapp {
|
||||
for part in file_path.split('/') {
|
||||
path.push(part);
|
||||
}
|
||||
LocalFile::from_path(&path, None)
|
||||
// Check if file exists
|
||||
fs::File::open(path.clone()).ok().map(|file| {
|
||||
let content_type = mime_guess::guess_mime_type(path);
|
||||
let len = file.metadata().ok().map_or(0, |meta| meta.len());
|
||||
LocalFile {
|
||||
content_type: content_type.to_string(),
|
||||
buffer: [0; 4096],
|
||||
file: file,
|
||||
pos: 0,
|
||||
len: len,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,24 +94,6 @@ struct LocalFile {
|
||||
pos: u64,
|
||||
}
|
||||
|
||||
impl LocalFile {
|
||||
fn from_path<P: AsRef<Path>>(path: P, mime: Option<&str>) -> Option<Self> {
|
||||
// Check if file exists
|
||||
fs::File::open(&path).ok().map(|file| {
|
||||
let content_type = mime.map(|mime| mime.to_owned())
|
||||
.unwrap_or_else(|| mime_guess::guess_mime_type(path).to_string());
|
||||
let len = file.metadata().ok().map_or(0, |meta| meta.len());
|
||||
LocalFile {
|
||||
content_type: content_type,
|
||||
buffer: [0; 4096],
|
||||
file: file,
|
||||
pos: 0,
|
||||
len: len,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl handler::DappFile for LocalFile {
|
||||
fn content_type(&self) -> &str {
|
||||
&self.content_type
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -21,5 +21,4 @@ mod handler;
|
||||
|
||||
pub use self::local::LocalPageEndpoint;
|
||||
pub use self::builtin::PageEndpoint;
|
||||
pub use self::handler::{PageCache, PageHandlerWaiting};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -18,46 +18,31 @@
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::HOME_PAGE;
|
||||
use {address, Embeddable};
|
||||
use apps::DAPPS_DOMAIN;
|
||||
|
||||
pub struct ProxyPac {
|
||||
embeddable: Embeddable,
|
||||
dapps_domain: String,
|
||||
}
|
||||
pub struct ProxyPac;
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac { embeddable, dapps_domain })
|
||||
pub fn boxed() -> Box<Endpoint> {
|
||||
Box::new(ProxyPac)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
.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}"))
|
||||
{{
|
||||
return "PROXY {4}";
|
||||
}}
|
||||
|
||||
if (shExpMatch(host, "*.{1}"))
|
||||
{{
|
||||
return "PROXY {2}:{3}";
|
||||
return "PROXY {1}:{2}";
|
||||
}}
|
||||
|
||||
return "DIRECT";
|
||||
}}
|
||||
"#,
|
||||
HOME_PAGE, self.dapps_domain, path.host, path.port, ui);
|
||||
|
||||
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||
DAPPS_DOMAIN, path.host, path.port);
|
||||
Box::new(ContentHandler::ok(content, "application/javascript".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
104
dapps/src/router/auth.rs
Normal file
104
dapps/src/router/auth.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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::new(
|
||||
status::StatusCode::Unauthorized,
|
||||
"<h1>Unauthorized</h1>".into(),
|
||||
"text/html".into(),
|
||||
)))
|
||||
},
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dapps/src/router/host_validation.rs
Normal file
42
dapps/src/router/host_validation.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use DAPPS_DOMAIN;
|
||||
use hyper::server;
|
||||
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);
|
||||
|
||||
is_host_header_valid(request, &endpoints)
|
||||
}
|
||||
|
||||
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
|
||||
Box::new(ContentHandler::forbidden(
|
||||
r#"
|
||||
<h1>Request with disallowed <code>Host</code> header has been blocked.</h1>
|
||||
<p>Check the URL in your browser address bar.</p>
|
||||
"#.into(),
|
||||
"text/html".into()
|
||||
))
|
||||
}
|
||||
236
dapps/src/router/mod.rs
Normal file
236
dapps/src/router/mod.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Router implementation
|
||||
//! Processes request handling authorization and dispatching it to proper application.
|
||||
|
||||
pub mod auth;
|
||||
mod host_validation;
|
||||
|
||||
use DAPPS_DOMAIN;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, Next, Encoder, Decoder};
|
||||
use hyper::net::HttpStream;
|
||||
use apps;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use handlers::{Redirection, extract_url};
|
||||
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> {
|
||||
main_page: &'static str,
|
||||
endpoints: Arc<Endpoints>,
|
||||
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 {
|
||||
// Validate Host header
|
||||
if let Some(ref hosts) = self.allowed_hosts {
|
||||
if !host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect()) {
|
||||
self.handler = host_validation::host_invalid_response();
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
}
|
||||
|
||||
// Check authorization
|
||||
let auth = self.authorization.is_authorized(&req);
|
||||
if let Authorized::No(handler) = auth {
|
||||
self.handler = handler;
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = extract_url(&req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
|
||||
self.handler = match endpoint {
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
|
||||
self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
|
||||
},
|
||||
// Redirection to main page
|
||||
_ if *req.method() == hyper::method::Method::Get => {
|
||||
Redirection::new(self.main_page)
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
|
||||
}
|
||||
};
|
||||
|
||||
// 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(
|
||||
main_page: &'static str,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
) -> Self {
|
||||
|
||||
let handler = special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default());
|
||||
Router {
|
||||
main_page: main_page,
|
||||
endpoints: endpoints,
|
||||
special: special,
|
||||
authorization: authorization,
|
||||
allowed_hosts: allowed_hosts,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), SpecialEndpoint::Api)
|
||||
);
|
||||
}
|
||||
44
dapps/src/rpc.rs
Normal file
44
dapps/src/rpc.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use jsonrpc_core::IoHandler;
|
||||
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
|
||||
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: handler,
|
||||
panic_handler: panic_handler,
|
||||
cors_domain: Some(vec![AccessControlAllowOrigin::Null]),
|
||||
// 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<IoHandler>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Endpoint for RpcEndpoint {
|
||||
fn to_handler(&self, _path: EndpointPath) -> 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))
|
||||
}
|
||||
}
|
||||
@@ -1,85 +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 tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_return_error() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/empty HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
response.assert_header("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_handle_ping() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_try_to_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/content/1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d HTTP/1.1\r\n\
|
||||
Host: home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -1,540 +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 devtools::http_client;
|
||||
use rustc_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,
|
||||
request, assert_security_headers_for_embed,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn should_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar_and_sync();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 503 Service Unavailable");
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000";
|
||||
const GAVCOIN_ICON: &'static str = "00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e000000000000000000000000000000000000000000000000000000000000007768747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f657468636f72652f646170702d6173736574732f623838653938336162616131613661363334356238643934343863313562313137646462353430652f746f6b656e732f676176636f696e2d36347836342e706e67000000000000000000";
|
||||
|
||||
#[test]
|
||||
fn should_return_502_on_hash_mismatch() {
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
|
||||
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
|
||||
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
|
||||
fetch.assert_no_more_requests();
|
||||
|
||||
response.assert_status("HTTP/1.1 502 Bad Gateway");
|
||||
assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_for_invalid_dapp_zip() {
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
|
||||
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
|
||||
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
|
||||
fetch.assert_no_more_requests();
|
||||
|
||||
response.assert_status("HTTP/1.1 502 Bad Gateway");
|
||||
assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_fetched_dapp_content() {
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
|
||||
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
fetch.set_response(include_bytes!("../../res/gavcoin.zip"));
|
||||
|
||||
// when
|
||||
let response1 = http_client::request(server.addr(),
|
||||
"\
|
||||
GET /index.html HTTP/1.1\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
let response2 = http_client::request(server.addr(),
|
||||
"\
|
||||
GET /manifest.json HTTP/1.1\r\n\
|
||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
|
||||
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
|
||||
fetch.assert_no_more_requests();
|
||||
|
||||
response1.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response1.headers);
|
||||
assert_eq!(
|
||||
response1.body,
|
||||
r#"18
|
||||
<h1>Hello Gavcoin!</h1>
|
||||
|
||||
"#
|
||||
);
|
||||
|
||||
response2.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response2.headers);
|
||||
assert_eq!(
|
||||
response2.body,
|
||||
r#"BE
|
||||
{
|
||||
"id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a",
|
||||
"name": "Gavcoin",
|
||||
"description": "Gavcoin",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"iconUrl": "icon.png"
|
||||
}
|
||||
0
|
||||
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_fetched_content() {
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
|
||||
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
|
||||
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
|
||||
fetch.assert_no_more_requests();
|
||||
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_security_headers_present(None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_cache_content() {
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
|
||||
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
let request_str = "\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
";
|
||||
|
||||
let response = http_client::request(server.addr(), request_str);
|
||||
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
|
||||
fetch.assert_no_more_requests();
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
|
||||
// when
|
||||
let response = http_client::request(server.addr(), request_str);
|
||||
|
||||
// then
|
||||
fetch.assert_no_more_requests();
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_request_content_twice() {
|
||||
use std::thread;
|
||||
|
||||
// given
|
||||
let (server, fetch, registrar) = serve_with_registrar_and_fetch_and_threads(true);
|
||||
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
|
||||
registrar.set_result(
|
||||
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
|
||||
Ok(gavcoin.clone())
|
||||
);
|
||||
let request_str = "\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
";
|
||||
let fire_request = || {
|
||||
let addr = server.addr().to_owned();
|
||||
let req = request_str.to_owned();
|
||||
thread::spawn(move || {
|
||||
http_client::request(&addr, &req)
|
||||
})
|
||||
};
|
||||
let control = fetch.manual();
|
||||
|
||||
// when
|
||||
|
||||
// Fire two requests at the same time
|
||||
let r1 = fire_request();
|
||||
let r2 = fire_request();
|
||||
|
||||
// wait for single request in fetch, the second one should go into waiting state.
|
||||
control.wait_for_requests(1);
|
||||
control.respond();
|
||||
|
||||
let response1 = r1.join().unwrap();
|
||||
let response2 = r2.join().unwrap();
|
||||
|
||||
// then
|
||||
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
|
||||
fetch.assert_no_more_requests();
|
||||
response1.assert_status("HTTP/1.1 200 OK");
|
||||
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");
|
||||
|
||||
// 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 200 OK");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_requested("https://parity.io/");
|
||||
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");
|
||||
|
||||
// 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_protocol() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "ftp://parity.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /web/token/ftp/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 400 Bad Request");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_non_get_requests() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
123\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 405 Method Not Allowed");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fix_absolute_requests_based_on_referer() {
|
||||
// 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/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\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");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
use std::{io, thread, time};
|
||||
use std::sync::{atomic, mpsc, Arc};
|
||||
use util::Mutex;
|
||||
|
||||
use futures::{self, Future};
|
||||
use fetch::{self, Fetch};
|
||||
|
||||
pub struct FetchControl {
|
||||
sender: mpsc::Sender<()>,
|
||||
fetch: FakeFetch,
|
||||
}
|
||||
|
||||
impl FetchControl {
|
||||
pub fn respond(self) {
|
||||
self.sender.send(())
|
||||
.expect("Fetch cannot be finished without sending a response at least once.");
|
||||
}
|
||||
|
||||
pub fn wait_for_requests(&self, len: usize) {
|
||||
const MAX_TIMEOUT_MS: u64 = 5000;
|
||||
const ATTEMPTS: u64 = 10;
|
||||
let mut attempts_left = ATTEMPTS;
|
||||
loop {
|
||||
let current = self.fetch.requested.lock().len();
|
||||
|
||||
if current == len {
|
||||
break;
|
||||
} else if attempts_left == 0 {
|
||||
panic!(
|
||||
"Timeout reached when waiting for pending requests. Expected: {}, current: {}",
|
||||
len, current
|
||||
);
|
||||
} else {
|
||||
attempts_left -= 1;
|
||||
// Should we handle spurious timeouts better?
|
||||
thread::park_timeout(time::Duration::from_millis(MAX_TIMEOUT_MS / ATTEMPTS));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FakeFetch {
|
||||
manual: Arc<Mutex<Option<mpsc::Receiver<()>>>>,
|
||||
response: Arc<Mutex<Option<&'static [u8]>>>,
|
||||
asserted: Arc<atomic::AtomicUsize>,
|
||||
requested: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl FakeFetch {
|
||||
pub fn set_response(&self, data: &'static [u8]) {
|
||||
*self.response.lock() = Some(data);
|
||||
}
|
||||
|
||||
pub fn manual(&self) -> FetchControl {
|
||||
assert!(self.manual.lock().is_none(), "Only one manual control may be active.");
|
||||
let (tx, rx) = mpsc::channel();
|
||||
*self.manual.lock() = Some(rx);
|
||||
|
||||
FetchControl {
|
||||
sender: tx,
|
||||
fetch: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_requested(&self, url: &str) {
|
||||
let requests = self.requested.lock();
|
||||
let idx = self.asserted.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
|
||||
assert_eq!(requests.get(idx), Some(&url.to_owned()), "Expected fetch from specific URL.");
|
||||
}
|
||||
|
||||
pub fn assert_no_more_requests(&self) {
|
||||
let requests = self.requested.lock();
|
||||
let len = self.asserted.load(atomic::Ordering::SeqCst);
|
||||
assert_eq!(requests.len(), len, "Didn't expect any more requests, got: {:?}", &requests[len..]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Fetch for FakeFetch {
|
||||
type Result = futures::BoxFuture<fetch::Response, fetch::Error>;
|
||||
|
||||
fn new() -> Result<Self, fetch::Error> where Self: Sized {
|
||||
Ok(FakeFetch::default())
|
||||
}
|
||||
|
||||
fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result {
|
||||
self.requested.lock().push(url.into());
|
||||
let manual = self.manual.clone();
|
||||
let response = self.response.clone();
|
||||
|
||||
let (tx, rx) = futures::oneshot();
|
||||
thread::spawn(move || {
|
||||
if let Some(rx) = manual.lock().take() {
|
||||
// wait for manual resume
|
||||
let _ = rx.recv();
|
||||
}
|
||||
|
||||
let data = response.lock().take().unwrap_or(b"Some content");
|
||||
let cursor = io::Cursor::new(data);
|
||||
tx.send(fetch::Response::from_reader(cursor)).unwrap();
|
||||
});
|
||||
|
||||
rx.map_err(|_| fetch::Error::Aborted).boxed()
|
||||
}
|
||||
}
|
||||
@@ -1,308 +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 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 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;
|
||||
|
||||
use self::registrar::FakeRegistrar;
|
||||
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") {
|
||||
let mut builder = LogBuilder::new();
|
||||
builder.parse(&log);
|
||||
let _ = builder.init(); // ignore errors since ./test.sh will call this multiple times.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||
B: Fetch,
|
||||
{
|
||||
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");
|
||||
|
||||
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();
|
||||
(
|
||||
server,
|
||||
registrar,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serve_with_rpc(io: IoHandler) -> Server {
|
||||
init_server(|builder| builder, io, Remote::new_sync()).0
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder, Default::default(), 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())
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||
serve_with_registrar_and_fetch_and_threads(false)
|
||||
}
|
||||
|
||||
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| {
|
||||
builder.fetch(f.clone())
|
||||
}, Default::default(), 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) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, _) = init_server(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());
|
||||
|
||||
(server, fetch)
|
||||
}
|
||||
|
||||
pub fn serve() -> Server {
|
||||
init_server(|builder| builder, Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
http_client::request(server.addr(), request)
|
||||
}
|
||||
|
||||
pub fn assert_security_headers(headers: &[String]) {
|
||||
http_client::assert_security_headers_present(headers, None)
|
||||
}
|
||||
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,73 +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 std::str;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use rustc_hex::FromHex;
|
||||
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use util::{Bytes, Address, Mutex, H256, ToPretty};
|
||||
|
||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||
const URLHINT_RESOLVE: &'static str = "267b6922";
|
||||
const DEFAULT_HASH: &'static str = "1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d";
|
||||
|
||||
pub struct FakeRegistrar {
|
||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
||||
pub responses: Mutex<HashMap<(String, String), Result<Bytes, String>>>,
|
||||
}
|
||||
|
||||
impl FakeRegistrar {
|
||||
pub fn new() -> Self {
|
||||
FakeRegistrar {
|
||||
calls: Arc::new(Mutex::new(Vec::new())),
|
||||
responses: Mutex::new({
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
(REGISTRAR.into(), "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".into()),
|
||||
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
|
||||
);
|
||||
map.insert(
|
||||
(URLHINT.into(), format!("{}{}", URLHINT_RESOLVE, DEFAULT_HASH)),
|
||||
Ok(vec![])
|
||||
);
|
||||
map
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_result(&self, hash: H256, result: Result<Bytes, String>) {
|
||||
self.responses.lock().insert(
|
||||
(URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)),
|
||||
result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ContractClient for FakeRegistrar {
|
||||
fn registrar(&self) -> Result<Address, String> {
|
||||
Ok(REGISTRAR.parse().unwrap())
|
||||
}
|
||||
|
||||
fn call(&self, address: Address, data: Bytes) -> ::futures::BoxFuture<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))
|
||||
}
|
||||
}
|
||||
@@ -1,207 +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 tests::helpers::{serve, request, assert_security_headers, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// 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
|
||||
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.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /app HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// 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_display_404_on_invalid_dapp() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /invaliddapp/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: invaliddapp.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Content-Type: application/json\r\n
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// 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}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc_at_slash_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
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
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// 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}"#));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_serve_proxy_pac() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /proxy/proxy.pac HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// 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_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_utils() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/inject.js HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
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,99 +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 tests::helpers::{serve_hosts, request};
|
||||
|
||||
#[test]
|
||||
fn should_reject_invalid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// 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 403 Forbidden".to_owned());
|
||||
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_valid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /ui/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_dapps_domains() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: ui.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// NOTE [todr] This is required for error pages to be styled properly.
|
||||
fn should_allow_parity_utils_even_on_invalid_domain() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/styles.css 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());
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -37,9 +37,6 @@ pub struct Url {
|
||||
/// Empty entries of `""` correspond to trailing slashes.
|
||||
pub path: Vec<String>,
|
||||
|
||||
/// The URL query.
|
||||
pub query: Option<String>,
|
||||
|
||||
/// The URL username field, from the userinfo section of the URL.
|
||||
///
|
||||
/// `None` if the `@` character was not part of the input OR
|
||||
@@ -85,17 +82,15 @@ impl Url {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let port = raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))?;
|
||||
let host = raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())?.to_owned();
|
||||
let path = raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())?
|
||||
let port = try!(raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme())));
|
||||
let host = try!(raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())).to_owned();
|
||||
let path = try!(raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned()))
|
||||
.map(|part| part.to_owned()).collect();
|
||||
let query = raw_url.query().map(|x| x.to_owned());
|
||||
|
||||
Ok(Url {
|
||||
port: port,
|
||||
host: host,
|
||||
path: path,
|
||||
query: query,
|
||||
raw: raw_url,
|
||||
username: username,
|
||||
password: password,
|
||||
|
||||
245
dapps/src/web.rs
245
dapps/src/web.rs
@@ -1,245 +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/>.
|
||||
|
||||
//! Serving web-based content (proxying)
|
||||
|
||||
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;
|
||||
|
||||
use apps;
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::{
|
||||
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
|
||||
StreamingHandler, extract_url,
|
||||
};
|
||||
use url::Url;
|
||||
use {Embeddable, WebProxyTokens};
|
||||
|
||||
pub struct Web<F> {
|
||||
embeddable_on: Embeddable,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
}
|
||||
|
||||
impl<F: Fetch> Web<F> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fetch> Endpoint for Web<F> {
|
||||
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
Box::new(WebHandler {
|
||||
control: control,
|
||||
state: State::Initial,
|
||||
path: path,
|
||||
remote: self.remote.clone(),
|
||||
fetch: self.fetch.clone(),
|
||||
web_proxy_tokens: self.web_proxy_tokens.clone(),
|
||||
embeddable_on: self.embeddable_on.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct WebInstaller {
|
||||
embeddable_on: Embeddable,
|
||||
referer: String,
|
||||
}
|
||||
|
||||
impl ContentValidator for WebInstaller {
|
||||
type Error = String;
|
||||
|
||||
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, String> {
|
||||
let status = StatusCode::from_u16(response.status().to_u16());
|
||||
let is_html = response.is_html();
|
||||
let mime = response.content_type().unwrap_or(mime!(Text/Html));
|
||||
let mut handler = StreamingHandler::new(
|
||||
response,
|
||||
status,
|
||||
mime,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
if is_html {
|
||||
handler.set_initial_content(&format!(
|
||||
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
|
||||
apps::UTILS_PATH,
|
||||
apps::URL_REFERER,
|
||||
apps::WEB_PATH,
|
||||
&self.referer,
|
||||
));
|
||||
}
|
||||
Ok(ValidatorResponse::Streaming(handler))
|
||||
}
|
||||
}
|
||||
|
||||
enum State<F: Fetch> {
|
||||
Initial,
|
||||
Error(ContentHandler),
|
||||
Fetching(ContentFetcherHandler<WebInstaller, F>),
|
||||
}
|
||||
|
||||
struct WebHandler<F: Fetch> {
|
||||
control: hyper::Control,
|
||||
state: State<F>,
|
||||
path: EndpointPath,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
embeddable_on: Embeddable,
|
||||
}
|
||||
|
||||
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()
|
||||
)))?;
|
||||
|
||||
let mut token_it = token_and_url.split('+');
|
||||
let token = token_it.next();
|
||||
let target_url = token_it.next();
|
||||
|
||||
// Check if token supplied in URL is correct.
|
||||
let domain = match token.and_then(|token| self.web_proxy_tokens.domain(token)) {
|
||||
Some(domain) => domain,
|
||||
_ => {
|
||||
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(),
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
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(),
|
||||
)));
|
||||
}
|
||||
|
||||
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),
|
||||
None => "".into(),
|
||||
};
|
||||
|
||||
Ok(format!("{}{}{}", target_url, path.join("/"), query))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Ok(url) => url,
|
||||
Err(error) => {
|
||||
self.state = error;
|
||||
return Next::write();
|
||||
}
|
||||
};
|
||||
|
||||
let mut handler = ContentFetcherHandler::new(
|
||||
target_url,
|
||||
self.path.clone(),
|
||||
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(),
|
||||
},
|
||||
self.embeddable_on.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch.clone(),
|
||||
);
|
||||
let res = handler.on_request(request);
|
||||
self.state = State::Fetching(handler);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<net::HttpStream>) -> Next {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_request_readable(decoder),
|
||||
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_response(res),
|
||||
State::Fetching(ref mut handler) => handler.on_response(res),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
|
||||
match self.state {
|
||||
State::Initial => Next::end(),
|
||||
State::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
description = "Ethcore Parity UI"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "parity-ui"
|
||||
version = "1.7.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[build-dependencies]
|
||||
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" }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev"]
|
||||
use-precompiled-js = ["parity-ui-precompiled"]
|
||||
26
db/Cargo.toml
Normal file
26
db/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
description = "Ethcore Database"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-db"
|
||||
version = "1.3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
syntex = "*"
|
||||
ethcore-ipc-codegen = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
clippy = { version = "0.0.80", optional = true}
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
semver = "0.2"
|
||||
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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,29 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Eth PUB-SUB rpc interface.
|
||||
extern crate syntex;
|
||||
extern crate ethcore_ipc_codegen as codegen;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use jsonrpc_macros::pubsub::Subscriber;
|
||||
use jsonrpc_pubsub::SubscriptionId;
|
||||
use futures::BoxFuture;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
use v1::types::pubsub;
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Eth PUB-SUB rpc interface.
|
||||
pub trait EthPubSub {
|
||||
type Metadata;
|
||||
// ipc pass
|
||||
{
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
|
||||
#[pubsub(name = "eth_subscription")] {
|
||||
/// Subscribe to Eth subscription.
|
||||
#[rpc(name = "eth_subscribe")]
|
||||
fn subscribe(&self, Self::Metadata, Subscriber<pubsub::Result>, pubsub::Kind, Trailing<pubsub::Params>);
|
||||
|
||||
/// Unsubscribe from existing Eth subscription.
|
||||
#[rpc(name = "eth_unsubscribe")]
|
||||
fn unsubscribe(&self, SubscriptionId) -> BoxFuture<bool, Error>;
|
||||
}
|
||||
// binary serialization pass
|
||||
{
|
||||
let src = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
566
db/src/database.rs
Normal file
566
db/src/database.rs
Normal file
@@ -0,0 +1,566 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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) => {
|
||||
try!(batch.put(&key, val));
|
||||
},
|
||||
WriteCacheEntry::Remove => {
|
||||
try!(batch.delete(&key));
|
||||
},
|
||||
}
|
||||
key.clone()
|
||||
};
|
||||
|
||||
self.entries.remove(&removed_key);
|
||||
|
||||
removed_so_far = removed_so_far + 1;
|
||||
}
|
||||
if removed_so_far > 0 {
|
||||
try!(db.write(batch));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// flushes until cache is empty
|
||||
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
|
||||
while !self.is_empty() { try!(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 {
|
||||
try!(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();
|
||||
|
||||
try!(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");
|
||||
|
||||
try!(cache_lock.flush_all(&db));
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Database {
|
||||
fn drop(&mut self) {
|
||||
self.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(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(try!(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> {
|
||||
try!(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 = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
match try!(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 = try!(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 = try!(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 = try!(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::init_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::init_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::init_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::init_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::init_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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,8 +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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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);
|
||||
try!(::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);
|
||||
try!(::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 = try!(blocks_service_url(db_path));
|
||||
let client = try!(nanoipc::init_client::<DatabaseClient<_>>(&url));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = try!(extras_service_url(db_path));
|
||||
let client = try!(nanoipc::init_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
138
db/src/traits.rs
Normal file
138
db/src/traits.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore database trait
|
||||
|
||||
use std::mem;
|
||||
use ipc::binary::BinaryConvertError;
|
||||
use std::collections::VecDeque;
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
description = "Ethcore development/test/build tools"
|
||||
homepage = "http://parity.io"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-devtools"
|
||||
version = "1.7.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
|
||||
@@ -1,123 +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 std::thread;
|
||||
use std::time::Duration;
|
||||
use std::io::{Read, Write};
|
||||
use std::str::{self, Lines};
|
||||
use std::net::{TcpStream, SocketAddr};
|
||||
|
||||
pub struct Response {
|
||||
pub status: String,
|
||||
pub headers: Vec<String>,
|
||||
pub headers_raw: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn assert_header(&self, header: &str, value: &str) {
|
||||
let header = format!("{}: {}", header, value);
|
||||
assert!(self.headers.iter().find(|h| *h == &header).is_some(), "Couldn't find header {} in {:?}", header, &self.headers)
|
||||
}
|
||||
|
||||
pub fn assert_status(&self, status: &str) {
|
||||
assert_eq!(self.status, status.to_owned(), "Got unexpected code. Body: {:?}", self.body);
|
||||
}
|
||||
|
||||
pub fn assert_security_headers_present(&self, port: Option<u16>) {
|
||||
assert_security_headers_present(&self.headers, port)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_block(lines: &mut Lines, all: bool) -> String {
|
||||
let mut block = String::new();
|
||||
loop {
|
||||
let line = lines.next();
|
||||
match line {
|
||||
None => break,
|
||||
Some("") if !all => break,
|
||||
Some(v) => {
|
||||
block.push_str(v);
|
||||
block.push_str("\n");
|
||||
},
|
||||
}
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn connect(address: &SocketAddr) -> TcpStream {
|
||||
let mut retries = 0;
|
||||
let mut last_error = None;
|
||||
while retries < 10 {
|
||||
retries += 1;
|
||||
|
||||
let res = TcpStream::connect(address);
|
||||
match res {
|
||||
Ok(stream) => {
|
||||
return stream;
|
||||
},
|
||||
Err(e) => {
|
||||
last_error = Some(e);
|
||||
thread::sleep(Duration::from_millis(retries * 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Unable to connect to the server. Last error: {:?}", last_error);
|
||||
}
|
||||
|
||||
pub fn request(address: &SocketAddr, request: &str) -> Response {
|
||||
let mut req = connect(address);
|
||||
req.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
|
||||
req.write_all(request.as_bytes()).unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
let _ = req.read_to_string(&mut response);
|
||||
|
||||
let mut lines = response.lines();
|
||||
let status = lines.next().expect("Expected a response").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);
|
||||
|
||||
Response {
|
||||
status: status,
|
||||
headers: headers,
|
||||
headers_raw: headers_raw,
|
||||
body: body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if all required security headers are present
|
||||
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
|
||||
if let None = port {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
|
||||
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(),
|
||||
"X-XSS-Protection missing: {:?}", headers
|
||||
);
|
||||
assert!(
|
||||
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 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -22,7 +22,6 @@ extern crate rand;
|
||||
mod random_path;
|
||||
mod test_socket;
|
||||
mod stop_guard;
|
||||
pub mod http_client;
|
||||
|
||||
pub use random_path::*;
|
||||
pub use test_socket::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -23,8 +23,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use rand::random;
|
||||
|
||||
pub struct RandomTempPath {
|
||||
path: PathBuf,
|
||||
pub panic_on_drop_failure: bool,
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
pub fn random_filename() -> String {
|
||||
@@ -40,8 +39,7 @@ impl RandomTempPath {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(random_filename());
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +48,7 @@ impl RandomTempPath {
|
||||
dir.push(random_filename());
|
||||
fs::create_dir_all(dir.as_path()).unwrap();
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,33 +67,17 @@ impl RandomTempPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for RandomTempPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
impl Deref for RandomTempPath {
|
||||
type Target = Path;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandomTempPath {
|
||||
fn drop(&mut self) {
|
||||
if let Err(_) = fs::remove_dir_all(&self) {
|
||||
if let Err(e) = fs::remove_file(&self) {
|
||||
if self.panic_on_drop_failure {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(self.as_path()) {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Ethcore (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 Ethcore (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,36 +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
|
||||
|
||||
ls&&\
|
||||
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
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
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 \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
gcc \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
binutils \
|
||||
file \
|
||||
openssl \
|
||||
libssl-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
dpkg-dev \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
libudev-dev &&\
|
||||
# cmake and llvm ppa's. then update ppa's
|
||||
add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev && \
|
||||
# install evmjit
|
||||
git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd && \
|
||||
# install rustup
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
# rustup directory
|
||||
PATH=/root/.cargo/bin:$PATH && \
|
||||
# show backtraces
|
||||
RUST_BACKTRACE=1 && \
|
||||
# build parity
|
||||
cd /build&&git clone https://github.com/paritytech/parity && \
|
||||
cd parity && \
|
||||
git pull&& \
|
||||
git checkout $BUILD_TAG && \
|
||||
cargo build --verbose --release --features final && \
|
||||
#ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity && \
|
||||
file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\
|
||||
#cleanup Docker image
|
||||
rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\
|
||||
apt-get purge -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
gcc \
|
||||
binutils \
|
||||
file \
|
||||
pkg-config \
|
||||
dpkg-dev \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
cmake llvm-3.7-dev&&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# setup ENTRYPOINT
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/parity/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'\
|
||||
@@ -38,8 +40,4 @@ RUN cd parity && \
|
||||
cargo build --target aarch64-unknown-linux-gnu --release --verbose && \
|
||||
ls /build/parity/target/aarch64-unknown-linux-gnu/release/parity && \
|
||||
/usr/bin/aarch64-linux-gnu-strip /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
RUN file /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/aarch64-unknown-linux-gnu/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 armv7-unknown-linux-gnueabihf
|
||||
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'\
|
||||
@@ -38,8 +40,4 @@ RUN cd parity && \
|
||||
cargo build --target armv7-unknown-linux-gnueabihf --release --verbose && \
|
||||
ls /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity && \
|
||||
/usr/bin/arm-linux-gnueabihf-strip /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
RUN file /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/armv7-unknown-linux-gnueabihf/release/parity"]
|
||||
|
||||
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,13 +45,11 @@ 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
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
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
|
||||
binutils
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -30,13 +25,12 @@ 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 --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,7 +1,7 @@
|
||||
[package]
|
||||
name = "ethash"
|
||||
version = "1.7.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.3.0"
|
||||
authors = ["arkpar <arkadiy@ethcore.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.2.6"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (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,17 +86,16 @@ impl Node {
|
||||
pub type H256 = [u8; 32];
|
||||
|
||||
pub struct Light {
|
||||
cache_dir: PathBuf,
|
||||
block_number: u64,
|
||||
cache: Vec<Node>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
}
|
||||
|
||||
/// Light cache structure
|
||||
/// Light cache structur
|
||||
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,57 +105,45 @@ 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 mut file = File::open(path)?;
|
||||
let path = Light::file_path(seed_compute.get_seedhash(block_number));
|
||||
let mut file = try!(File::open(path));
|
||||
|
||||
let cache_size = get_cache_size(block_number);
|
||||
if file.metadata()?.len() != cache_size as u64 {
|
||||
if try!(file.metadata()).len() != cache_size as u64 {
|
||||
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)?;
|
||||
try!(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> {
|
||||
pub fn to_file(&self) -> io::Result<()> {
|
||||
let seed_compute = self.seed_compute.lock();
|
||||
let path = Light::file_path(&self.cache_dir, 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)
|
||||
);
|
||||
|
||||
if deprecated.exists() {
|
||||
debug!(target: "ethash", "removing: {:?}", &deprecated);
|
||||
fs::remove_file(deprecated)?;
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
let mut file = File::create(&path)?;
|
||||
let path = Light::file_path(seed_compute.get_seedhash(self.block_number));
|
||||
try!(fs::create_dir_all(path.parent().unwrap()));
|
||||
let mut file = try!(File::create(path));
|
||||
|
||||
let cache_size = self.cache.len() * NODE_BYTES;
|
||||
let buf = unsafe { slice::from_raw_parts(self.cache.as_ptr() as *const u8, cache_size) };
|
||||
file.write(buf)?;
|
||||
Ok(path)
|
||||
try!(file.write(buf));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,9 +191,6 @@ impl SeedHashCompute {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slow_get_seedhash(block_number: u64) -> H256 {
|
||||
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
@@ -343,12 +327,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 +359,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 +419,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[..]);
|
||||
@@ -469,19 +455,3 @@ fn test_seed_compute_after_newer() {
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let path = ::std::env::temp_dir();
|
||||
let first = Light::new(&path, 0).to_file().unwrap();
|
||||
|
||||
let second = Light::new(&path, ETHASH_EPOCH_LENGTH).to_file().unwrap();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = Light::new(&path, 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();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -25,9 +25,8 @@ 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};
|
||||
pub use compute::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty};
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::Mutex;
|
||||
@@ -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,
|
||||
@@ -72,30 +69,25 @@ impl EthashManager {
|
||||
Some(ref e) if *e == epoch => lights.recent.clone(),
|
||||
_ => match lights.prev_epoch.clone() {
|
||||
Some(e) if e == epoch => {
|
||||
// don't swap if recent is newer.
|
||||
if lights.recent_epoch > lights.prev_epoch {
|
||||
None
|
||||
} else {
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
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 +107,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);
|
||||
|
||||
@@ -1,68 +1,51 @@
|
||||
[package]
|
||||
description = "Ethcore library"
|
||||
homepage = "http://parity.io"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore"
|
||||
version = "1.7.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
"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"
|
||||
semver = "0.2"
|
||||
bit-set = "0.4"
|
||||
time = "0.1"
|
||||
transient-hashmap = "0.4"
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
clippy = { version = "0.0.80", 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" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rand = "0.3"
|
||||
lru-cache = "0.0.7"
|
||||
bloomfilter = { git = "https://github.com/ethcore/rust-bloom-filter" }
|
||||
byteorder = "0.5"
|
||||
|
||||
[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"]
|
||||
evm-debug = ["slow-blocks"]
|
||||
evm-debug-tests = ["evm-debug"]
|
||||
slow-blocks = [] # Use SLOW_TX_DURATION="50" (compile time!) to track transactions over 50ms
|
||||
jit = ["evmjit"]
|
||||
evm-debug = []
|
||||
json-tests = []
|
||||
test-heavy = []
|
||||
dev = ["clippy"]
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -17,7 +17,7 @@
|
||||
extern crate ethcore_ipc_codegen;
|
||||
|
||||
fn main() {
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/client/traits.rs", cfg!(feature="ipc")).unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/snapshot/snapshot_service_trait.rs", cfg!(feature="ipc")).unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/client/chain_notify.rs", cfg!(feature="ipc")).unwrap();
|
||||
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap();
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "evm"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
bit-set = "0.4"
|
||||
byteorder = "1.0"
|
||||
common-types = { path = "../types" }
|
||||
ethcore-util = { path = "../../util" }
|
||||
evmjit = { path = "../../evmjit", optional = true }
|
||||
ethjson = { path = "../../json" }
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
rlp = { path = "../../util/rlp" }
|
||||
parity-wasm = "0.12"
|
||||
wasm-utils = { git = "https://github.com/paritytech/wasm-utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-hex = "1.0"
|
||||
|
||||
[features]
|
||||
jit = ["evmjit"]
|
||||
@@ -1,70 +0,0 @@
|
||||
//! EVM call types.
|
||||
|
||||
use rlp::{Encodable, Decodable, DecoderError, RlpStream, UntrustedRlp};
|
||||
|
||||
/// The type of the call-like instruction.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum CallType {
|
||||
/// Not a CALL.
|
||||
None,
|
||||
/// CALL.
|
||||
Call,
|
||||
/// CALLCODE.
|
||||
CallCode,
|
||||
/// DELEGATECALL.
|
||||
DelegateCall,
|
||||
/// STATICCALL
|
||||
StaticCall,
|
||||
}
|
||||
|
||||
impl Encodable for CallType {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
let v = match *self {
|
||||
CallType::None => 0u32,
|
||||
CallType::Call => 1,
|
||||
CallType::CallCode => 2,
|
||||
CallType::DelegateCall => 3,
|
||||
CallType::StaticCall => 4,
|
||||
};
|
||||
Encodable::rlp_append(&v, s);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for CallType {
|
||||
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
|
||||
rlp.as_val().and_then(|v| Ok(match v {
|
||||
0u32 => CallType::None,
|
||||
1 => CallType::Call,
|
||||
2 => CallType::CallCode,
|
||||
3 => CallType::DelegateCall,
|
||||
4 => CallType::StaticCall,
|
||||
_ => return Err(DecoderError::Custom("Invalid value of CallType item")),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rlp::*;
|
||||
use super::CallType;
|
||||
|
||||
#[test]
|
||||
fn encode_call_type() {
|
||||
let ct = CallType::Call;
|
||||
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(&ct);
|
||||
assert!(!s.is_finished(), "List shouldn't finished yet");
|
||||
s.append(&ct);
|
||||
assert!(s.is_finished(), "List should be finished now");
|
||||
s.out();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_and_decode_call_type() {
|
||||
let original = CallType::Call;
|
||||
let encoded = encode(&original);
|
||||
let decoded = decode(&encoded);
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
}
|
||||
@@ -1,373 +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 util::*;
|
||||
use super::u256_to_address;
|
||||
|
||||
use {evm, ext};
|
||||
use instructions::{self, Instruction, InstructionInfo};
|
||||
use interpreter::stack::Stack;
|
||||
use schedule::Schedule;
|
||||
|
||||
macro_rules! overflowing {
|
||||
($x: expr) => {{
|
||||
let (v, overflow) = $x;
|
||||
if overflow { return Err(evm::Error::OutOfGas); }
|
||||
v
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="dev", allow(enum_variant_names))]
|
||||
enum Request<Cost: ::evm::CostType> {
|
||||
Gas(Cost),
|
||||
GasMem(Cost, Cost),
|
||||
GasMemProvide(Cost, Cost, Option<U256>),
|
||||
GasMemCopy(Cost, Cost, Cost)
|
||||
}
|
||||
|
||||
pub struct InstructionRequirements<Cost> {
|
||||
pub gas_cost: Cost,
|
||||
pub provide_gas: Option<Cost>,
|
||||
pub memory_total_gas: Cost,
|
||||
pub memory_required_size: usize,
|
||||
}
|
||||
|
||||
pub struct Gasometer<Gas> {
|
||||
pub current_gas: Gas,
|
||||
pub current_mem_gas: Gas,
|
||||
}
|
||||
|
||||
impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
|
||||
pub fn new(current_gas: Gas) -> Self {
|
||||
Gasometer {
|
||||
current_gas: current_gas,
|
||||
current_mem_gas: Gas::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_gas(&self, gas_cost: &Gas) -> evm::Result<()> {
|
||||
match &self.current_gas < gas_cost {
|
||||
true => Err(evm::Error::OutOfGas),
|
||||
false => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// How much gas is provided to a CALL/CREATE, given that we need to deduct `needed` for this operation
|
||||
/// and that we `requested` some.
|
||||
pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option<U256>) -> evm::Result<Gas> {
|
||||
// Try converting requested gas to `Gas` (`U256/u64`)
|
||||
// but in EIP150 even if we request more we should never fail from OOG
|
||||
let requested = requested.map(Gas::from_u256);
|
||||
|
||||
match schedule.sub_gas_cap_divisor {
|
||||
Some(cap_divisor) if self.current_gas >= needed => {
|
||||
let gas_remaining = self.current_gas - needed;
|
||||
let max_gas_provided = match cap_divisor {
|
||||
64 => gas_remaining - (gas_remaining >> 6),
|
||||
cap_divisor => gas_remaining - gas_remaining / Gas::from(cap_divisor),
|
||||
};
|
||||
|
||||
if let Some(Ok(r)) = requested {
|
||||
Ok(min(r, max_gas_provided))
|
||||
} else {
|
||||
Ok(max_gas_provided)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(r) = requested {
|
||||
r
|
||||
} else if self.current_gas >= needed {
|
||||
Ok(self.current_gas - needed)
|
||||
} else {
|
||||
Ok(0.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="dev", allow(cyclomatic_complexity))]
|
||||
/// Determine how much gas is used by the given instruction, given the machine's state.
|
||||
///
|
||||
/// We guarantee that the final element of the returned tuple (`provided`) will be `Some`
|
||||
/// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case,
|
||||
/// it will be the amount of gas that the current context provides to the child context.
|
||||
pub fn requirements(
|
||||
&mut self,
|
||||
ext: &ext::Ext,
|
||||
instruction: Instruction,
|
||||
info: &InstructionInfo,
|
||||
stack: &Stack<U256>,
|
||||
current_mem_size: usize,
|
||||
) -> evm::Result<InstructionRequirements<Gas>> {
|
||||
let schedule = ext.schedule();
|
||||
let tier = instructions::get_tier_idx(info.tier);
|
||||
let default_gas = Gas::from(schedule.tier_step_gas[tier]);
|
||||
|
||||
let cost = match instruction {
|
||||
instructions::JUMPDEST => {
|
||||
Request::Gas(Gas::from(1))
|
||||
},
|
||||
instructions::SSTORE => {
|
||||
let address = H256::from(stack.peek(0));
|
||||
let newval = stack.peek(1);
|
||||
let val = U256::from(&*ext.storage_at(&address)?);
|
||||
|
||||
let gas = if val.is_zero() && !newval.is_zero() {
|
||||
schedule.sstore_set_gas
|
||||
} else {
|
||||
// Refund for below case is added when actually executing sstore
|
||||
// !is_zero(&val) && is_zero(newval)
|
||||
schedule.sstore_reset_gas
|
||||
};
|
||||
Request::Gas(Gas::from(gas))
|
||||
},
|
||||
instructions::SLOAD => {
|
||||
Request::Gas(Gas::from(schedule.sload_gas))
|
||||
},
|
||||
instructions::BALANCE => {
|
||||
Request::Gas(Gas::from(schedule.balance_gas))
|
||||
},
|
||||
instructions::EXTCODESIZE => {
|
||||
Request::Gas(Gas::from(schedule.extcodesize_gas))
|
||||
},
|
||||
instructions::SUICIDE => {
|
||||
let mut gas = Gas::from(schedule.suicide_gas);
|
||||
|
||||
let is_value_transfer = !ext.origin_balance()?.is_zero();
|
||||
let address = u256_to_address(stack.peek(0));
|
||||
if (
|
||||
!schedule.no_empty && !ext.exists(&address)?
|
||||
) || (
|
||||
schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)?
|
||||
) {
|
||||
gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into()));
|
||||
}
|
||||
|
||||
Request::Gas(gas)
|
||||
},
|
||||
instructions::MSTORE | instructions::MLOAD => {
|
||||
Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 32)?)
|
||||
},
|
||||
instructions::MSTORE8 => {
|
||||
Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 1)?)
|
||||
},
|
||||
instructions::RETURN | instructions::REVERT => {
|
||||
Request::GasMem(default_gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
instructions::SHA3 => {
|
||||
let w = overflowing!(add_gas_usize(Gas::from_u256(*stack.peek(1))?, 31));
|
||||
let words = w >> 5;
|
||||
let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words);
|
||||
Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
instructions::CALLDATACOPY | instructions::CODECOPY | instructions::RETURNDATACOPY => {
|
||||
Request::GasMemCopy(default_gas, mem_needed(stack.peek(0), stack.peek(2))?, Gas::from_u256(*stack.peek(2))?)
|
||||
},
|
||||
instructions::EXTCODECOPY => {
|
||||
Request::GasMemCopy(schedule.extcodecopy_base_gas.into(), mem_needed(stack.peek(1), stack.peek(3))?, Gas::from_u256(*stack.peek(3))?)
|
||||
},
|
||||
instructions::LOG0...instructions::LOG4 => {
|
||||
let no_of_topics = instructions::get_log_topics(instruction);
|
||||
let log_gas = schedule.log_gas + schedule.log_topic_gas * no_of_topics;
|
||||
|
||||
let data_gas = overflowing!(Gas::from_u256(*stack.peek(1))?.overflow_mul(Gas::from(schedule.log_data_gas)));
|
||||
let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
|
||||
Request::GasMem(gas, mem_needed(stack.peek(0), stack.peek(1))?)
|
||||
},
|
||||
instructions::CALL | instructions::CALLCODE => {
|
||||
let mut gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
mem_needed(stack.peek(5), stack.peek(6))?,
|
||||
mem_needed(stack.peek(3), stack.peek(4))?
|
||||
);
|
||||
|
||||
let address = u256_to_address(stack.peek(1));
|
||||
let is_value_transfer = !stack.peek(2).is_zero();
|
||||
|
||||
if instruction == instructions::CALL && (
|
||||
(!schedule.no_empty && !ext.exists(&address)?)
|
||||
||
|
||||
(schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address)?)
|
||||
) {
|
||||
gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into()));
|
||||
}
|
||||
|
||||
if is_value_transfer {
|
||||
gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into()));
|
||||
}
|
||||
|
||||
let requested = *stack.peek(0);
|
||||
|
||||
Request::GasMemProvide(gas, mem, Some(requested))
|
||||
},
|
||||
instructions::DELEGATECALL | instructions::STATICCALL => {
|
||||
let gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
mem_needed(stack.peek(4), stack.peek(5))?,
|
||||
mem_needed(stack.peek(2), stack.peek(3))?
|
||||
);
|
||||
let requested = *stack.peek(0);
|
||||
|
||||
Request::GasMemProvide(gas, mem, Some(requested))
|
||||
},
|
||||
instructions::CREATE | instructions::CREATE2 => {
|
||||
let gas = Gas::from(schedule.create_gas);
|
||||
let mem = mem_needed(stack.peek(1), stack.peek(2))?;
|
||||
|
||||
Request::GasMemProvide(gas, mem, None)
|
||||
},
|
||||
instructions::EXP => {
|
||||
let expon = stack.peek(1);
|
||||
let bytes = ((expon.bits() + 7) / 8) as usize;
|
||||
let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes);
|
||||
Request::Gas(gas)
|
||||
},
|
||||
instructions::BLOCKHASH => {
|
||||
Request::Gas(Gas::from(schedule.blockhash_gas))
|
||||
},
|
||||
_ => Request::Gas(default_gas),
|
||||
};
|
||||
|
||||
Ok(match cost {
|
||||
Request::Gas(gas) => {
|
||||
InstructionRequirements {
|
||||
gas_cost: gas,
|
||||
provide_gas: None,
|
||||
memory_required_size: 0,
|
||||
memory_total_gas: self.current_mem_gas,
|
||||
}
|
||||
},
|
||||
Request::GasMem(gas, mem_size) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
InstructionRequirements {
|
||||
gas_cost: gas,
|
||||
provide_gas: None,
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
Request::GasMemProvide(gas, mem_size, requested) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
let provided = self.gas_provided(schedule, gas, requested)?;
|
||||
let total_gas = overflowing!(gas.overflow_add(provided));
|
||||
|
||||
InstructionRequirements {
|
||||
gas_cost: total_gas,
|
||||
provide_gas: Some(provided),
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
Request::GasMemCopy(gas, mem_size, copy) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = self.mem_gas_cost(schedule, current_mem_size, &mem_size)?;
|
||||
let copy = overflowing!(add_gas_usize(copy, 31)) >> 5;
|
||||
let copy_gas = Gas::from(schedule.copy_gas) * copy;
|
||||
let gas = overflowing!(gas.overflow_add(copy_gas));
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
|
||||
InstructionRequirements {
|
||||
gas_cost: gas,
|
||||
provide_gas: None,
|
||||
memory_required_size: new_mem_size,
|
||||
memory_total_gas: new_mem_gas,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn mem_gas_cost(&self, schedule: &Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, Gas, usize)> {
|
||||
let gas_for_mem = |mem_size: Gas| {
|
||||
let s = mem_size >> 5;
|
||||
// s * memory_gas + s * s / quad_coeff_div
|
||||
let a = overflowing!(s.overflow_mul(Gas::from(schedule.memory_gas)));
|
||||
|
||||
// Calculate s*s/quad_coeff_div
|
||||
assert_eq!(schedule.quad_coeff_div, 512);
|
||||
let b = overflowing!(s.overflow_mul_shr(s, 9));
|
||||
Ok(overflowing!(a.overflow_add(b)))
|
||||
};
|
||||
|
||||
let current_mem_size = Gas::from(current_mem_size);
|
||||
let req_mem_size_rounded = (overflowing!(mem_size.overflow_add(Gas::from(31 as usize))) >> 5) << 5;
|
||||
|
||||
let (mem_gas_cost, new_mem_gas) = if req_mem_size_rounded > current_mem_size {
|
||||
let new_mem_gas = gas_for_mem(req_mem_size_rounded)?;
|
||||
(new_mem_gas - self.current_mem_gas, new_mem_gas)
|
||||
} else {
|
||||
(Gas::from(0), self.current_mem_gas)
|
||||
};
|
||||
|
||||
Ok((mem_gas_cost, new_mem_gas, req_mem_size_rounded.as_usize()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn mem_needed_const<Gas: evm::CostType>(mem: &U256, add: usize) -> evm::Result<Gas> {
|
||||
Gas::from_u256(overflowing!(mem.overflowing_add(U256::from(add))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_needed<Gas: evm::CostType>(offset: &U256, size: &U256) -> evm::Result<Gas> {
|
||||
if size.is_zero() {
|
||||
return Ok(Gas::from(0));
|
||||
}
|
||||
|
||||
Gas::from_u256(overflowing!(offset.overflowing_add(*size)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_gas_usize<Gas: evm::CostType>(value: Gas, num: usize) -> (Gas, bool) {
|
||||
value.overflow_add(Gas::from(num))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mem_gas_cost() {
|
||||
// given
|
||||
let gasometer = Gasometer::<U256>::new(U256::zero());
|
||||
let schedule = Schedule::default();
|
||||
let current_mem_size = 5;
|
||||
let mem_size = !U256::zero();
|
||||
|
||||
// when
|
||||
let result = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size);
|
||||
|
||||
// then
|
||||
if result.is_ok() {
|
||||
assert!(false, "Should fail with OutOfGas");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_mem_cost() {
|
||||
// given
|
||||
let gasometer = Gasometer::<usize>::new(0);
|
||||
let schedule = Schedule::default();
|
||||
let current_mem_size = 0;
|
||||
let mem_size = 5;
|
||||
|
||||
// when
|
||||
let (mem_cost, new_mem_gas, mem_size) = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(mem_cost, 3);
|
||||
assert_eq!(new_mem_gas, 3);
|
||||
assert_eq!(mem_size, 32);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user