Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6a0b4660 | ||
|
|
73f94c33f7 | ||
|
|
e73f6573d1 | ||
|
|
a19e5f5628 | ||
|
|
cfdc0d8cfb | ||
|
|
0d20b21ee8 | ||
|
|
2e9cde38e4 | ||
|
|
b88823b51f | ||
|
|
1cdb17cd24 | ||
|
|
8f89235a25 | ||
|
|
3df2e3358c | ||
|
|
78c04856e8 | ||
|
|
e49ba9d0ae | ||
|
|
043ca21863 | ||
|
|
aea995cf55 | ||
|
|
0799dbf95f | ||
|
|
054f0c0014 | ||
|
|
47729ae199 | ||
|
|
e086b4e94a | ||
|
|
fbe02fc6b6 | ||
|
|
f978b3e833 | ||
|
|
20d7f8a9d9 | ||
|
|
e727d92e0f | ||
|
|
8b5a9b701a | ||
|
|
33cc10549a | ||
|
|
522401296d | ||
|
|
a3261d1428 | ||
|
|
250736a085 | ||
|
|
ce50286138 | ||
|
|
19d0905cbd | ||
|
|
11f2b5d0ae | ||
|
|
8bd89d05be | ||
|
|
2b5d82c901 | ||
|
|
ec99be1b28 | ||
|
|
52ce4f9bc1 | ||
|
|
9e9157b1bd | ||
|
|
576c9e7801 | ||
|
|
5d83c60b75 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,5 +32,3 @@
|
||||
out/
|
||||
|
||||
.vscode
|
||||
|
||||
/parity.*
|
||||
|
||||
378
.gitlab-ci.yml
378
.gitlab-ci.yml
@@ -1,7 +1,6 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- push-release
|
||||
- build
|
||||
variables:
|
||||
GIT_DEPTH: "3"
|
||||
@@ -10,7 +9,7 @@ variables:
|
||||
RUSTFLAGS: ""
|
||||
CARGOFLAGS: ""
|
||||
cache:
|
||||
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
key: "CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
@@ -19,27 +18,21 @@ linux-stable:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- md5sum target/release/parity >> parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- md5sum "parity_"$VER"_amd64.deb" >> "parity_"$VER"_amd64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-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/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
@@ -47,41 +40,6 @@ linux-stable:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
image: ethcore/rust-debian:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
- 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
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; 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
|
||||
tags:
|
||||
- rust
|
||||
- rust-debian
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
||||
linux-beta:
|
||||
stage: build
|
||||
image: ethcore/rust:beta
|
||||
@@ -89,9 +47,8 @@ linux-beta:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
@@ -108,9 +65,8 @@ linux-nightly:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
@@ -127,23 +83,16 @@ linux-centos:
|
||||
- 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 --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity >> parity.md5
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
|
||||
- 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-centos
|
||||
@@ -151,38 +100,31 @@ linux-centos:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: ethcore/rust-i686:latest
|
||||
allow_failure: true
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: ethcore/rust-i686:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
# - 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 i686-unknown-linux-gnu --features final --release $CARGOFLAGS
|
||||
- strip target/$PLATFORM/release/parity
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
|
||||
- cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS
|
||||
- strip target/i686-unknown-linux-gnu/release/parity
|
||||
- md5sum target/i686-unknown-linux-gnu/release/parity >> parity.md5
|
||||
- sh scripts/deb-build.sh i386
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/i686-unknown-linux-gnu/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
|
||||
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
|
||||
- md5sum "parity_"$VER"_i386.deb" >> "parity_"$VER"_i386.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- 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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-i686
|
||||
@@ -198,37 +140,30 @@ linux-armv7:
|
||||
- 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
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- cargo build --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS
|
||||
- arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
- md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/armv7-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
|
||||
- md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- 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/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -244,37 +179,30 @@ linux-arm:
|
||||
- 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
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- cargo build --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS
|
||||
- arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity
|
||||
- md5sum target/arm-unknown-linux-gnueabihf/release/parity >> parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/arm-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
|
||||
- md5sum "parity_"$VER"_armhf.deb" >> "parity_"$VER"_armhf.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- 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/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -289,31 +217,24 @@ linux-armv6:
|
||||
only:
|
||||
# - beta
|
||||
# - tags
|
||||
# - stable
|
||||
- triggers
|
||||
- stable
|
||||
script:
|
||||
- export CC=arm-linux-gnueabi-gcc
|
||||
- export CXX=arm-linux-gnueabi-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=arm-unknown-linux-gnueabi
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- arm-linux-gnueabi-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- cargo build --target arm-unknown-linux-gnueabi --release $CARGOFLAGS
|
||||
- arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity
|
||||
- md5sum target/arm-unknown-linux-gnueabi/release/parity >> parity.md5
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity.md5 --body parity.md5
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -329,36 +250,30 @@ linux-aarch64:
|
||||
- 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.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
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- cargo build --target aarch64-unknown-linux-gnu --release $CARGOFLAGS
|
||||
- aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity
|
||||
- md5sum target/aarch64-unknown-linux-gnu/release/parity >> parity.md5
|
||||
- sh scripts/deb-build.sh arm64
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- cp target/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_arm64.deb"
|
||||
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
|
||||
- md5sum "parity_"$VER"_arm64.deb" >> "parity_"$VER"_arm64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$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/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
@@ -373,30 +288,14 @@ darwin:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script: |
|
||||
export COMMIT=$(git rev-parse HEAD)
|
||||
export PLATFORM=x86_64-apple-darwin
|
||||
cargo build -j 8 --features final --release #$CARGOFLAGS
|
||||
cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS
|
||||
cargo build -j 8 --features final --release -p evmbin #$CARGOFLAGS
|
||||
rm -rf parity.md5
|
||||
md5sum target/release/parity > parity.md5
|
||||
export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
packagesbuild -v mac/Parity.pkgproj
|
||||
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
mv target/release/Parity\ Ethereum.pkg "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
md5sum "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" >> "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
aws configure set aws_access_key_id $s3_key
|
||||
aws configure set aws_secret_access_key $s3_secret
|
||||
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
script:
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- rm -rf parity.md5
|
||||
- md5sum target/release/parity >> parity.md5
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5
|
||||
tags:
|
||||
- osx
|
||||
artifacts:
|
||||
@@ -412,22 +311,18 @@ windows:
|
||||
- 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 1.14.0-x86_64-pc-windows-msvc
|
||||
- cargo build --features final --release #%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
|
||||
- set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off
|
||||
- rustup default stable-x86_64-pc-windows-msvc
|
||||
- cargo build --release %CARGOFLAGS%
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x86 /p:Configuration=Release
|
||||
- signtool sign /f %keyfile% /p %certpass% windows\ptray\release\ptray.exe
|
||||
- cd nsis
|
||||
- makensis.exe installer.nsi
|
||||
- copy installer.exe InstallParity.exe
|
||||
@@ -443,22 +338,14 @@ windows:
|
||||
- cd ..\..
|
||||
- aws configure set aws_access_key_id %s3_key%
|
||||
- aws configure set aws_secret_access_key %s3_secret%
|
||||
- echo %CI_BUILD_REF_NAME%
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %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.exe --body target\release\parity.exe
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
|
||||
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
|
||||
tags:
|
||||
- rust-windows
|
||||
artifacts:
|
||||
@@ -467,41 +354,30 @@ windows:
|
||||
- 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
|
||||
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
|
||||
- sh scripts/docker-build.sh $DOCKER_TAG
|
||||
tags:
|
||||
- docker
|
||||
test-darwin:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
# - beta
|
||||
# - tags
|
||||
- stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- 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 $CARGOFLAGS --no-release
|
||||
tags:
|
||||
- osx
|
||||
allow_failure: true
|
||||
test-windows:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
# - beta
|
||||
# - tags
|
||||
- stable
|
||||
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 ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p evmbin -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
|
||||
- cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
|
||||
tags:
|
||||
- rust-windows
|
||||
allow_failure: true
|
||||
@@ -510,55 +386,27 @@ test-rust-stable:
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only CI_BUILD_REF CI_BUILD_REF@{1} | grep \.js | wc -l)
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi
|
||||
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
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
js-test:
|
||||
js-tests:
|
||||
stage: test
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||
- ./js/scripts/install-deps.sh
|
||||
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
|
||||
- ./js/scripts/lint.sh
|
||||
- ./js/scripts/test.sh
|
||||
- ./js/scripts/build.sh
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
test-rust-beta:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
image: ethcore/rust:beta
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- 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
|
||||
image: ethcore/rust:nightly
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- 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
|
||||
- javascript-test
|
||||
js-release:
|
||||
stage: js-build
|
||||
only:
|
||||
@@ -567,21 +415,9 @@ js-release:
|
||||
- stable
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||
- ./js/scripts/install-deps.sh
|
||||
script:
|
||||
- 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
|
||||
- ./js/scripts/build.sh
|
||||
- ./js/scripts/release.sh
|
||||
tags:
|
||||
- javascript
|
||||
push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- tags
|
||||
image: ethcore/rust:stable
|
||||
script:
|
||||
- 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
|
||||
|
||||
@@ -16,8 +16,8 @@ git:
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
env: RUN_TESTS="true" TEST_OPTIONS=""
|
||||
- rust: stable
|
||||
env: RUN_TESTS="true" TEST_OPTIONS="--no-release"
|
||||
- rust: beta
|
||||
env: RUN_COVERAGE="true"
|
||||
- rust: stable
|
||||
env: RUN_DOCS="true"
|
||||
@@ -71,7 +71,8 @@ install:
|
||||
script:
|
||||
- if [ "$RUN_TESTS" = "true" ]; then
|
||||
./js/scripts/lint.sh &&
|
||||
travis_wait 40 ./test.sh $TEST_OPTIONS;
|
||||
./js/scripts/test.sh &&
|
||||
./test.sh $TEST_OPTIONS --verbose;
|
||||
fi
|
||||
- if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi
|
||||
|
||||
|
||||
1037
Cargo.lock
generated
1037
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
32
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
description = "Ethcore client."
|
||||
name = "parity"
|
||||
version = "1.5.7"
|
||||
version = "1.4.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
@@ -20,7 +20,7 @@ time = "0.1"
|
||||
num_cpus = "0.2"
|
||||
number_prefix = "0.2"
|
||||
rpassword = "0.2.1"
|
||||
semver = "0.5"
|
||||
semver = "0.2"
|
||||
ansi_term = "0.7"
|
||||
lazy_static = "0.2"
|
||||
regex = "0.1"
|
||||
@@ -28,32 +28,25 @@ isatty = "0.1"
|
||||
toml = "0.2"
|
||||
serde = "0.8.0"
|
||||
serde_json = "0.8.0"
|
||||
app_dirs = "1.1.1"
|
||||
hyper = { version = "0.9", default-features = false }
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
fdlimit = "0.1"
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
rlp = { path = "util/rlp" }
|
||||
ethsync = { path = "sync" }
|
||||
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.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-rpc = { path = "rpc" }
|
||||
ethcore-signer = { path = "signer" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
||||
ethcore-logger = { path = "logger" }
|
||||
rlp = { path = "util/rlp" }
|
||||
ethcore-stratum = { path = "stratum" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
evmbin = { path = "evmbin" }
|
||||
rpc-cli = { path = "rpc_cli" }
|
||||
parity-rpc-client = { path = "rpc_client" }
|
||||
ethcore-light = { path = "ethcore/light" }
|
||||
parity-hash-fetch = { path = "hash-fetch" }
|
||||
parity-updater = { path = "updater" }
|
||||
parity-reactor = { path = "util/reactor" }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
@@ -63,6 +56,7 @@ daemonize = "0.2"
|
||||
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
|
||||
ui = [
|
||||
"dapps",
|
||||
"ethcore-dapps/ui",
|
||||
@@ -73,6 +67,7 @@ ui-precompiled = [
|
||||
"ethcore-signer/ui-precompiled",
|
||||
"ethcore-dapps/ui-precompiled",
|
||||
]
|
||||
|
||||
dapps = ["ethcore-dapps"]
|
||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||
jit = ["ethcore/jit"]
|
||||
@@ -85,7 +80,6 @@ ethstore-cli = ["ethcore/ethstore-cli"]
|
||||
evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
final = ["ethcore-util/final"]
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
@@ -94,4 +88,4 @@ name = "parity"
|
||||
[profile.release]
|
||||
debug = false
|
||||
lto = false
|
||||
panic = "abort"
|
||||
|
||||
|
||||
17
README.md
17
README.md
@@ -58,18 +58,11 @@ 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:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl` and `pkg-config` packages to be installed.
|
||||
- OSX:
|
||||
- Linux and 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
|
||||
@@ -103,14 +96,6 @@ $ cargo build --release
|
||||
|
||||
This will produce an executable in the `./target/release` subdirectory.
|
||||
|
||||
----
|
||||
|
||||
## 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
|
||||
|
||||
10
appveyor.yml
10
appveyor.yml
@@ -6,7 +6,7 @@ environment:
|
||||
certpass:
|
||||
secure: 0BgXJqxq9Ei34/hZ7121FQ==
|
||||
keyfile: C:\users\appveyor\Certificates.p12
|
||||
RUSTFLAGS: -D warnings
|
||||
RUSTFLAGS: -Zorbit=off -D warnings
|
||||
|
||||
branches:
|
||||
only:
|
||||
@@ -19,10 +19,10 @@ branches:
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- ps: Install-Product node 6
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.13.0-x86_64-pc-windows-msvc.exe"
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.12.0-x86_64-pc-windows-msvc.exe"
|
||||
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -FileName nsis\SimpleFC.dll
|
||||
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -FileName nsis\vc_redist.x64.exe
|
||||
- rust-1.13.0-x86_64-pc-windows-msvc.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- rust-1.12.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
|
||||
@@ -38,8 +38,8 @@ after_test:
|
||||
- cargo build --verbose --release
|
||||
- ps: if($env:cert) { Start-FileDownload $env:cert -FileName $env:keyfile }
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass target\release\parity.exe }
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\x64\release\ptray.exe }
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x86 /p:Configuration=Release
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\release\ptray.exe }
|
||||
- makensis.exe nsis\installer.nsi
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass nsis\installer.exe }
|
||||
|
||||
|
||||
2
build.rs
2
build.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,30 +1,29 @@
|
||||
[package]
|
||||
description = "Parity Dapps crate"
|
||||
name = "ethcore-dapps"
|
||||
version = "1.5.0"
|
||||
version = "1.4.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = ["Ethcore <admin@ethcore.io"]
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
rand = "0.3.14"
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
futures = "0.1"
|
||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio-old" }
|
||||
jsonrpc-core = "3.0"
|
||||
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"
|
||||
rustc-serialize = "0.3"
|
||||
serde = "0.8"
|
||||
serde_json = "0.8"
|
||||
ethabi = "0.2.2"
|
||||
linked-hash-map = "0.3"
|
||||
parity-dapps-glue = "1.4"
|
||||
mime = "0.2"
|
||||
mime_guess = "1.6.1"
|
||||
time = "0.1.35"
|
||||
serde_macros = { version = "0.8", optional = true }
|
||||
zip = { version = "0.1", default-features = false }
|
||||
@@ -33,10 +32,9 @@ ethcore-rpc = { path = "../rpc" }
|
||||
ethcore-util = { path = "../util" }
|
||||
fetch = { path = "../util/fetch" }
|
||||
parity-ui = { path = "./ui" }
|
||||
parity-hash-fetch = { path = "../hash-fetch" }
|
||||
parity-reactor = { path = "../util/reactor" }
|
||||
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
mime_guess = { version = "1.6.1" }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.8", optional = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,9 +1,9 @@
|
||||
[package]
|
||||
description = "Base Package for all Parity built-in dapps"
|
||||
name = "parity-dapps-glue"
|
||||
version = "1.5.0"
|
||||
version = "1.4.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = ["Ethcore <admin@ethcore.io"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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, 2016 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, 2016 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
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,7 +23,7 @@ use hyper::header::AccessControlAllowOrigin;
|
||||
|
||||
use api::types::{App, ApiError};
|
||||
use api::response;
|
||||
use apps::fetcher::Fetcher;
|
||||
use apps::fetcher::ContentFetcher;
|
||||
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
@@ -33,11 +33,11 @@ use jsonrpc_http_server::cors;
|
||||
pub struct RestApi {
|
||||
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetcher: Arc<Fetcher>,
|
||||
fetcher: Arc<ContentFetcher>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<ContentFetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
endpoints: endpoints,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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, 2016 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,6 @@
|
||||
use endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct App {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@@ -55,7 +54,6 @@ impl Into<EndpointInfo> for App {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApiError {
|
||||
pub code: String,
|
||||
pub title: String,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,13 +17,14 @@
|
||||
//! Fetchable Dapps support.
|
||||
|
||||
use std::fs;
|
||||
use std::sync::{Arc};
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use page::LocalPageEndpoint;
|
||||
use handlers::FetchControl;
|
||||
|
||||
pub enum ContentStatus {
|
||||
Fetching(FetchControl),
|
||||
Fetching(Arc<FetchControl>),
|
||||
Ready(LocalPageEndpoint),
|
||||
}
|
||||
|
||||
|
||||
440
dapps/src/apps/fetcher.rs
Normal file
440
dapps/src/apps/fetcher.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
// 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/>.
|
||||
|
||||
//! Fetchable Dapps support.
|
||||
//! Manages downloaded (cached) Dapps and downloads them when necessary.
|
||||
//! Uses `URLHint` to resolve addresses into Dapps bundle file location.
|
||||
|
||||
use zip;
|
||||
use std::{fs, env, fmt};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
|
||||
use hyper;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use random_filename;
|
||||
use SyncStatus;
|
||||
use util::{Mutex, H256};
|
||||
use util::sha3::sha3;
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
use apps::cache::{ContentCache, ContentStatus};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
|
||||
use apps::urlhint::{URLHintContract, URLHint, URLHintResult};
|
||||
|
||||
/// Limit of cached dapps/content
|
||||
const MAX_CACHED_DAPPS: usize = 20;
|
||||
|
||||
pub struct ContentFetcher<R: URLHint = URLHintContract> {
|
||||
dapps_path: PathBuf,
|
||||
resolver: R,
|
||||
cache: Arc<Mutex<ContentCache>>,
|
||||
sync: Arc<SyncStatus>,
|
||||
embeddable_at: Option<u16>,
|
||||
}
|
||||
|
||||
impl<R: URLHint> Drop for ContentFetcher<R> {
|
||||
fn drop(&mut self) {
|
||||
// Clear cache path
|
||||
let _ = fs::remove_dir_all(&self.dapps_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint> ContentFetcher<R> {
|
||||
|
||||
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_at: Option<u16>) -> Self {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
ContentFetcher {
|
||||
dapps_path: dapps_path,
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_at: embeddable_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn still_syncing(port: Option<u16>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Sync In Progress",
|
||||
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
|
||||
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
|
||||
port,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
||||
self.cache.lock().insert(content_id.to_owned(), status);
|
||||
}
|
||||
|
||||
pub 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() {
|
||||
// else try to resolve the app_id
|
||||
let has_content = self.resolver.resolve(content_id).is_some();
|
||||
// if there is content or we are syncing return true
|
||||
has_content || self.sync.is_major_importing()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub 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)) => {
|
||||
trace!(target: "dapps", "Content fetching in progress. Waiting...");
|
||||
(None, fetch_control.to_handler(control))
|
||||
},
|
||||
// We need to start fetching the content
|
||||
None => {
|
||||
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.resolver.resolve(content_hex);
|
||||
|
||||
let cache = self.cache.clone();
|
||||
let on_done = move |id: String, result: Option<LocalPageEndpoint>| {
|
||||
let mut cache = cache.lock();
|
||||
match result {
|
||||
Some(endpoint) => {
|
||||
cache.insert(id, 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_at))
|
||||
},
|
||||
Some(URLHintResult::Dapp(dapp)) => {
|
||||
let (handler, fetch_control) = ContentFetcherHandler::new(
|
||||
dapp.url(),
|
||||
control,
|
||||
DappInstaller {
|
||||
id: content_id.clone(),
|
||||
dapps_path: self.dapps_path.clone(),
|
||||
on_done: Box::new(on_done),
|
||||
embeddable_at: self.embeddable_at,
|
||||
},
|
||||
self.embeddable_at,
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
|
||||
},
|
||||
Some(URLHintResult::Content(content)) => {
|
||||
let (handler, fetch_control) = ContentFetcherHandler::new(
|
||||
content.url,
|
||||
control,
|
||||
ContentInstaller {
|
||||
id: content_id.clone(),
|
||||
mime: content.mime,
|
||||
content_path: self.dapps_path.clone(),
|
||||
on_done: Box::new(on_done),
|
||||
},
|
||||
self.embeddable_at,
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
|
||||
},
|
||||
None if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_at))
|
||||
},
|
||||
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_at,
|
||||
)) as Box<Handler>)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(status) = new_status {
|
||||
cache.clear_garbage(MAX_CACHED_DAPPS);
|
||||
cache.insert(content_id, status);
|
||||
}
|
||||
|
||||
handler
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentInstaller {
|
||||
id: String,
|
||||
mime: String,
|
||||
content_path: PathBuf,
|
||||
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
|
||||
}
|
||||
|
||||
impl ContentValidator for ContentInstaller {
|
||||
type Error = ValidationError;
|
||||
|
||||
fn validate_and_install(&self, path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
|
||||
// Create dir
|
||||
try!(fs::create_dir_all(&self.content_path));
|
||||
|
||||
// Validate hash
|
||||
let mut file_reader = io::BufReader::new(try!(fs::File::open(&path)));
|
||||
let hash = try!(sha3(&mut file_reader));
|
||||
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
|
||||
if id != hash {
|
||||
return Err(ValidationError::HashMismatch {
|
||||
expected: id,
|
||||
got: hash,
|
||||
});
|
||||
}
|
||||
|
||||
// And prepare path for a file
|
||||
let filename = path.file_name().expect("We always fetch a file.");
|
||||
let mut content_path = self.content_path.clone();
|
||||
content_path.push(&filename);
|
||||
|
||||
if content_path.exists() {
|
||||
try!(fs::remove_dir_all(&content_path))
|
||||
}
|
||||
|
||||
try!(fs::copy(&path, &content_path));
|
||||
|
||||
Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)))
|
||||
}
|
||||
|
||||
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
|
||||
(self.on_done)(self.id.clone(), endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DappInstaller {
|
||||
id: String,
|
||||
dapps_path: PathBuf,
|
||||
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
|
||||
embeddable_at: Option<u16>,
|
||||
}
|
||||
|
||||
impl DappInstaller {
|
||||
fn find_manifest(zip: &mut zip::ZipArchive<fs::File>) -> Result<(Manifest, PathBuf), ValidationError> {
|
||||
for i in 0..zip.len() {
|
||||
let mut file = try!(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)
|
||||
}
|
||||
|
||||
fn dapp_target_path(&self, manifest: &Manifest) -> PathBuf {
|
||||
let mut target = self.dapps_path.clone();
|
||||
target.push(&manifest.id);
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentValidator for DappInstaller {
|
||||
type Error = ValidationError;
|
||||
|
||||
fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
|
||||
trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path);
|
||||
let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path)));
|
||||
let hash = try!(sha3(&mut file_reader));
|
||||
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
|
||||
if id != hash {
|
||||
return Err(ValidationError::HashMismatch {
|
||||
expected: id,
|
||||
got: hash,
|
||||
});
|
||||
}
|
||||
let file = file_reader.into_inner();
|
||||
// Unpack archive
|
||||
let mut zip = try!(zip::ZipArchive::new(file));
|
||||
// First find manifest file
|
||||
let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip));
|
||||
// Overwrite id to match hash
|
||||
manifest.id = self.id.clone();
|
||||
|
||||
let target = self.dapp_target_path(&manifest);
|
||||
|
||||
// Remove old directory
|
||||
if target.exists() {
|
||||
warn!(target: "dapps", "Overwriting existing dapp: {}", manifest.id);
|
||||
try!(fs::remove_dir_all(target.clone()));
|
||||
}
|
||||
|
||||
// Unpack zip
|
||||
for i in 0..zip.len() {
|
||||
let mut file = try!(zip.by_index(i));
|
||||
// TODO [todr] Check if it's consistent on windows.
|
||||
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 = target.join(location_in_manifest_base);
|
||||
// Check if it's a directory
|
||||
if is_dir {
|
||||
try!(fs::create_dir_all(p));
|
||||
} else {
|
||||
let mut target = try!(fs::File::create(p));
|
||||
try!(io::copy(&mut file, &mut target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write manifest
|
||||
let manifest_str = try!(serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization));
|
||||
let manifest_path = target.join(MANIFEST_FILENAME);
|
||||
let mut manifest_file = try!(fs::File::create(manifest_path));
|
||||
try!(manifest_file.write_all(manifest_str.as_bytes()));
|
||||
|
||||
// Create endpoint
|
||||
let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at);
|
||||
|
||||
// Return modified app manifest
|
||||
Ok((manifest.id.clone(), app))
|
||||
}
|
||||
|
||||
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
|
||||
(self.on_done)(self.id.clone(), endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use util::Bytes;
|
||||
use endpoint::EndpointInfo;
|
||||
use page::LocalPageEndpoint;
|
||||
use apps::cache::ContentStatus;
|
||||
use apps::urlhint::{URLHint, URLHintResult};
|
||||
use super::ContentFetcher;
|
||||
|
||||
struct FakeResolver;
|
||||
impl URLHint for FakeResolver {
|
||||
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_true_if_contains_the_app() {
|
||||
// given
|
||||
let path = env::temp_dir();
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None);
|
||||
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,255 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use 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};
|
||||
|
||||
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: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Dapp {
|
||||
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Dapp {
|
||||
id: id,
|
||||
dapps_path: dapps_path,
|
||||
on_done: on_done,
|
||||
embeddable_on: 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,265 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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_serialize::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 {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 + Send + Sync + 'static = URLHintContract> {
|
||||
dapps_path: PathBuf,
|
||||
resolver: R,
|
||||
cache: Arc<Mutex<ContentCache>>,
|
||||
sync: Arc<SyncStatus>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
}
|
||||
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
|
||||
fn drop(&mut self) {
|
||||
// Clear cache path
|
||||
let _ = fs::remove_dir_all(&self.dapps_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> ContentFetcher<F, R> {
|
||||
|
||||
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
ContentFetcher {
|
||||
dapps_path: dapps_path,
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_on: embeddable_on,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
}
|
||||
}
|
||||
|
||||
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Sync In Progress",
|
||||
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
|
||||
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
|
||||
address,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_status(&self, content_id: &str, status: ContentStatus) {
|
||||
self.cache.lock().insert(content_id.to_owned(), status);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
|
||||
fn contains(&self, content_id: &str) -> bool {
|
||||
{
|
||||
let mut cache = self.cache.lock();
|
||||
// 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() {
|
||||
// else try to resolve the app_id
|
||||
let has_content = self.resolver.resolve(content_id).is_some();
|
||||
// if there is content or we are syncing return true
|
||||
has_content || self.sync.is_major_importing()
|
||||
} 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.resolver.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(dapp)) => {
|
||||
let handler = ContentFetcherHandler::new(
|
||||
dapp.url(),
|
||||
path,
|
||||
control,
|
||||
installers::Dapp::new(
|
||||
content_id.clone(),
|
||||
self.dapps_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.dapps_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 hash_fetch::urlhint::{URLHint, URLHintResult};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use apps::cache::ContentStatus;
|
||||
use endpoint::EndpointInfo;
|
||||
use page::LocalPageEndpoint;
|
||||
use super::{ContentFetcher, Fetcher};
|
||||
|
||||
struct FakeResolver;
|
||||
impl URLHint for FakeResolver {
|
||||
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_true_if_contains_the_app() {
|
||||
// given
|
||||
let path = env::temp_dir();
|
||||
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap());
|
||||
let handler = LocalPageEndpoint::new(path, EndpointInfo {
|
||||
name: "fake".into(),
|
||||
description: "".into(),
|
||||
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, 2016 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 @@
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
@@ -28,86 +28,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, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
|
||||
let path = path.as_ref().to_owned();
|
||||
path.canonicalize().ok().and_then(|path| {
|
||||
let name = path.file_name().and_then(|name| name.to_str());
|
||||
name.map(|name| {
|
||||
let dapp = local_dapp(name.into(), path.clone());
|
||||
(dapp.id, Box::new(LocalPageEndpoint::new(
|
||||
dapp.path, dapp.info, PageCache::Disabled, signer_address.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, signer_address: Option<(String, u16)>) -> Endpoints {
|
||||
let mut pages = Endpoints::new();
|
||||
for dapp in local_dapps(dapps_path.as_ref()) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.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() {
|
||||
@@ -128,6 +59,51 @@ 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_FILENAME);
|
||||
|
||||
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
|
||||
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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn local_endpoints(dapps_path: String, signer_port: Option<u16>) -> 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, PageCache::Disabled, signer_port))
|
||||
);
|
||||
}
|
||||
pages
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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,71 +14,49 @@
|
||||
// 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::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 {WebProxyTokens};
|
||||
|
||||
mod cache;
|
||||
mod fs;
|
||||
pub mod urlhint;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
|
||||
extern crate parity_ui;
|
||||
|
||||
pub const HOME_PAGE: &'static str = "home";
|
||||
pub const DAPPS_DOMAIN: &'static str = ".parity";
|
||||
pub const RPC_PATH: &'static str = "rpc";
|
||||
pub const API_PATH: &'static str = "api";
|
||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
||||
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 utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
pub fn all_endpoints<F: Fetch>(
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Endpoints {
|
||||
pub fn all_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoints {
|
||||
// fetch fs dapps at first to avoid overwriting builtins
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
|
||||
for path in extra_dapps {
|
||||
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
|
||||
pages.insert(id, endpoint);
|
||||
} else {
|
||||
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
|
||||
}
|
||||
}
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_port);
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone()));
|
||||
pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_port));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_port));
|
||||
|
||||
pages
|
||||
}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
|
||||
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
||||
Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port),
|
||||
Embeddable::No => PageEndpoint::new(T::default()),
|
||||
}));
|
||||
}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<(String, u16)>),
|
||||
Yes(Option<u16>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,12 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! URLHint Contract
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use mime::Mime;
|
||||
use mime_guess;
|
||||
|
||||
use ethabi::{Interface, Contract, Token};
|
||||
@@ -27,30 +24,15 @@ use util::{Address, Bytes, Hashable};
|
||||
|
||||
const COMMIT_LEN: usize = 20;
|
||||
|
||||
/// RAW Contract interface.
|
||||
/// Should execute transaction using current blockchain state.
|
||||
pub trait ContractClient: Send + Sync {
|
||||
/// Get registrar address
|
||||
fn registrar(&self) -> Result<Address, String>;
|
||||
/// Call Contract
|
||||
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
|
||||
}
|
||||
|
||||
/// Github-hosted dapp.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct GithubApp {
|
||||
/// Github Account
|
||||
pub account: String,
|
||||
/// Github Repository
|
||||
pub repo: String,
|
||||
/// Commit on Github
|
||||
pub commit: [u8;COMMIT_LEN],
|
||||
/// Dapp owner address
|
||||
pub owner: Address,
|
||||
}
|
||||
|
||||
impl GithubApp {
|
||||
/// Returns URL of this Github-hosted dapp package.
|
||||
pub fn url(&self) -> String {
|
||||
// Since https fetcher doesn't support redirections we use direct link
|
||||
// format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex())
|
||||
@@ -71,17 +53,22 @@ impl GithubApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash-Addressed Content
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Content {
|
||||
/// URL of the content
|
||||
pub url: String,
|
||||
/// MIME type of the content
|
||||
pub mime: Mime,
|
||||
/// Content owner address
|
||||
pub mime: String,
|
||||
pub owner: Address,
|
||||
}
|
||||
|
||||
/// RAW Contract interface.
|
||||
/// Should execute transaction using current blockchain state.
|
||||
pub trait ContractClient: Send + Sync {
|
||||
/// Get registrar address
|
||||
fn registrar(&self) -> Result<Address, String>;
|
||||
/// Call Contract
|
||||
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
|
||||
}
|
||||
|
||||
/// Result of resolving id to URL
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum URLHintResult {
|
||||
@@ -97,7 +84,6 @@ pub trait URLHint {
|
||||
fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
|
||||
}
|
||||
|
||||
/// `URLHintContract` API
|
||||
pub struct URLHintContract {
|
||||
urlhint: Contract,
|
||||
registrar: Contract,
|
||||
@@ -105,10 +91,9 @@ pub struct URLHintContract {
|
||||
}
|
||||
|
||||
impl URLHintContract {
|
||||
/// Creates new `URLHintContract`
|
||||
pub fn new(client: Arc<ContractClient>) -> Self {
|
||||
let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI");
|
||||
let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI");
|
||||
let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI");
|
||||
let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI");
|
||||
|
||||
URLHintContract {
|
||||
urlhint: Contract::new(urlhint),
|
||||
@@ -119,12 +104,12 @@ impl URLHintContract {
|
||||
|
||||
fn urlhint_address(&self) -> Option<Address> {
|
||||
let res = || {
|
||||
let get_address = self.registrar.function("getAddress".into()).map_err(as_string)?;
|
||||
let params = get_address.encode_call(
|
||||
let get_address = try!(self.registrar.function("getAddress".into()).map_err(as_string));
|
||||
let params = try!(get_address.encode_call(
|
||||
vec![Token::FixedBytes((*"githubhint".sha3()).to_vec()), Token::String("A".into())]
|
||||
).map_err(as_string)?;
|
||||
let output = self.client.call(self.client.registrar()?, params)?;
|
||||
let result = get_address.decode_output(output).map_err(as_string)?;
|
||||
).map_err(as_string));
|
||||
let output = try!(self.client.call(try!(self.client.registrar()), params));
|
||||
let result = try!(get_address.decode_output(output).map_err(as_string));
|
||||
|
||||
match result.get(0) {
|
||||
Some(&Token::Address(address)) if address != *Address::default() => Ok(address.into()),
|
||||
@@ -184,7 +169,7 @@ impl URLHintContract {
|
||||
|
||||
let commit = GithubApp::commit(&commit);
|
||||
if commit == Some(Default::default()) {
|
||||
let mime = guess_mime_type(&account_slash_repo).unwrap_or(mime!(Application/_));
|
||||
let mime = guess_mime_type(&account_slash_repo).unwrap_or("application/octet-stream".into());
|
||||
return Some(URLHintResult::Content(Content {
|
||||
url: account_slash_repo,
|
||||
mime: mime,
|
||||
@@ -236,7 +221,7 @@ impl URLHint for URLHintContract {
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_mime_type(url: &str) -> Option<Mime> {
|
||||
fn guess_mime_type(url: &str) -> Option<String> {
|
||||
const CONTENT_TYPE: &'static str = "content-type=";
|
||||
|
||||
let mut it = url.split('#');
|
||||
@@ -248,17 +233,22 @@ fn guess_mime_type(url: &str) -> Option<Mime> {
|
||||
for meta in metas.split('&') {
|
||||
let meta = meta.to_lowercase();
|
||||
if meta.starts_with(CONTENT_TYPE) {
|
||||
return meta[CONTENT_TYPE.len()..].parse().ok();
|
||||
return Some(meta[CONTENT_TYPE.len()..].to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
url.and_then(|url| {
|
||||
url.split('.').last()
|
||||
}).and_then(|extension| {
|
||||
mime_guess::get_mime_type_opt(extension)
|
||||
mime_guess::get_mime_type_str(extension).map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_guess_mime_type(url: &str) -> Option<String> {
|
||||
guess_mime_type(url)
|
||||
}
|
||||
|
||||
fn as_string<T: fmt::Debug>(e: T) -> String {
|
||||
format!("{:?}", e)
|
||||
}
|
||||
@@ -270,7 +260,6 @@ mod tests {
|
||||
use rustc_serialize::hex::FromHex;
|
||||
|
||||
use super::*;
|
||||
use super::guess_mime_type;
|
||||
use util::{Bytes, Address, Mutex, ToPretty};
|
||||
|
||||
struct FakeRegistrar {
|
||||
@@ -370,7 +359,7 @@ mod tests {
|
||||
// then
|
||||
assert_eq!(res, Some(URLHintResult::Content(Content {
|
||||
url: "https://ethcore.io/assets/images/ethcore-black-horizontal.png".into(),
|
||||
mime: mime!(Image/Png),
|
||||
mime: "image/png".into(),
|
||||
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
|
||||
})))
|
||||
}
|
||||
@@ -401,10 +390,10 @@ mod tests {
|
||||
let url5 = "https://ethcore.io/parity.png";
|
||||
|
||||
|
||||
assert_eq!(guess_mime_type(url1), None);
|
||||
assert_eq!(guess_mime_type(url2), Some(mime!(Image/Png)));
|
||||
assert_eq!(guess_mime_type(url3), Some(mime!(Image/Png)));
|
||||
assert_eq!(guess_mime_type(url4), Some(mime!(Image/Jpeg)));
|
||||
assert_eq!(guess_mime_type(url5), Some(mime!(Image/Png)));
|
||||
assert_eq!(test_guess_mime_type(url1), None);
|
||||
assert_eq!(test_guess_mime_type(url2), Some("image/png".into()));
|
||||
assert_eq!(test_guess_mime_type(url3), Some("image/png".into()));
|
||||
assert_eq!(test_guess_mime_type(url4), Some("image/jpeg".into()));
|
||||
assert_eq!(test_guess_mime_type(url5), Some("image/png".into()));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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,6 +16,7 @@
|
||||
|
||||
//! Simple Content Handler
|
||||
|
||||
use std::io::Write;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::mime::Mime;
|
||||
@@ -31,7 +32,7 @@ pub struct ContentHandler {
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
write_pos: usize,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
safe_to_embed_at_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
@@ -43,31 +44,31 @@ impl ContentHandler {
|
||||
Self::new(StatusCode::NotFound, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
|
||||
pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at)
|
||||
}
|
||||
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option<u16>) -> Self {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
version=version(),
|
||||
), embeddable_on)
|
||||
), embeddable_at)
|
||||
}
|
||||
|
||||
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, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option<u16>) -> Self {
|
||||
ContentHandler {
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0,
|
||||
safe_to_embed_on: embeddable_on,
|
||||
safe_to_embed_at_port: embeddable_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +85,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.clone());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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,56 +16,41 @@
|
||||
|
||||
//! Hyper Server Handler that fetches a file during a request (proxy).
|
||||
|
||||
use std::fmt;
|
||||
use std::{fs, fmt};
|
||||
use std::path::PathBuf;
|
||||
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 url::Url;
|
||||
use fetch::{Client, Fetch, FetchResult};
|
||||
|
||||
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 handlers::{ContentHandler, Redirection, extract_url};
|
||||
use page::LocalPageEndpoint;
|
||||
|
||||
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>;
|
||||
}
|
||||
const FETCH_TIMEOUT: u64 = 30;
|
||||
|
||||
enum FetchState {
|
||||
Waiting,
|
||||
NotStarted(String),
|
||||
Error(ContentHandler),
|
||||
InProgress(mpsc::Receiver<FetchState>),
|
||||
Streaming(StreamingHandler<fetch::Response>),
|
||||
Done(LocalPageEndpoint, Box<PageHandlerWaiting>),
|
||||
InProgress(mpsc::Receiver<FetchResult>),
|
||||
Done(String, LocalPageEndpoint, Redirection),
|
||||
}
|
||||
|
||||
enum WaitResult {
|
||||
Error(ContentHandler),
|
||||
Done(LocalPageEndpoint),
|
||||
NonAwaitable,
|
||||
pub trait ContentValidator {
|
||||
type Error: fmt::Debug + fmt::Display;
|
||||
|
||||
fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>;
|
||||
fn done(&self, Option<LocalPageEndpoint>);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FetchControl {
|
||||
abort: Arc<AtomicBool>,
|
||||
listeners: Arc<Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>>,
|
||||
listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
@@ -73,17 +58,16 @@ impl Default for FetchControl {
|
||||
fn default() -> Self {
|
||||
FetchControl {
|
||||
abort: Arc::new(AtomicBool::new(false)),
|
||||
listeners: Arc::new(Mutex::new(Vec::new())),
|
||||
listeners: Mutex::new(Vec::new()),
|
||||
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchControl {
|
||||
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
|
||||
fn notify<F: Fn() -> FetchState>(&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 {
|
||||
@@ -94,246 +78,148 @@ impl FetchControl {
|
||||
|
||||
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 => {},
|
||||
FetchState::Error(ref handler) => self.notify(|| FetchState::Error(handler.clone())),
|
||||
FetchState::Done(ref id, ref endpoint, ref handler) => self.notify(|| FetchState::Done(id.clone(), endpoint.clone(), handler.clone())),
|
||||
FetchState::NotStarted(_) | FetchState::InProgress(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
pub fn to_handler(&self, 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,
|
||||
state: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitingHandler {
|
||||
receiver: mpsc::Receiver<WaitResult>,
|
||||
state: FetchState,
|
||||
uri: RequestUri,
|
||||
path: EndpointPath,
|
||||
receiver: mpsc::Receiver<FetchState>,
|
||||
state: Option<FetchState>,
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for WaitingHandler {
|
||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||
self.uri = request.uri().clone();
|
||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
||||
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_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
self.state = self.receiver.try_recv().ok();
|
||||
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),
|
||||
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response(res),
|
||||
Some(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),
|
||||
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response_writable(encoder),
|
||||
Some(FetchState::Error(ref mut handler)) => handler.on_response_writable(encoder),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Errors {
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
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,
|
||||
pub struct ContentFetcherHandler<H: ContentValidator> {
|
||||
fetch_control: Arc<FetchControl>,
|
||||
control: Option<Control>,
|
||||
status: FetchState,
|
||||
fetch: F,
|
||||
installer: Option<H>,
|
||||
path: EndpointPath,
|
||||
errors: Errors,
|
||||
client: Option<Client>,
|
||||
installer: H,
|
||||
request_url: Option<Url>,
|
||||
embeddable_at: Option<u16>,
|
||||
}
|
||||
|
||||
impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
|
||||
impl<H: ContentValidator> Drop for ContentFetcherHandler<H> {
|
||||
fn drop(&mut self) {
|
||||
let result = match self.status {
|
||||
FetchState::Done(_, ref result, _) => Some(result.clone()),
|
||||
_ => None,
|
||||
};
|
||||
self.installer.done(result);
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: ContentValidator> ContentFetcherHandler<H> {
|
||||
|
||||
pub fn new(
|
||||
url: String,
|
||||
path: EndpointPath,
|
||||
control: Control,
|
||||
installer: H,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
ContentFetcherHandler {
|
||||
fetch_control: FetchControl::default(),
|
||||
control: control,
|
||||
remote: remote,
|
||||
fetch: fetch,
|
||||
handler: H,
|
||||
embeddable_at: Option<u16>,
|
||||
) -> (Self, Arc<FetchControl>) {
|
||||
|
||||
let fetch_control = Arc::new(FetchControl::default());
|
||||
let client = Client::default();
|
||||
let handler = ContentFetcherHandler {
|
||||
fetch_control: fetch_control.clone(),
|
||||
control: Some(control),
|
||||
client: Some(client),
|
||||
status: FetchState::NotStarted(url),
|
||||
installer: Some(installer),
|
||||
path: path,
|
||||
errors: Errors {
|
||||
embeddable_on: embeddable_on,
|
||||
},
|
||||
}
|
||||
installer: handler,
|
||||
request_url: None,
|
||||
embeddable_at: embeddable_at,
|
||||
};
|
||||
|
||||
(handler, fetch_control)
|
||||
}
|
||||
|
||||
pub fn fetch_control(&self) -> FetchControl {
|
||||
self.fetch_control.clone()
|
||||
fn close_client(client: &mut Option<Client>) {
|
||||
client.take()
|
||||
.expect("After client is closed we are going into write, hence we can never close it again")
|
||||
.close();
|
||||
}
|
||||
|
||||
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);
|
||||
fn fetch_content(client: &mut Client, url: &str, abort: Arc<AtomicBool>, control: Control) -> Result<mpsc::Receiver<FetchResult>, String> {
|
||||
client.request(url, abort, Box::new(move || {
|
||||
trace!(target: "dapps", "Fetching finished.");
|
||||
// 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
|
||||
})).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetcherHandler<H, F> {
|
||||
impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<H> {
|
||||
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
|
||||
self.request_url = extract_url(&request);
|
||||
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)
|
||||
let control = self.control.take().expect("on_request is called only once, thus control is always Some");
|
||||
let client = self.client.as_mut().expect("on_request is called before client is closed.");
|
||||
let fetch = Self::fetch_content(client, url, self.fetch_control.abort.clone(), control);
|
||||
match fetch {
|
||||
Ok(receiver) => FetchState::InProgress(receiver),
|
||||
Err(e) => FetchState::Error(ContentHandler::error(
|
||||
StatusCode::BadGateway,
|
||||
"Unable To Start Dapp Download",
|
||||
"Could not initialize download of the dapp. It might be a problem with the remote server.",
|
||||
Some(&format!("{}", e)),
|
||||
self.embeddable_at,
|
||||
)),
|
||||
}
|
||||
},
|
||||
// or return error
|
||||
_ => FetchState::Error(self.errors.method_not_allowed()),
|
||||
_ => FetchState::Error(ContentHandler::error(
|
||||
StatusCode::MethodNotAllowed,
|
||||
"Method Not Allowed",
|
||||
"Only <code>GET</code> requests are allowed.",
|
||||
None,
|
||||
self.embeddable_at,
|
||||
)),
|
||||
})
|
||||
} else { None };
|
||||
|
||||
@@ -348,16 +234,60 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
|
||||
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() => {
|
||||
FetchState::InProgress(_) if self.fetch_control.deadline < Instant::now() => {
|
||||
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
|
||||
(Some(FetchState::Error(self.errors.timeout_error())), Next::write())
|
||||
let timeout = ContentHandler::error(
|
||||
StatusCode::GatewayTimeout,
|
||||
"Download Timeout",
|
||||
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
|
||||
None,
|
||||
self.embeddable_at,
|
||||
);
|
||||
Self::close_client(&mut self.client);
|
||||
(Some(FetchState::Error(timeout)), 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()),
|
||||
// Unpack and validate
|
||||
Ok(Ok(path)) => {
|
||||
trace!(target: "dapps", "Fetching content finished. Starting validation ({:?})", path);
|
||||
Self::close_client(&mut self.client);
|
||||
// Unpack and verify
|
||||
let state = match self.installer.validate_and_install(path.clone()) {
|
||||
Err(e) => {
|
||||
trace!(target: "dapps", "Error while validating content: {:?}", e);
|
||||
FetchState::Error(ContentHandler::error(
|
||||
StatusCode::BadGateway,
|
||||
"Invalid Dapp",
|
||||
"Downloaded bundle does not contain a valid content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_at,
|
||||
))
|
||||
},
|
||||
Ok((id, result)) => {
|
||||
let url: String = self.request_url.take()
|
||||
.map(|url| url.raw.into_string())
|
||||
.expect("Request URL always read in on_request; qed");
|
||||
FetchState::Done(id, result, Redirection::new(&url))
|
||||
},
|
||||
};
|
||||
// Remove temporary zip file
|
||||
let _ = fs::remove_file(path);
|
||||
(Some(state), Next::write())
|
||||
},
|
||||
Ok(Err(e)) => {
|
||||
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
|
||||
let error = ContentHandler::error(
|
||||
StatusCode::BadGateway,
|
||||
"Download Error",
|
||||
"There was an error when fetching the content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_at,
|
||||
);
|
||||
(Some(FetchState::Error(error)), Next::write())
|
||||
},
|
||||
// wait some more
|
||||
_ => (None, Next::wait())
|
||||
}
|
||||
@@ -376,8 +306,7 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
|
||||
|
||||
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::Done(_, _, ref mut handler) => handler.on_response(res),
|
||||
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||
_ => Next::end(),
|
||||
}
|
||||
@@ -385,8 +314,7 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
|
||||
|
||||
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::Done(_, _, 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, 2016 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,33 +17,31 @@
|
||||
//! Hyper handlers implementations.
|
||||
|
||||
mod auth;
|
||||
mod content;
|
||||
mod echo;
|
||||
mod fetch;
|
||||
mod content;
|
||||
mod redirect;
|
||||
mod streaming;
|
||||
mod fetch;
|
||||
|
||||
pub use self::auth::AuthRequiredHandler;
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::echo::EchoHandler;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::redirect::Redirection;
|
||||
pub use self::streaming::StreamingHandler;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
|
||||
|
||||
use url::Url;
|
||||
use hyper::{server, header, net, uri};
|
||||
use address;
|
||||
use signer_address;
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) {
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option<u16>) {
|
||||
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
|
||||
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
|
||||
|
||||
// Embedding header:
|
||||
if let Some(embeddable_on) = embeddable_on {
|
||||
if let Some(port) = embeddable_at {
|
||||
headers.set_raw(
|
||||
"X-Frame-Options",
|
||||
vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
|
||||
vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()]
|
||||
);
|
||||
} else {
|
||||
// TODO [ToDr] Should we be more strict here (DENY?)?
|
||||
@@ -51,30 +49,20 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 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,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,101 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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;
|
||||
|
||||
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: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl<R: io::Read> StreamingHandler<R> {
|
||||
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> 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.clone());
|
||||
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()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
165
dapps/src/lib.rs
165
dapps/src/lib.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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
|
||||
@@ -51,19 +51,16 @@ extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate zip;
|
||||
extern crate rand;
|
||||
extern crate ethabi;
|
||||
extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
extern crate mime_guess;
|
||||
extern crate rustc_serialize;
|
||||
extern crate ethcore_rpc;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate parity_hash_fetch as hash_fetch;
|
||||
extern crate linked_hash_map;
|
||||
extern crate fetch;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
extern crate futures;
|
||||
extern crate parity_reactor;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
@@ -84,21 +81,18 @@ mod rpc;
|
||||
mod api;
|
||||
mod proxypac;
|
||||
mod url;
|
||||
mod web;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
pub use self::apps::urlhint::ContractClient;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hash_fetch::urlhint::ContractClient;
|
||||
use fetch::{Fetch, Client as FetchClient};
|
||||
use jsonrpc_core::{IoHandler, IoDelegate};
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use ethcore_rpc::Extendable;
|
||||
use parity_reactor::Remote;
|
||||
|
||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
|
||||
@@ -112,30 +106,16 @@ impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
|
||||
fn is_major_importing(&self) -> bool { self() }
|
||||
}
|
||||
|
||||
/// Validates Web Proxy tokens
|
||||
pub trait WebProxyTokens: Send + Sync {
|
||||
/// Should return true if token is a valid web proxy access token.
|
||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool;
|
||||
}
|
||||
|
||||
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool { self(token.to_owned()) }
|
||||
}
|
||||
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
pub struct ServerBuilder {
|
||||
dapps_path: String,
|
||||
handler: Arc<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
remote: Remote,
|
||||
fetch: Option<T>,
|
||||
signer_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl<T: Fetch> Extendable for ServerBuilder<T> {
|
||||
impl Extendable for ServerBuilder {
|
||||
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
||||
self.handler.add_delegate(delegate);
|
||||
}
|
||||
@@ -143,105 +123,55 @@ impl<T: Fetch> Extendable for ServerBuilder<T> {
|
||||
|
||||
impl ServerBuilder {
|
||||
/// Construct new dapps server
|
||||
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
|
||||
pub fn new(dapps_path: String, registrar: Arc<ContractClient>) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path.as_ref().to_owned(),
|
||||
extra_dapps: vec![],
|
||||
dapps_path: dapps_path,
|
||||
handler: Arc::new(IoHandler::new()),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(|| false),
|
||||
web_proxy_tokens: Arc::new(|_| false),
|
||||
signer_address: None,
|
||||
remote: remote,
|
||||
fetch: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Fetch> ServerBuilder<T> {
|
||||
/// Set a fetch client to use.
|
||||
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
|
||||
ServerBuilder {
|
||||
dapps_path: self.dapps_path,
|
||||
extra_dapps: vec![],
|
||||
handler: self.handler,
|
||||
registrar: self.registrar,
|
||||
sync_status: self.sync_status,
|
||||
web_proxy_tokens: self.web_proxy_tokens,
|
||||
signer_address: self.signer_address,
|
||||
remote: self.remote,
|
||||
fetch: Some(fetch),
|
||||
signer_port: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change default sync status.
|
||||
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
|
||||
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
|
||||
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 extra dapps paths (apart from `dapps_path`)
|
||||
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
|
||||
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
|
||||
self
|
||||
pub fn with_signer_port(&mut self, signer_port: Option<u16>) {
|
||||
self.signer_port = signer_port;
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
NoAuth,
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.extra_dapps.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
self.web_proxy_tokens.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch_client()?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||
/// return result with `Server` handle on success or an error.
|
||||
pub fn start_basic_auth_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||
pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.extra_dapps.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
self.web_proxy_tokens.clone(),
|
||||
self.remote.clone(),
|
||||
self.fetch_client()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn fetch_client(&self) -> Result<T, ServerError> {
|
||||
match self.fetch.clone() {
|
||||
Some(fetch) => Ok(fetch),
|
||||
None => T::new().map_err(|_| ServerError::FetchInitialization),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapps HTTP server.
|
||||
@@ -267,52 +197,35 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
||||
match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
fn cors_domains(signer_port: Option<u16>) -> Vec<String> {
|
||||
match signer_port {
|
||||
Some(port) => vec![
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}", address(signer_address)),
|
||||
format!("http://{}", signer_address(port)),
|
||||
],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http<A: Authorization + 'static, F: Fetch>(
|
||||
fn start_http<A: Authorization + 'static>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: PathBuf,
|
||||
extra_dapps: Vec<PathBuf>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
dapps_path: String,
|
||||
signer_port: Option<u16>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
remote: Remote,
|
||||
fetch: F,
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status,
|
||||
signer_address.clone(),
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
));
|
||||
let endpoints = Arc::new(apps::all_endpoints(
|
||||
dapps_path,
|
||||
extra_dapps,
|
||||
signer_address.clone(),
|
||||
web_proxy_tokens,
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
));
|
||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port));
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone()));
|
||||
let cors_domains = Self::cors_domains(signer_port);
|
||||
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Api,
|
||||
@@ -322,10 +235,10 @@ impl Server {
|
||||
});
|
||||
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
|
||||
|
||||
hyper::Server::http(addr)?
|
||||
try!(hyper::Server::http(addr))
|
||||
.handle(move |ctrl| router::Router::new(
|
||||
ctrl,
|
||||
signer_address.clone(),
|
||||
signer_port.clone(),
|
||||
content_fetcher.clone(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
@@ -354,11 +267,7 @@ impl Server {
|
||||
#[cfg(test)]
|
||||
/// Returns address that this server is bound to.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
self.server.as_ref()
|
||||
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
|
||||
.addrs()
|
||||
.first()
|
||||
.expect("You cannot start the server without binding to at least one address; qed")
|
||||
self.server.as_ref().expect("server is always Some at the start; it's consumed only when object is dropped; qed").addr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,8 +284,6 @@ pub enum ServerError {
|
||||
IoError(std::io::Error),
|
||||
/// Other `hyper` error
|
||||
Other(hyper::error::Error),
|
||||
/// Fetch service initialization error
|
||||
FetchInitialization,
|
||||
}
|
||||
|
||||
impl From<hyper::error::Error> for ServerError {
|
||||
@@ -389,14 +296,14 @@ impl From<hyper::error::Error> for ServerError {
|
||||
}
|
||||
|
||||
/// Random filename
|
||||
fn random_filename() -> String {
|
||||
pub fn random_filename() -> String {
|
||||
use ::rand::Rng;
|
||||
let mut rng = ::rand::OsRng::new().unwrap();
|
||||
rng.gen_ascii_chars().take(12).collect()
|
||||
}
|
||||
|
||||
fn address(address: (String, u16)) -> String {
|
||||
format!("{}:{}", address.0, address.1)
|
||||
fn signer_address(port: u16) -> String {
|
||||
format!("127.0.0.1:{}", port)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -425,7 +332,7 @@ mod util_tests {
|
||||
|
||||
// when
|
||||
let none = Server::cors_domains(None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
let some = Server::cors_domains(Some(18180));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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 @@ 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: Option<(String, u16)>,
|
||||
safe_to_embed_at_port: Option<u16>,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
safe_to_embed_at_port: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
safe_to_embed_at_port: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
/// 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: Option<(String, u16)>) -> Self {
|
||||
pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
safe_to_embed_at_port: port,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@@ -79,9 +79,9 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||
file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
|
||||
cache: PageCache::Disabled,
|
||||
safe_to_embed_on: self.safe_to_embed_on.clone(),
|
||||
safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::Write;
|
||||
use time::{self, Duration};
|
||||
|
||||
use hyper::header;
|
||||
@@ -59,13 +60,13 @@ pub enum ServedFile<T: Dapp> {
|
||||
}
|
||||
|
||||
impl<T: Dapp> ServedFile<T> {
|
||||
pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
|
||||
pub fn new(embeddable_at: Option<u16>) -> Self {
|
||||
ServedFile::Error(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested dapp resource was not found.",
|
||||
None,
|
||||
embeddable_on,
|
||||
embeddable_at,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -83,45 +84,24 @@ impl Default for PageCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// File currently being served (or `None` if file does not exist).
|
||||
pub file: ServedFile<T>,
|
||||
/// 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: Option<(String, u16)>,
|
||||
pub safe_to_embed_at_port: Option<u16>,
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Dapp> PageHandler<T> {
|
||||
fn extract_path(&self, path: &str) -> String {
|
||||
let app_id = &self.path.app_id;
|
||||
@@ -145,7 +125,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,
|
||||
}.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
@@ -174,7 +162,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
}
|
||||
|
||||
// Security headers:
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port);
|
||||
Next::write()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
@@ -258,7 +246,7 @@ fn should_extract_path_with_appid() {
|
||||
},
|
||||
file: ServedFile::new(None),
|
||||
cache: Default::default(),
|
||||
safe_to_embed_on: None,
|
||||
safe_to_embed_at_port: None,
|
||||
};
|
||||
|
||||
// when
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,73 +18,42 @@ use mime_guess;
|
||||
use std::io::{Seek, Read, SeekFrom};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::handler::{self, PageCache, PageHandlerWaiting};
|
||||
use page::handler::{self, PageCache};
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use mime::Mime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalPageEndpoint {
|
||||
path: PathBuf,
|
||||
mime: Option<Mime>,
|
||||
mime: Option<String>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
embeddable_at: Option<u16>,
|
||||
}
|
||||
|
||||
impl LocalPageEndpoint {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option<u16>) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: None,
|
||||
info: Some(info),
|
||||
cache: cache,
|
||||
embeddable_on: embeddable_on,
|
||||
embeddable_at: embeddable_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self {
|
||||
pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: Some(mime),
|
||||
info: None,
|
||||
cache: cache,
|
||||
embeddable_on: None,
|
||||
embeddable_at: 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for LocalPageEndpoint {
|
||||
@@ -94,9 +63,23 @@ impl Endpoint for LocalPageEndpoint {
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
if let Some(ref mime) = self.mime {
|
||||
Box::new(self.page_handler_with_mime(path, mime))
|
||||
Box::new(handler::PageHandler {
|
||||
app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: self.embeddable_at,
|
||||
cache: self.cache,
|
||||
})
|
||||
} else {
|
||||
Box::new(self.page_handler(path))
|
||||
Box::new(handler::PageHandler {
|
||||
app: LocalDapp { path: self.path.clone() },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: self.embeddable_at,
|
||||
cache: self.cache,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,5 @@ mod handler;
|
||||
|
||||
pub use self::local::LocalPageEndpoint;
|
||||
pub use self::builtin::PageEndpoint;
|
||||
pub use self::handler::{PageCache, PageHandlerWaiting};
|
||||
pub use self::handler::PageCache;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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
|
||||
@@ -19,24 +19,24 @@
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
use address;
|
||||
use signer_address;
|
||||
|
||||
pub struct ProxyPac {
|
||||
signer_address: Option<(String, u16)>,
|
||||
signer_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
pub fn boxed(signer_port: Option<u16>) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac {
|
||||
signer_address: signer_address
|
||||
signer_port: signer_port
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for ProxyPac {
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
let signer = self.signer_address.clone()
|
||||
.map(address)
|
||||
let signer = self.signer_port
|
||||
.map(signer_address)
|
||||
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
|
||||
|
||||
let content = format!(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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,17 +20,16 @@
|
||||
pub mod auth;
|
||||
mod host_validation;
|
||||
|
||||
use address;
|
||||
use std::cmp;
|
||||
use signer_address;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode};
|
||||
use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
use apps::{self, DAPPS_DOMAIN};
|
||||
use apps::fetcher::Fetcher;
|
||||
use apps::fetcher::ContentFetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use handlers::{self, Redirection, ContentHandler};
|
||||
use handlers::{Redirection, extract_url, ContentHandler};
|
||||
use self::auth::{Authorization, Authorized};
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
@@ -44,9 +43,9 @@ pub enum SpecialEndpoint {
|
||||
|
||||
pub struct Router<A: Authorization + 'static> {
|
||||
control: Option<Control>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
signer_port: Option<u16>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetch: Arc<Fetcher>,
|
||||
fetch: Arc<ContentFetcher>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
@@ -56,12 +55,11 @@ pub struct Router<A: Authorization + 'static> {
|
||||
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = handlers::extract_url(&req);
|
||||
let url = extract_url(&req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
let referer = extract_referer_endpoint(&req);
|
||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
||||
let is_get_request = *req.method() == hyper::Method::Get;
|
||||
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
@@ -85,41 +83,25 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
|
||||
|
||||
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
|
||||
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
|
||||
self.handler = match (endpoint.0, endpoint.1, referer) {
|
||||
// Handle invalid web requests that we can recover from
|
||||
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
|
||||
if referer.app_id == apps::WEB_PATH
|
||||
&& self.endpoints.contains_key(apps::WEB_PATH)
|
||||
&& !is_web_endpoint(path)
|
||||
=>
|
||||
{
|
||||
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
|
||||
// TODO [ToDr] Some nice util for this!
|
||||
let using_domain = if referer.using_dapps_domains { 0 } else { 1 };
|
||||
let len = cmp::min(referer_url.path.len(), using_domain + 3); // token + protocol + hostname
|
||||
let base = referer_url.path[..len].join("/");
|
||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
||||
Redirection::boxed(&format!("/{}/{}", base, requested))
|
||||
},
|
||||
self.handler = match endpoint {
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
|
||||
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
|
||||
trace!(target: "dapps", "Resolving to special endpoint.");
|
||||
self.special.get(endpoint)
|
||||
.expect("special known to contain key; qed")
|
||||
.to_async_handler(path.clone().unwrap_or_default(), control)
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to local/builtin dapp.");
|
||||
self.endpoints.get(&path.app_id)
|
||||
.expect("endpoints known to contain key; qed")
|
||||
.expect("special 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) => {
|
||||
(Some(ref path), _) if self.fetch.contains(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||
self.fetch.to_async_handler(path.clone(), control)
|
||||
},
|
||||
@@ -128,29 +110,29 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
// It should be safe to remove it in (near) future.
|
||||
//
|
||||
// 404 for non-existent content
|
||||
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
|
||||
(Some(ref path), _) if *req.method() == hyper::Method::Get && path.app_id != "home" => {
|
||||
trace!(target: "dapps", "Resolving to 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested content was not found.",
|
||||
None,
|
||||
self.signer_address.clone(),
|
||||
self.signer_port,
|
||||
))
|
||||
},
|
||||
// Redirect any other GET request to signer.
|
||||
_ if is_get_request => {
|
||||
if let Some(signer_address) = self.signer_address.clone() {
|
||||
_ if *req.method() == hyper::Method::Get => {
|
||||
if let Some(port) = self.signer_port {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
Redirection::boxed(&format!("http://{}", address(signer_address)))
|
||||
Redirection::boxed(&format!("http://{}", signer_address(port)))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
|
||||
self.signer_address.clone(),
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."),
|
||||
self.signer_port,
|
||||
))
|
||||
}
|
||||
},
|
||||
@@ -186,8 +168,8 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
impl<A: Authorization> Router<A> {
|
||||
pub fn new(
|
||||
control: Control,
|
||||
signer_address: Option<(String, u16)>,
|
||||
content_fetcher: Arc<Fetcher>,
|
||||
signer_port: Option<u16>,
|
||||
content_fetcher: Arc<ContentFetcher>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
@@ -199,7 +181,7 @@ impl<A: Authorization> Router<A> {
|
||||
.to_handler(EndpointPath::default());
|
||||
Router {
|
||||
control: Some(control),
|
||||
signer_address: signer_address,
|
||||
signer_port: signer_port,
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
@@ -210,41 +192,6 @@ impl<A: Authorization> Router<A> {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
|
||||
match *path {
|
||||
Some(ref path) if path.app_id == apps::WEB_PATH => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_referer_endpoint(req: &server::Request<HttpStream>) -> Option<(EndpointPath, Url)> {
|
||||
let referer = req.headers().get::<header::Referer>();
|
||||
|
||||
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
|
||||
url.and_then(|url| {
|
||||
let option = Some(url);
|
||||
extract_url_referer_endpoint(&option).or_else(|| {
|
||||
extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)> {
|
||||
let query = url.as_ref().and_then(|url| url.query.as_ref());
|
||||
match (url, query) {
|
||||
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
|
||||
let referer_url = format!("http://{}:{}/{}", url.host, url.port, &query[apps::URL_REFERER.len()..]);
|
||||
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
|
||||
|
||||
let referer_url = Url::parse(&referer_url).ok();
|
||||
extract_endpoint(&referer_url).0.map(|endpoint| {
|
||||
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
|
||||
107
dapps/src/rpc.rs
107
dapps/src/rpc.rs
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,22 @@
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use hyper;
|
||||
|
||||
use jsonrpc_core::{IoHandler, ResponseHandler, Request, Response};
|
||||
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin, RpcHandler};
|
||||
use jsonrpc_core::IoHandler;
|
||||
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
|
||||
pub fn rpc(
|
||||
handler: Arc<IoHandler>,
|
||||
cors_domains: Vec<String>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
) -> Box<Endpoint> {
|
||||
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: Arc::new(RpcMiddleware::new(handler)),
|
||||
handler: handler,
|
||||
panic_handler: panic_handler,
|
||||
cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
cors_domain: None,
|
||||
// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
|
||||
allowed_hosts: None,
|
||||
})
|
||||
}
|
||||
|
||||
struct RpcEndpoint {
|
||||
handler: Arc<RpcMiddleware>,
|
||||
handler: Arc<IoHandler>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
@@ -54,91 +49,3 @@ impl Endpoint for RpcEndpoint {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct RpcMiddleware {
|
||||
handler: Arc<IoHandler>,
|
||||
methods: Vec<String>,
|
||||
}
|
||||
|
||||
impl RpcMiddleware {
|
||||
fn new(handler: Arc<IoHandler>) -> Self {
|
||||
RpcMiddleware {
|
||||
handler: handler,
|
||||
methods: vec![
|
||||
"eth_accounts".into(),
|
||||
"eth_coinbase".into(),
|
||||
"parity_accountsInfo".into(),
|
||||
"parity_defaultAccount".into(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends additional parameter for specific calls.
|
||||
fn augment_request(&self, request: &mut Request, meta: Option<Meta>) {
|
||||
use jsonrpc_core::{Call, Params, to_value};
|
||||
|
||||
fn augment_call(call: &mut Call, meta: Option<&Meta>, methods: &Vec<String>) {
|
||||
match (call, meta) {
|
||||
(&mut Call::MethodCall(ref mut method_call), Some(meta)) if methods.contains(&method_call.method) => {
|
||||
let session = to_value(&meta.app_id);
|
||||
|
||||
let params = match method_call.params {
|
||||
Some(Params::Array(ref vec)) if vec.len() == 0 => Some(Params::Array(vec![session])),
|
||||
// invalid params otherwise
|
||||
_ => None,
|
||||
};
|
||||
|
||||
method_call.params = params;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match *request {
|
||||
Request::Single(ref mut call) => augment_call(call, meta.as_ref(), &self.methods),
|
||||
Request::Batch(ref mut vec) => {
|
||||
for mut call in vec {
|
||||
augment_call(call, meta.as_ref(), &self.methods)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Meta {
|
||||
app_id: String,
|
||||
}
|
||||
|
||||
impl RpcHandler for RpcMiddleware {
|
||||
type Metadata = Meta;
|
||||
|
||||
fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Option<Self::Metadata> {
|
||||
request.headers().get::<hyper::header::Referer>()
|
||||
.and_then(|referer| hyper::Url::parse(referer).ok())
|
||||
.and_then(|url| {
|
||||
url.path_segments()
|
||||
.and_then(|mut split| split.next())
|
||||
.map(|app_id| Meta {
|
||||
app_id: app_id.to_owned(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_request<H>(&self, request_str: &str, response_handler: H, meta: Option<Self::Metadata>) where
|
||||
H: ResponseHandler<Option<String>, Option<String>> + 'static
|
||||
{
|
||||
let handler = IoHandler::convert_handler(response_handler);
|
||||
let request = IoHandler::read_request(request_str);
|
||||
trace!(target: "rpc", "Request metadata: {:?}", meta);
|
||||
|
||||
match request {
|
||||
Ok(mut request) => {
|
||||
self.augment_request(&mut request, meta);
|
||||
self.handler.request_handler().handle_request(request, handler, None)
|
||||
},
|
||||
Err(error) => handler.send(Some(Response::from(error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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,13 +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 devtools::http_client;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use tests::helpers::{
|
||||
serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch,
|
||||
serve_with_registrar_and_fetch, serve_with_registrar_and_fetch_and_threads,
|
||||
request, assert_security_headers_for_embed,
|
||||
};
|
||||
use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_resolve_dapp() {
|
||||
@@ -38,7 +32,7 @@ fn should_resolve_dapp() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
@@ -47,6 +41,14 @@ fn should_resolve_dapp() {
|
||||
fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar_and_sync();
|
||||
{
|
||||
let mut responses = registrar.responses.lock();
|
||||
let res1 = responses.get(0).unwrap().clone();
|
||||
let res2 = responses.get(1).unwrap().clone();
|
||||
// Registrar will be called twice - fill up the responses.
|
||||
responses.push(res1);
|
||||
responses.push(res2);
|
||||
}
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
@@ -59,378 +61,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 503 Service Unavailable");
|
||||
assert_eq!(response.status, "HTTP/1.1 503 Service Unavailable".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
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.parity\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.parity\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.parity\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.parity\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.parity\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.parity\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.parity\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_stream_web_content() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /web/token/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
|
||||
fetch.assert_requested("https://parity.io/");
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_on_invalid_token() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /web/invalidtoken/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 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");
|
||||
|
||||
// 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_redirect_if_trailing_slash_is_missing() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /web/token/https/parity.io HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
response.assert_header("Location", "/web/token/https/parity.io/");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_non_get_requests() {
|
||||
// given
|
||||
let (server, fetch) = serve_with_fetch("token");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /token/https/parity.io/ HTTP/1.1\r\n\
|
||||
Host: web.parity\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
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");
|
||||
|
||||
// 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/token/https/parity.io/\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 302 Found");
|
||||
response.assert_header("Location", "/web/token/https/parity.io/styles.css");
|
||||
|
||||
fetch.assert_no_more_requests();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,46 +17,68 @@
|
||||
use std::env;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use env_logger::LogBuilder;
|
||||
|
||||
use ServerBuilder;
|
||||
use Server;
|
||||
use fetch::Fetch;
|
||||
use apps::urlhint::ContractClient;
|
||||
use util::{Bytes, Address, Mutex, ToPretty};
|
||||
use devtools::http_client;
|
||||
use parity_reactor::Remote;
|
||||
|
||||
mod registrar;
|
||||
mod fetch;
|
||||
|
||||
use self::registrar::FakeRegistrar;
|
||||
use self::fetch::FakeFetch;
|
||||
|
||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||
const SIGNER_PORT: u16 = 18180;
|
||||
|
||||
pub struct FakeRegistrar {
|
||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
||||
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
||||
}
|
||||
|
||||
impl FakeRegistrar {
|
||||
fn new() -> Self {
|
||||
FakeRegistrar {
|
||||
calls: Arc::new(Mutex::new(Vec::new())),
|
||||
responses: Mutex::new(
|
||||
vec![
|
||||
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
|
||||
Ok(Vec::new())
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContractClient for FakeRegistrar {
|
||||
fn registrar(&self) -> Result<Address, String> {
|
||||
Ok(REGISTRAR.parse().unwrap())
|
||||
}
|
||||
|
||||
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||
self.calls.lock().push((address.to_hex(), data.to_hex()));
|
||||
self.responses.lock().remove(0)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
builder.init().expect("Logger is initialized only once.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_server<F, B>(hosts: Option<Vec<String>>, process: F, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
|
||||
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||
B: Fetch,
|
||||
{
|
||||
pub fn init_server(hosts: Option<Vec<String>>, is_syncing: bool) -> (Server, Arc<FakeRegistrar>) {
|
||||
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(), hosts).unwrap();
|
||||
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
|
||||
builder.with_sync_status(Arc::new(move || is_syncing));
|
||||
builder.with_signer_port(Some(SIGNER_PORT));
|
||||
(
|
||||
server,
|
||||
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
|
||||
registrar,
|
||||
)
|
||||
}
|
||||
@@ -66,53 +88,25 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
|
||||
let registrar = Arc::new(FakeRegistrar::new());
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
ServerBuilder::new(&dapps_path, registrar.clone(), Remote::new_sync())
|
||||
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
|
||||
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
|
||||
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar);
|
||||
builder.with_signer_port(Some(SIGNER_PORT));
|
||||
builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
|
||||
}
|
||||
|
||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
|
||||
init_server(hosts, |builder| builder, Remote::new_sync()).0
|
||||
init_server(hosts, false).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(None, |builder| builder, Remote::new_sync())
|
||||
init_server(None, false)
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(None, |builder| {
|
||||
builder.sync_status(Arc::new(|| true))
|
||||
}, 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(None, move |builder| {
|
||||
builder.fetch(f.clone())
|
||||
}, if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
|
||||
|
||||
(server, fetch, reg)
|
||||
}
|
||||
|
||||
pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) {
|
||||
let fetch = FakeFetch::default();
|
||||
let f = fetch.clone();
|
||||
let (server, _) = init_server(None, move |builder| {
|
||||
builder
|
||||
.fetch(f.clone())
|
||||
.web_proxy_tokens(Arc::new(move |token| &token == web_token))
|
||||
}, Remote::new_sync());
|
||||
|
||||
(server, fetch)
|
||||
init_server(None, true)
|
||||
}
|
||||
|
||||
pub fn serve() -> Server {
|
||||
init_server(None, |builder| builder, Remote::new_sync()).0
|
||||
init_server(None, false).0
|
||||
}
|
||||
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
@@ -1,122 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{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.complete(fetch::Response::from_reader(cursor));
|
||||
});
|
||||
|
||||
rx.map_err(|_| fetch::Error::Aborted).boxed()
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use rustc_serialize::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) -> Result<Bytes, String> {
|
||||
let call = (address.to_hex(), data.to_hex());
|
||||
self.calls.lock().push(call.clone());
|
||||
self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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, 2016 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,
|
||||
|
||||
229
dapps/src/web.rs
229
dapps/src/web.rs
@@ -1,229 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Serving web-based content (proxying)
|
||||
|
||||
use std::sync::Arc;
|
||||
use fetch::{self, Fetch};
|
||||
use parity_reactor::Remote;
|
||||
|
||||
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, Redirection, extract_url,
|
||||
};
|
||||
use url::Url;
|
||||
use WebProxyTokens;
|
||||
|
||||
pub type Embeddable = Option<(String, u16)>;
|
||||
|
||||
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: embeddable_on,
|
||||
web_proxy_tokens: web_proxy_tokens,
|
||||
remote: remote,
|
||||
fetch: 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,
|
||||
&self.referer,
|
||||
));
|
||||
}
|
||||
Ok(ValidatorResponse::Streaming(handler))
|
||||
}
|
||||
}
|
||||
|
||||
enum State<F: Fetch> {
|
||||
Initial,
|
||||
Error(ContentHandler),
|
||||
Redirecting(Redirection),
|
||||
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, String), State<F>> {
|
||||
let (path, query) = match url {
|
||||
Some(url) => (url.path, url.query),
|
||||
None => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
// Support domain based routing.
|
||||
let idx = match path.get(0).map(|m| m.as_ref()) {
|
||||
Some(apps::WEB_PATH) => 1,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// Check if token supplied in URL is correct.
|
||||
match path.get(idx) {
|
||||
Some(ref token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate protocol
|
||||
let protocol = match path.get(idx + 1).map(|a| a.as_str()) {
|
||||
Some("http") => "http",
|
||||
Some("https") => "https",
|
||||
_ => {
|
||||
return Err(State::Error(ContentHandler::error(
|
||||
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
// Redirect if address to main page does not end with /
|
||||
if let None = path.get(idx + 3) {
|
||||
return Err(State::Redirecting(
|
||||
Redirection::new(&format!("/{}/", path.join("/")))
|
||||
));
|
||||
}
|
||||
|
||||
let query = match query {
|
||||
Some(query) => format!("?{}", query),
|
||||
None => "".into(),
|
||||
};
|
||||
|
||||
Ok((format!("{}://{}{}", protocol, path[idx + 2..].join("/"), query), path[0..].join("/")))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
let url = extract_url(&request);
|
||||
|
||||
// First extract the URL (reject invalid URLs)
|
||||
let (target_url, referer) = 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: referer,
|
||||
},
|
||||
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::Redirecting(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::Redirecting(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::Redirecting(ref mut handler) => handler.on_response_writable(encoder),
|
||||
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
[package]
|
||||
description = "Ethcore Parity UI"
|
||||
homepage = "http://parity.io"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "parity-ui"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.4.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.1"
|
||||
|
||||
[dependencies]
|
||||
parity-ui-dev = { path = "../../js", optional = true }
|
||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
|
||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true, branch = "beta" }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,21 +1,21 @@
|
||||
[package]
|
||||
description = "Ethcore Database"
|
||||
homepage = "http://parity.io"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-db"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.4.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
ethcore-ipc-codegen = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
semver = "0.5"
|
||||
semver = "0.2"
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
crossbeam = "0.2"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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
|
||||
@@ -73,10 +73,10 @@ impl WriteCache {
|
||||
|
||||
match *cache_entry {
|
||||
WriteCacheEntry::Write(ref val) => {
|
||||
batch.put(&key, val)?;
|
||||
try!(batch.put(&key, val));
|
||||
},
|
||||
WriteCacheEntry::Remove => {
|
||||
batch.delete(&key)?;
|
||||
try!(batch.delete(&key));
|
||||
},
|
||||
}
|
||||
key.clone()
|
||||
@@ -87,14 +87,14 @@ impl WriteCache {
|
||||
removed_so_far = removed_so_far + 1;
|
||||
}
|
||||
if removed_so_far > 0 {
|
||||
db.write(batch)?;
|
||||
try!(db.write(batch));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// flushes until cache is empty
|
||||
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
|
||||
while !self.is_empty() { self.flush(db, FLUSH_BATCH_SIZE)?; }
|
||||
while !self.is_empty() { try!(self.flush(db, FLUSH_BATCH_SIZE)); }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ impl WriteCache {
|
||||
|
||||
fn try_shrink(&mut self, db: &DB) -> Result<(), Error> {
|
||||
if self.entries.len() > self.preferred_len {
|
||||
self.flush(db, FLUSH_BATCH_SIZE)?;
|
||||
try!(self.flush(db, FLUSH_BATCH_SIZE));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -135,7 +135,7 @@ impl Database {
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().unwrap();
|
||||
|
||||
cache_lock.try_shrink(&db)?;
|
||||
try!(cache_lock.try_shrink(&db));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ impl Database {
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step");
|
||||
|
||||
cache_lock.flush_all(&db)?;
|
||||
try!(cache_lock.flush_all(&db));
|
||||
Ok(())
|
||||
|
||||
}
|
||||
@@ -174,7 +174,7 @@ impl DatabaseService for Database {
|
||||
opts.set_block_based_table_factory(&block_opts);
|
||||
opts.set_prefix_extractor_fixed_size(size);
|
||||
}
|
||||
*db = Some(DB::open(&opts, &path)?);
|
||||
*db = Some(try!(DB::open(&opts, &path)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -185,7 +185,7 @@ impl DatabaseService for Database {
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
self.flush_all()?;
|
||||
try!(self.flush_all());
|
||||
|
||||
let mut db = self.db.write();
|
||||
if db.is_none() { return Err(Error::IsClosed); }
|
||||
@@ -231,9 +231,9 @@ impl DatabaseService for Database {
|
||||
}
|
||||
}
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
match db.get(key)? {
|
||||
match try!(db.get(key)) {
|
||||
Some(db_vec) => {
|
||||
Ok(Some(db_vec.to_vec()))
|
||||
},
|
||||
@@ -243,7 +243,7 @@ impl DatabaseService for Database {
|
||||
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward));
|
||||
match iter.next() {
|
||||
@@ -255,14 +255,14 @@ impl DatabaseService for Database {
|
||||
|
||||
fn is_empty(&self) -> Result<bool, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
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 = db_lock.as_ref().ok_or(Error::IsClosed)?;
|
||||
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;
|
||||
@@ -270,7 +270,8 @@ impl DatabaseService for Database {
|
||||
Ok(next_iterator)
|
||||
}
|
||||
|
||||
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue> {
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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
|
||||
@@ -52,27 +52,27 @@ impl std::convert::From<nanoipc::SocketError> for ServiceError {
|
||||
|
||||
pub fn blocks_service_url(db_path: &str) -> Result<String, std::io::Error> {
|
||||
let mut path = PathBuf::from(db_path);
|
||||
::std::fs::create_dir_all(db_path)?;
|
||||
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);
|
||||
::std::fs::create_dir_all(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 = blocks_service_url(db_path)?;
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
|
||||
let url = try!(blocks_service_url(db_path));
|
||||
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = extras_service_url(db_path)?;
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
|
||||
let url = try!(extras_service_url(db_path));
|
||||
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,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.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.4.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,7 +14,6 @@
|
||||
// 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};
|
||||
@@ -27,21 +26,6 @@ pub struct Response {
|
||||
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 {
|
||||
@@ -58,29 +42,9 @@ pub fn read_block(lines: &mut Lines, all: bool) -> String {
|
||||
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();
|
||||
let mut req = TcpStream::connect(address).unwrap();
|
||||
req.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||
req.write_all(request.as_bytes()).unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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, 2016 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, 2016 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, 2016 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,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
|
||||
# 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/ethcore/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"]
|
||||
@@ -3,12 +3,12 @@ WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils
|
||||
binutils \
|
||||
make
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends g++ gcc libc6 libc6-dev \
|
||||
python binutils curl git make file ca-certificates zip dpkg-dev rhash openssl build-essential pkg-config libssl-dev
|
||||
|
||||
# install AWS CLI
|
||||
RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
|
||||
python get-pip.py && \
|
||||
pip install awscli
|
||||
|
||||
# install nodejs
|
||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
apt-get clean
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils \
|
||||
make
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -35,7 +30,7 @@ RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
cargo build --features final --release --verbose && \
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ethash"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.4.0"
|
||||
authors = ["arkpar <arkadiy@ethcore.io"]
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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
|
||||
@@ -116,17 +116,17 @@ impl Light {
|
||||
pub fn from_file(block_number: u64) -> io::Result<Light> {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let path = Light::file_path(seed_compute.get_seedhash(block_number));
|
||||
let mut file = File::open(path)?;
|
||||
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::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 {
|
||||
cache: nodes,
|
||||
block_number: block_number,
|
||||
@@ -144,16 +144,16 @@ impl Light {
|
||||
|
||||
if deprecated.exists() {
|
||||
debug!(target: "ethash", "removing: {:?}", &deprecated);
|
||||
fs::remove_file(deprecated)?;
|
||||
try!(fs::remove_file(deprecated));
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
let mut file = File::create(&path)?;
|
||||
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)?;
|
||||
try!(file.write(buf));
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,10 +1,10 @@
|
||||
[package]
|
||||
description = "Ethcore library"
|
||||
homepage = "http://parity.io"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
version = "1.4.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
@@ -21,15 +21,14 @@ crossbeam = "0.2.9"
|
||||
lazy_static = "0.2"
|
||||
bloomchain = "0.1"
|
||||
rayon = "0.4.2"
|
||||
semver = "0.5"
|
||||
semver = "0.2"
|
||||
bit-set = "0.4"
|
||||
time = "0.1"
|
||||
rand = "0.3"
|
||||
byteorder = "0.5"
|
||||
transient-hashmap = "0.1"
|
||||
linked-hash-map = "0.3.0"
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
ethash = { path = "../ethash" }
|
||||
ethcore-util = { path = "../util" }
|
||||
ethcore-io = { path = "../util/io" }
|
||||
@@ -42,7 +41,6 @@ ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
lru-cache = "0.1.0"
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
ethabi = "0.2.2"
|
||||
|
||||
[dependencies.hyper]
|
||||
git = "https://github.com/ethcore/hyper"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 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,26 +0,0 @@
|
||||
[package]
|
||||
description = "Parity LES primitives"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-light"
|
||||
version = "1.5.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
"ethcore-ipc-codegen" = { path = "../../ipc/codegen", optional = true }
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
ethcore = { path = ".."}
|
||||
ethcore-util = { path = "../../util" }
|
||||
ethcore-network = { path = "../../util/network" }
|
||||
ethcore-io = { path = "../../util/io" }
|
||||
ethcore-ipc = { path = "../../ipc/rpc", optional = true }
|
||||
rlp = { path = "../../util/rlp" }
|
||||
time = "0.1"
|
||||
smallvec = "0.3.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ipc = ["ethcore-ipc", "ethcore-ipc-codegen"]
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
extern crate ethcore_ipc_codegen;
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
fn main() {
|
||||
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/provider.rs", true).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ipc"))]
|
||||
fn main() { }
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
//! Canonical hash trie definitions and helper functions.
|
||||
|
||||
/// The size of each CHT.
|
||||
pub const SIZE: u64 = 2048;
|
||||
|
||||
/// Convert a block number to a CHT number.
|
||||
pub fn block_to_cht_number(block_num: u64) -> u64 {
|
||||
(block_num + 1) / SIZE
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client header chain.
|
||||
//!
|
||||
//! Unlike a full node's `BlockChain` this doesn't store much in the database.
|
||||
//! It stores candidates for the last 2048-4096 blocks as well as CHT roots for
|
||||
//! historical blocks all the way to the genesis.
|
||||
//!
|
||||
//! This is separate from the `BlockChain` for two reasons:
|
||||
//! - It stores only headers (and a pruned subset of them)
|
||||
//! - To allow for flexibility in the database layout once that's incorporated.
|
||||
// TODO: use DB instead of memory. DB Layout: just the contents of `candidates`/`headers`
|
||||
//
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use client::cht;
|
||||
|
||||
use ethcore::block_status::BlockStatus;
|
||||
use ethcore::error::BlockError;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::views::HeaderView;
|
||||
use util::{Bytes, H256, U256, HeapSizeOf, Mutex, RwLock};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Store at least this many candidate headers at all times.
|
||||
/// Also functions as the delay for computing CHTs as they aren't
|
||||
/// relevant to any blocks we've got in memory.
|
||||
const HISTORY: u64 = 2048;
|
||||
|
||||
/// Information about a block.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockDescriptor {
|
||||
/// The block's hash
|
||||
pub hash: H256,
|
||||
/// The block's number
|
||||
pub number: u64,
|
||||
/// The block's total difficulty.
|
||||
pub total_difficulty: U256,
|
||||
}
|
||||
|
||||
// candidate block description.
|
||||
struct Candidate {
|
||||
hash: H256,
|
||||
parent_hash: H256,
|
||||
total_difficulty: U256,
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
candidates: SmallVec<[Candidate; 3]>, // 3 arbitrarily chosen
|
||||
canonical_hash: H256,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for Entry {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match self.candidates.spilled() {
|
||||
false => 0,
|
||||
true => self.candidates.capacity() * ::std::mem::size_of::<Candidate>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Header chain. See module docs for more details.
|
||||
pub struct HeaderChain {
|
||||
genesis_header: Bytes, // special-case the genesis.
|
||||
candidates: RwLock<BTreeMap<u64, Entry>>,
|
||||
headers: RwLock<HashMap<H256, Bytes>>,
|
||||
best_block: RwLock<BlockDescriptor>,
|
||||
cht_roots: Mutex<Vec<H256>>,
|
||||
}
|
||||
|
||||
impl HeaderChain {
|
||||
/// Create a new header chain given this genesis block.
|
||||
pub fn new(genesis: &[u8]) -> Self {
|
||||
let g_view = HeaderView::new(genesis);
|
||||
|
||||
HeaderChain {
|
||||
genesis_header: genesis.to_owned(),
|
||||
best_block: RwLock::new(BlockDescriptor {
|
||||
hash: g_view.hash(),
|
||||
number: 0,
|
||||
total_difficulty: g_view.difficulty(),
|
||||
}),
|
||||
candidates: RwLock::new(BTreeMap::new()),
|
||||
headers: RwLock::new(HashMap::new()),
|
||||
cht_roots: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a pre-verified header.
|
||||
///
|
||||
/// This blindly trusts that the data given to it is
|
||||
/// a) valid RLP encoding of a header and
|
||||
/// b) has sensible data contained within it.
|
||||
pub fn insert(&self, header: Bytes) -> Result<(), BlockError> {
|
||||
let view = HeaderView::new(&header);
|
||||
let hash = view.hash();
|
||||
let number = view.number();
|
||||
let parent_hash = view.parent_hash();
|
||||
|
||||
// hold candidates the whole time to guard import order.
|
||||
let mut candidates = self.candidates.write();
|
||||
|
||||
// find parent details.
|
||||
let parent_td =
|
||||
if number == 1 {
|
||||
let g_view = HeaderView::new(&self.genesis_header);
|
||||
g_view.difficulty()
|
||||
} else {
|
||||
candidates.get(&(number - 1))
|
||||
.and_then(|entry| entry.candidates.iter().find(|c| c.hash == parent_hash))
|
||||
.map(|c| c.total_difficulty)
|
||||
.ok_or_else(|| BlockError::UnknownParent(parent_hash))?
|
||||
};
|
||||
|
||||
let total_difficulty = parent_td + view.difficulty();
|
||||
|
||||
// insert headers and candidates entries.
|
||||
candidates.entry(number).or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash})
|
||||
.candidates.push(Candidate {
|
||||
hash: hash,
|
||||
parent_hash: parent_hash,
|
||||
total_difficulty: total_difficulty,
|
||||
});
|
||||
|
||||
self.headers.write().insert(hash, header.clone());
|
||||
|
||||
// reorganize ancestors so canonical entries are first in their
|
||||
// respective candidates vectors.
|
||||
if self.best_block.read().total_difficulty < total_difficulty {
|
||||
let mut canon_hash = hash;
|
||||
for (_, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) {
|
||||
if entry.canonical_hash == canon_hash { break; }
|
||||
|
||||
let canon = entry.candidates.iter().find(|x| x.hash == canon_hash)
|
||||
.expect("blocks are only inserted if parent is present; or this is the block we just added; qed");
|
||||
|
||||
// what about reorgs > cht::SIZE + HISTORY?
|
||||
// resetting to the last block of a given CHT should be possible.
|
||||
canon_hash = canon.parent_hash;
|
||||
}
|
||||
|
||||
*self.best_block.write() = BlockDescriptor {
|
||||
hash: hash,
|
||||
number: number,
|
||||
total_difficulty: total_difficulty,
|
||||
};
|
||||
|
||||
// produce next CHT root if it's time.
|
||||
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
|
||||
if earliest_era + HISTORY + cht::SIZE <= number {
|
||||
let mut values = Vec::with_capacity(cht::SIZE as usize);
|
||||
{
|
||||
let mut headers = self.headers.write();
|
||||
for i in (0..cht::SIZE).map(|x| x + earliest_era) {
|
||||
let era_entry = candidates.remove(&i)
|
||||
.expect("all eras are sequential with no gaps; qed");
|
||||
|
||||
for ancient in &era_entry.candidates {
|
||||
headers.remove(&ancient.hash);
|
||||
}
|
||||
|
||||
values.push((
|
||||
::rlp::encode(&i).to_vec(),
|
||||
::rlp::encode(&era_entry.canonical_hash).to_vec(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let cht_root = ::util::triehash::trie_root(values);
|
||||
debug!(target: "chain", "Produced CHT {} root: {:?}", (earliest_era - 1) % cht::SIZE, cht_root);
|
||||
|
||||
self.cht_roots.lock().push(cht_root);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a block header. In the case of query by number, only canonical blocks
|
||||
/// will be returned.
|
||||
pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
match id {
|
||||
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
|
||||
BlockId::Latest if self.headers.read().is_empty() => Some(self.genesis_header.clone()),
|
||||
BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()),
|
||||
BlockId::Number(num) => {
|
||||
if self.best_block.read().number < num { return None }
|
||||
|
||||
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
|
||||
.and_then(|hash| self.headers.read().get(&hash).map(|x| x.to_vec()))
|
||||
}
|
||||
BlockId::Latest | BlockId::Pending => {
|
||||
let hash = self.best_block.read().hash;
|
||||
self.headers.read().get(&hash).map(|x| x.to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the nth CHT root, if it's been computed.
|
||||
///
|
||||
/// CHT root 0 is from block `1..2048`.
|
||||
/// CHT root 1 is from block `2049..4096`
|
||||
/// and so on.
|
||||
///
|
||||
/// This is because it's assumed that the genesis hash is known,
|
||||
/// so including it within a CHT would be redundant.
|
||||
pub fn cht_root(&self, n: usize) -> Option<H256> {
|
||||
self.cht_roots.lock().get(n).map(|h| h.clone())
|
||||
}
|
||||
|
||||
/// Get the genesis hash.
|
||||
pub fn genesis_hash(&self) -> H256 {
|
||||
::util::Hashable::sha3(&self.genesis_header)
|
||||
}
|
||||
|
||||
/// Get the best block's data.
|
||||
pub fn best_block(&self) -> BlockDescriptor {
|
||||
self.best_block.read().clone()
|
||||
}
|
||||
|
||||
/// If there is a gap between the genesis and the rest
|
||||
/// of the stored blocks, return the first post-gap block.
|
||||
pub fn first_block(&self) -> Option<BlockDescriptor> {
|
||||
let candidates = self.candidates.read();
|
||||
match candidates.iter().next() {
|
||||
None | Some((&1, _)) => None,
|
||||
Some((&height, entry)) => Some(BlockDescriptor {
|
||||
number: height,
|
||||
hash: entry.canonical_hash,
|
||||
total_difficulty: entry.candidates.iter().find(|x| x.hash == entry.canonical_hash)
|
||||
.expect("entry always stores canonical candidate; qed").total_difficulty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block status.
|
||||
pub fn status(&self, hash: &H256) -> BlockStatus {
|
||||
match self.headers.read().contains_key(hash) {
|
||||
true => BlockStatus::InChain,
|
||||
false => BlockStatus::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for HeaderChain {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.candidates.read().heap_size_of_children() +
|
||||
self.headers.read().heap_size_of_children() +
|
||||
self.cht_roots.lock().heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HeaderChain;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::header::Header;
|
||||
use ethcore::spec::Spec;
|
||||
|
||||
#[test]
|
||||
fn basic_chain() {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
|
||||
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
|
||||
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
for i in 1..10000 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
|
||||
assert!(chain.get_header(BlockId::Number(10)).is_none());
|
||||
assert!(chain.get_header(BlockId::Number(9000)).is_some());
|
||||
assert!(chain.cht_root(2).is_some());
|
||||
assert!(chain.cht_root(3).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorganize() {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
|
||||
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
|
||||
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
for i in 1..6 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
|
||||
{
|
||||
let mut rolling_timestamp = rolling_timestamp;
|
||||
let mut parent_hash = parent_hash;
|
||||
for i in 6..16 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(chain.best_block().number, 15);
|
||||
|
||||
{
|
||||
let mut rolling_timestamp = rolling_timestamp;
|
||||
let mut parent_hash = parent_hash;
|
||||
|
||||
// import a shorter chain which has better TD.
|
||||
for i in 6..13 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * (i * i).into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 11;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(chain.best_block().number, 12);
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client implementation. Stores data from light sync
|
||||
|
||||
use ethcore::block_import_error::BlockImportError;
|
||||
use ethcore::block_status::BlockStatus;
|
||||
use ethcore::client::ClientReport;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::header::Header;
|
||||
use ethcore::views::HeaderView;
|
||||
use ethcore::verification::queue::{self, HeaderQueue};
|
||||
use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition};
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::spec::Spec;
|
||||
use ethcore::service::ClientIoMessage;
|
||||
use ethcore::encoded;
|
||||
use io::IoChannel;
|
||||
|
||||
use util::hash::{H256, H256FastMap};
|
||||
use util::{Bytes, Mutex, RwLock};
|
||||
|
||||
use provider::Provider;
|
||||
use request;
|
||||
use time;
|
||||
|
||||
use self::header_chain::HeaderChain;
|
||||
|
||||
pub use self::service::Service;
|
||||
|
||||
pub mod cht;
|
||||
|
||||
mod header_chain;
|
||||
mod service;
|
||||
|
||||
/// Configuration for the light client.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
/// Verification queue config.
|
||||
pub queue: queue::Config,
|
||||
}
|
||||
|
||||
/// Trait for interacting with the header chain abstractly.
|
||||
pub trait LightChainClient: Send + Sync {
|
||||
/// Get chain info.
|
||||
fn chain_info(&self) -> BlockChainInfo;
|
||||
|
||||
/// Queue header to be verified. Required that all headers queued have their
|
||||
/// parent queued prior.
|
||||
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError>;
|
||||
|
||||
/// Query whether a block is known.
|
||||
fn is_known(&self, hash: &H256) -> bool;
|
||||
|
||||
/// Clear the queue.
|
||||
fn clear_queue(&self);
|
||||
|
||||
/// Get queue info.
|
||||
fn queue_info(&self) -> queue::QueueInfo;
|
||||
|
||||
/// Get the `i`th CHT root.
|
||||
fn cht_root(&self, i: usize) -> Option<H256>;
|
||||
}
|
||||
|
||||
/// Light client implementation.
|
||||
pub struct Client {
|
||||
queue: HeaderQueue,
|
||||
chain: HeaderChain,
|
||||
tx_pool: Mutex<H256FastMap<PendingTransaction>>,
|
||||
report: RwLock<ClientReport>,
|
||||
import_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new `Client`.
|
||||
pub fn new(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self {
|
||||
Client {
|
||||
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
|
||||
chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())),
|
||||
tx_pool: Mutex::new(Default::default()),
|
||||
report: RwLock::new(ClientReport::default()),
|
||||
import_lock: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a header to the queue for additional verification.
|
||||
pub fn import_header(&self, header: Header) -> Result<H256, BlockImportError> {
|
||||
self.queue.import(header).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Import a local transaction.
|
||||
pub fn import_own_transaction(&self, tx: PendingTransaction) {
|
||||
self.tx_pool.lock().insert(tx.transaction.hash(), tx);
|
||||
}
|
||||
|
||||
/// Fetch a vector of all pending transactions.
|
||||
pub fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
let best_num = self.chain.best_block().number;
|
||||
self.tx_pool.lock()
|
||||
.values()
|
||||
.filter(|t| match t.condition {
|
||||
Some(TransactionCondition::Number(ref x)) => x <= &best_num,
|
||||
Some(TransactionCondition::Timestamp(ref x)) => *x <= time::get_time().sec as u64,
|
||||
None => true,
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Inquire about the status of a given header.
|
||||
pub fn status(&self, hash: &H256) -> BlockStatus {
|
||||
match self.queue.status(hash) {
|
||||
queue::Status::Unknown => self.chain.status(hash),
|
||||
other => other.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the chain info.
|
||||
pub fn chain_info(&self) -> BlockChainInfo {
|
||||
let best_block = self.chain.best_block();
|
||||
let first_block = self.chain.first_block();
|
||||
let genesis_hash = self.chain.genesis_hash();
|
||||
|
||||
BlockChainInfo {
|
||||
total_difficulty: best_block.total_difficulty,
|
||||
pending_total_difficulty: best_block.total_difficulty,
|
||||
genesis_hash: genesis_hash,
|
||||
best_block_hash: best_block.hash,
|
||||
best_block_number: best_block.number,
|
||||
best_block_timestamp: HeaderView::new(&self.chain.get_header(BlockId::Latest).expect("Latest hash is always in the chain")).timestamp(),
|
||||
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
|
||||
ancient_block_number: if first_block.is_some() { Some(0) } else { None },
|
||||
first_block_hash: first_block.as_ref().map(|first| first.hash),
|
||||
first_block_number: first_block.as_ref().map(|first| first.number),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the header queue info.
|
||||
pub fn queue_info(&self) -> queue::QueueInfo {
|
||||
self.queue.queue_info()
|
||||
}
|
||||
|
||||
/// Get a block header by Id.
|
||||
pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
self.chain.get_header(id)
|
||||
}
|
||||
|
||||
/// Get the `i`th CHT root.
|
||||
pub fn cht_root(&self, i: usize) -> Option<H256> {
|
||||
self.chain.cht_root(i)
|
||||
}
|
||||
|
||||
/// Import a set of pre-verified headers from the queue.
|
||||
pub fn import_verified(&self) {
|
||||
const MAX: usize = 256;
|
||||
|
||||
let _lock = self.import_lock.lock();
|
||||
|
||||
let mut bad = Vec::new();
|
||||
let mut good = Vec::new();
|
||||
for verified_header in self.queue.drain(MAX) {
|
||||
let (num, hash) = (verified_header.number(), verified_header.hash());
|
||||
|
||||
match self.chain.insert(::rlp::encode(&verified_header).to_vec()) {
|
||||
Ok(()) => {
|
||||
good.push(hash);
|
||||
self.report.write().blocks_imported += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e);
|
||||
bad.push(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.mark_as_bad(&bad);
|
||||
self.queue.mark_as_good(&good);
|
||||
}
|
||||
|
||||
/// Get a report about blocks imported.
|
||||
pub fn report(&self) -> ClientReport {
|
||||
::std::mem::replace(&mut *self.report.write(), ClientReport::default())
|
||||
}
|
||||
|
||||
/// Get blockchain mem usage in bytes.
|
||||
pub fn chain_mem_used(&self) -> usize {
|
||||
use util::HeapSizeOf;
|
||||
|
||||
self.chain.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl LightChainClient for Client {
|
||||
fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) }
|
||||
|
||||
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError> {
|
||||
self.import_header(header)
|
||||
}
|
||||
|
||||
fn is_known(&self, hash: &H256) -> bool {
|
||||
self.status(hash) == BlockStatus::InChain
|
||||
}
|
||||
|
||||
fn clear_queue(&self) {
|
||||
self.queue.clear()
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> queue::QueueInfo {
|
||||
self.queue.queue_info()
|
||||
}
|
||||
|
||||
fn cht_root(&self, i: usize) -> Option<H256> {
|
||||
Client::cht_root(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
// dummy implementation -- may draw from canonical cache further on.
|
||||
impl Provider for Client {
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
Client::chain_info(self)
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||
self.chain.get_header(id).map(encoded::Header::new)
|
||||
}
|
||||
|
||||
fn block_body(&self, _id: BlockId) -> Option<encoded::Body> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_receipts(&self, _hash: &H256) -> Option<Bytes> {
|
||||
None
|
||||
}
|
||||
|
||||
fn state_proof(&self, _req: request::StateProof) -> Vec<Bytes> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn contract_code(&self, _req: request::ContractCode) -> Bytes {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Minimal IO service for light client.
|
||||
//! Just handles block import messages and passes them to the client.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore::service::ClientIoMessage;
|
||||
use ethcore::spec::Spec;
|
||||
use io::{IoContext, IoError, IoHandler, IoService};
|
||||
|
||||
use super::{Client, Config as ClientConfig};
|
||||
|
||||
/// Light client service.
|
||||
pub struct Service {
|
||||
client: Arc<Client>,
|
||||
_io_service: IoService<ClientIoMessage>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Start the service: initialize I/O workers and client itself.
|
||||
pub fn start(config: ClientConfig, spec: &Spec) -> Result<Self, IoError> {
|
||||
let io_service = try!(IoService::<ClientIoMessage>::start());
|
||||
let client = Arc::new(Client::new(config, spec, io_service.channel()));
|
||||
try!(io_service.register_handler(Arc::new(ImportBlocks(client.clone()))));
|
||||
|
||||
Ok(Service {
|
||||
client: client,
|
||||
_io_service: io_service,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a handle to the client.
|
||||
pub fn client(&self) -> &Arc<Client> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportBlocks(Arc<Client>);
|
||||
|
||||
impl IoHandler<ClientIoMessage> for ImportBlocks {
|
||||
fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) {
|
||||
if let ClientIoMessage::BlockVerified = *message {
|
||||
self.0.import_verified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Service;
|
||||
use ethcore::spec::Spec;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let spec = Spec::new_test();
|
||||
Service::start(Default::default(), &spec).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client logic and implementation.
|
||||
//!
|
||||
//! A "light" client stores very little chain-related data locally
|
||||
//! unlike a full node, which stores all blocks, headers, receipts, and more.
|
||||
//!
|
||||
//! This enables the client to have a much lower resource footprint in
|
||||
//! exchange for the cost of having to ask the network for state data
|
||||
//! while responding to queries. This makes a light client unsuitable for
|
||||
//! low-latency applications, but perfectly suitable for simple everyday
|
||||
//! use-cases like sending transactions from a personal account.
|
||||
//!
|
||||
//! The light client performs a header-only sync, doing verification and pruning
|
||||
//! historical blocks. Upon pruning, batches of 2048 blocks have a number => hash
|
||||
//! mapping sealed into "canonical hash tries" which can later be used to verify
|
||||
//! historical block queries from peers.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod client;
|
||||
pub mod net;
|
||||
|
||||
#[cfg(not(feature = "ipc"))]
|
||||
pub mod provider;
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
pub mod provider {
|
||||
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
|
||||
include!(concat!(env!("OUT_DIR"), "/provider.rs"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
pub mod remote {
|
||||
pub use provider::LightProviderClient;
|
||||
}
|
||||
|
||||
mod types;
|
||||
|
||||
pub use self::provider::Provider;
|
||||
pub use types::les_request as request;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate ethcore_network as network;
|
||||
extern crate ethcore_io as io;
|
||||
extern crate rlp;
|
||||
extern crate smallvec;
|
||||
extern crate time;
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
extern crate ethcore_ipc as ipc;
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! LES buffer flow management.
|
||||
//!
|
||||
//! Every request in the LES protocol leads to a reduction
|
||||
//! of the requester's buffer value as a rate-limiting mechanism.
|
||||
//! This buffer value will recharge at a set rate.
|
||||
//!
|
||||
//! This module provides an interface for configuration of buffer
|
||||
//! flow costs and recharge rates.
|
||||
//!
|
||||
//! Current default costs are picked completely arbitrarily, not based
|
||||
//! on any empirical timings or mathematical models.
|
||||
|
||||
use request;
|
||||
use super::packet;
|
||||
use super::error::Error;
|
||||
|
||||
use rlp::*;
|
||||
use util::U256;
|
||||
use time::{Duration, SteadyTime};
|
||||
|
||||
/// A request cost specification.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Cost(pub U256, pub U256);
|
||||
|
||||
/// Buffer value.
|
||||
///
|
||||
/// Produced and recharged using `FlowParams`.
|
||||
/// Definitive updates can be made as well -- these will reset the recharge
|
||||
/// point to the time of the update.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Buffer {
|
||||
estimate: U256,
|
||||
recharge_point: SteadyTime,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Get the current buffer value.
|
||||
pub fn current(&self) -> U256 { self.estimate.clone() }
|
||||
|
||||
/// Make a definitive update.
|
||||
/// This will be the value obtained after receiving
|
||||
/// a response to a request.
|
||||
pub fn update_to(&mut self, value: U256) {
|
||||
self.estimate = value;
|
||||
self.recharge_point = SteadyTime::now();
|
||||
}
|
||||
|
||||
/// Attempt to apply the given cost to the buffer.
|
||||
///
|
||||
/// If successful, the cost will be deducted successfully.
|
||||
///
|
||||
/// If unsuccessful, the structure will be unaltered an an
|
||||
/// error will be produced.
|
||||
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
|
||||
match cost > self.estimate {
|
||||
true => Err(Error::BufferEmpty),
|
||||
false => {
|
||||
self.estimate = self.estimate - cost;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A cost table, mapping requests to base and per-request costs.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CostTable {
|
||||
headers: Cost,
|
||||
bodies: Cost,
|
||||
receipts: Cost,
|
||||
state_proofs: Cost,
|
||||
contract_codes: Cost,
|
||||
header_proofs: Cost,
|
||||
}
|
||||
|
||||
impl Default for CostTable {
|
||||
fn default() -> Self {
|
||||
// arbitrarily chosen constants.
|
||||
CostTable {
|
||||
headers: Cost(100000.into(), 10000.into()),
|
||||
bodies: Cost(150000.into(), 15000.into()),
|
||||
receipts: Cost(50000.into(), 5000.into()),
|
||||
state_proofs: Cost(250000.into(), 25000.into()),
|
||||
contract_codes: Cost(200000.into(), 20000.into()),
|
||||
header_proofs: Cost(150000.into(), 15000.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RlpEncodable for CostTable {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
fn append_cost(s: &mut RlpStream, msg_id: u8, cost: &Cost) {
|
||||
s.begin_list(3)
|
||||
.append(&msg_id)
|
||||
.append(&cost.0)
|
||||
.append(&cost.1);
|
||||
}
|
||||
|
||||
s.begin_list(6);
|
||||
|
||||
append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers);
|
||||
append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies);
|
||||
append_cost(s, packet::GET_RECEIPTS, &self.receipts);
|
||||
append_cost(s, packet::GET_PROOFS, &self.state_proofs);
|
||||
append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes);
|
||||
append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs);
|
||||
}
|
||||
}
|
||||
|
||||
impl RlpDecodable for CostTable {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let rlp = decoder.as_rlp();
|
||||
|
||||
let mut headers = None;
|
||||
let mut bodies = None;
|
||||
let mut receipts = None;
|
||||
let mut state_proofs = None;
|
||||
let mut contract_codes = None;
|
||||
let mut header_proofs = None;
|
||||
|
||||
for row in rlp.iter() {
|
||||
let msg_id: u8 = row.val_at(0)?;
|
||||
let cost = {
|
||||
let base = row.val_at(1)?;
|
||||
let per = row.val_at(2)?;
|
||||
|
||||
Cost(base, per)
|
||||
};
|
||||
|
||||
match msg_id {
|
||||
packet::GET_BLOCK_HEADERS => headers = Some(cost),
|
||||
packet::GET_BLOCK_BODIES => bodies = Some(cost),
|
||||
packet::GET_RECEIPTS => receipts = Some(cost),
|
||||
packet::GET_PROOFS => state_proofs = Some(cost),
|
||||
packet::GET_CONTRACT_CODES => contract_codes = Some(cost),
|
||||
packet::GET_HEADER_PROOFS => header_proofs = Some(cost),
|
||||
_ => return Err(DecoderError::Custom("Unrecognized message in cost table")),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CostTable {
|
||||
headers: headers.ok_or(DecoderError::Custom("No headers cost specified"))?,
|
||||
bodies: bodies.ok_or(DecoderError::Custom("No bodies cost specified"))?,
|
||||
receipts: receipts.ok_or(DecoderError::Custom("No receipts cost specified"))?,
|
||||
state_proofs: state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))?,
|
||||
contract_codes: contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))?,
|
||||
header_proofs: header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A buffer-flow manager handles costs, recharge, limits
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FlowParams {
|
||||
costs: CostTable,
|
||||
limit: U256,
|
||||
recharge: U256,
|
||||
}
|
||||
|
||||
impl FlowParams {
|
||||
/// Create new flow parameters from a request cost table,
|
||||
/// buffer limit, and (minimum) rate of recharge.
|
||||
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
|
||||
FlowParams {
|
||||
costs: costs,
|
||||
limit: limit,
|
||||
recharge: recharge,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the buffer limit.
|
||||
pub fn limit(&self) -> &U256 { &self.limit }
|
||||
|
||||
/// Get a reference to the cost table.
|
||||
pub fn cost_table(&self) -> &CostTable { &self.costs }
|
||||
|
||||
/// Get a reference to the recharge rate.
|
||||
pub fn recharge_rate(&self) -> &U256 { &self.recharge }
|
||||
|
||||
/// Compute the actual cost of a request, given the kind of request
|
||||
/// and number of requests made.
|
||||
pub fn compute_cost(&self, kind: request::Kind, amount: usize) -> U256 {
|
||||
let cost = match kind {
|
||||
request::Kind::Headers => &self.costs.headers,
|
||||
request::Kind::Bodies => &self.costs.bodies,
|
||||
request::Kind::Receipts => &self.costs.receipts,
|
||||
request::Kind::StateProofs => &self.costs.state_proofs,
|
||||
request::Kind::Codes => &self.costs.contract_codes,
|
||||
request::Kind::HeaderProofs => &self.costs.header_proofs,
|
||||
};
|
||||
|
||||
let amount: U256 = amount.into();
|
||||
cost.0 + (amount * cost.1)
|
||||
}
|
||||
|
||||
/// Compute the maximum number of costs of a specific kind which can be made
|
||||
/// with the given buffer.
|
||||
/// Saturates at `usize::max()`. This is not a problem in practice because
|
||||
/// this amount of requests is already prohibitively large.
|
||||
pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize {
|
||||
use util::Uint;
|
||||
use std::usize;
|
||||
|
||||
let cost = match kind {
|
||||
request::Kind::Headers => &self.costs.headers,
|
||||
request::Kind::Bodies => &self.costs.bodies,
|
||||
request::Kind::Receipts => &self.costs.receipts,
|
||||
request::Kind::StateProofs => &self.costs.state_proofs,
|
||||
request::Kind::Codes => &self.costs.contract_codes,
|
||||
request::Kind::HeaderProofs => &self.costs.header_proofs,
|
||||
};
|
||||
|
||||
let start = buffer.current();
|
||||
|
||||
if start <= cost.0 {
|
||||
return 0;
|
||||
} else if cost.1 == U256::zero() {
|
||||
return usize::MAX;
|
||||
}
|
||||
|
||||
let max = (start - cost.0) / cost.1;
|
||||
if max >= usize::MAX.into() {
|
||||
usize::MAX
|
||||
} else {
|
||||
max.as_u64() as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Create initial buffer parameter.
|
||||
pub fn create_buffer(&self) -> Buffer {
|
||||
Buffer {
|
||||
estimate: self.limit,
|
||||
recharge_point: SteadyTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Recharge the buffer based on time passed since last
|
||||
/// update.
|
||||
pub fn recharge(&self, buf: &mut Buffer) {
|
||||
let now = SteadyTime::now();
|
||||
|
||||
// recompute and update only in terms of full seconds elapsed
|
||||
// in order to keep the estimate as an underestimate.
|
||||
let elapsed = (now - buf.recharge_point).num_seconds();
|
||||
buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed);
|
||||
|
||||
let elapsed: U256 = elapsed.into();
|
||||
|
||||
buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge));
|
||||
}
|
||||
|
||||
/// Refund some buffer which was previously deducted.
|
||||
/// Does not update the recharge timestamp.
|
||||
pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) {
|
||||
buf.estimate = buf.estimate + refund_amount;
|
||||
|
||||
if buf.estimate > self.limit {
|
||||
buf.estimate = self.limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FlowParams {
|
||||
fn default() -> Self {
|
||||
FlowParams {
|
||||
limit: 50_000_000.into(),
|
||||
costs: CostTable::default(),
|
||||
recharge: 100_000.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_serialize_cost_table() {
|
||||
let costs = CostTable::default();
|
||||
let serialized = ::rlp::encode(&costs);
|
||||
|
||||
let new_costs: CostTable = ::rlp::decode(&*serialized);
|
||||
|
||||
assert_eq!(costs, new_costs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_mechanism() {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
|
||||
let mut buffer = flow_params.create_buffer();
|
||||
|
||||
assert!(buffer.deduct_cost(101.into()).is_err());
|
||||
assert!(buffer.deduct_cost(10.into()).is_ok());
|
||||
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
flow_params.recharge(&mut buffer);
|
||||
|
||||
assert_eq!(buffer.estimate, 100.into());
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! I/O and event context generalizations.
|
||||
|
||||
use network::{NetworkContext, PeerId, NodeId};
|
||||
|
||||
use super::{Announcement, LightProtocol, ReqId};
|
||||
use super::error::Error;
|
||||
use request::{self, Request};
|
||||
|
||||
/// An I/O context which allows sending and receiving packets as well as
|
||||
/// disconnecting peers. This is used as a generalization of the portions
|
||||
/// of a p2p network which the light protocol structure makes use of.
|
||||
pub trait IoContext {
|
||||
/// Send a packet to a specific peer.
|
||||
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>);
|
||||
|
||||
/// Respond to a peer's message. Only works if this context is a byproduct
|
||||
/// of a packet handler.
|
||||
fn respond(&self, packet_id: u8, packet_body: Vec<u8>);
|
||||
|
||||
/// Disconnect a peer.
|
||||
fn disconnect_peer(&self, peer: PeerId);
|
||||
|
||||
/// Disable a peer -- this is a disconnect + a time-out.
|
||||
fn disable_peer(&self, peer: PeerId);
|
||||
|
||||
/// Get a peer's protocol version.
|
||||
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
|
||||
|
||||
/// Persistent peer id
|
||||
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
|
||||
}
|
||||
|
||||
|
||||
impl<'a> IoContext for NetworkContext<'a> {
|
||||
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
|
||||
if let Err(e) = self.send(peer, packet_id, packet_body) {
|
||||
debug!(target: "les", "Error sending packet to peer {}: {}", peer, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
|
||||
if let Err(e) = self.respond(packet_id, packet_body) {
|
||||
debug!(target: "les", "Error responding to peer message: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
NetworkContext::disconnect_peer(self, peer);
|
||||
}
|
||||
|
||||
fn disable_peer(&self, peer: PeerId) {
|
||||
NetworkContext::disable_peer(self, peer);
|
||||
}
|
||||
|
||||
fn protocol_version(&self, peer: PeerId) -> Option<u8> {
|
||||
self.protocol_version(self.subprotocol_name(), peer)
|
||||
}
|
||||
|
||||
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId> {
|
||||
self.session_info(peer).and_then(|info| info.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic context for a the protocol.
|
||||
pub trait BasicContext {
|
||||
/// Returns the relevant's peer persistent Id (aka NodeId).
|
||||
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
|
||||
|
||||
/// Make a request from a peer.
|
||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error>;
|
||||
|
||||
/// Make an announcement of new capabilities to the rest of the peers.
|
||||
// TODO: maybe just put this on a timer in LightProtocol?
|
||||
fn make_announcement(&self, announcement: Announcement);
|
||||
|
||||
/// Find the maximum number of requests of a specific type which can be made from
|
||||
/// supplied peer.
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize;
|
||||
|
||||
/// Disconnect a peer.
|
||||
fn disconnect_peer(&self, peer: PeerId);
|
||||
|
||||
/// Disable a peer.
|
||||
fn disable_peer(&self, peer: PeerId);
|
||||
}
|
||||
|
||||
/// Context for a protocol event which has a peer ID attached.
|
||||
pub trait EventContext: BasicContext {
|
||||
/// Get the peer relevant to the event e.g. message sender,
|
||||
/// disconnected/connected peer.
|
||||
fn peer(&self) -> PeerId;
|
||||
|
||||
/// Treat the event context as a basic context.
|
||||
fn as_basic(&self) -> &BasicContext;
|
||||
}
|
||||
|
||||
/// Basic context.
|
||||
pub struct TickCtx<'a> {
|
||||
/// Io context to enable dispatch.
|
||||
pub io: &'a IoContext,
|
||||
/// Protocol implementation.
|
||||
pub proto: &'a LightProtocol,
|
||||
}
|
||||
|
||||
impl<'a> BasicContext for TickCtx<'a> {
|
||||
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
|
||||
self.io.persistent_peer_id(id)
|
||||
}
|
||||
|
||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
|
||||
self.proto.request_from(self.io, &peer, request)
|
||||
}
|
||||
|
||||
fn make_announcement(&self, announcement: Announcement) {
|
||||
self.proto.make_announcement(self.io, announcement);
|
||||
}
|
||||
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.proto.max_requests(peer, kind)
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
self.io.disconnect_peer(peer);
|
||||
}
|
||||
|
||||
fn disable_peer(&self, peer: PeerId) {
|
||||
self.io.disable_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Concrete implementation of `EventContext` over the light protocol struct and
|
||||
/// an io context.
|
||||
pub struct Ctx<'a> {
|
||||
/// Io context to enable immediate response to events.
|
||||
pub io: &'a IoContext,
|
||||
/// Protocol implementation.
|
||||
pub proto: &'a LightProtocol,
|
||||
/// Relevant peer for event.
|
||||
pub peer: PeerId,
|
||||
}
|
||||
|
||||
impl<'a> BasicContext for Ctx<'a> {
|
||||
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
|
||||
self.io.persistent_peer_id(id)
|
||||
}
|
||||
|
||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
|
||||
self.proto.request_from(self.io, &peer, request)
|
||||
}
|
||||
|
||||
fn make_announcement(&self, announcement: Announcement) {
|
||||
self.proto.make_announcement(self.io, announcement);
|
||||
}
|
||||
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.proto.max_requests(peer, kind)
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
self.io.disconnect_peer(peer);
|
||||
}
|
||||
|
||||
fn disable_peer(&self, peer: PeerId) {
|
||||
self.io.disable_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventContext for Ctx<'a> {
|
||||
fn peer(&self) -> PeerId {
|
||||
self.peer
|
||||
}
|
||||
|
||||
fn as_basic(&self) -> &BasicContext {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines error types and levels of punishment to use upon
|
||||
//! encountering.
|
||||
|
||||
use rlp::DecoderError;
|
||||
use network::NetworkError;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Levels of punishment.
|
||||
///
|
||||
/// Currently just encompasses two different kinds of disconnect and
|
||||
/// no punishment, but this is where reputation systems might come into play.
|
||||
// In ascending order
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Punishment {
|
||||
/// Perform no punishment.
|
||||
None,
|
||||
/// Disconnect the peer, but don't prevent them from reconnecting.
|
||||
Disconnect,
|
||||
/// Disconnect the peer and prevent them from reconnecting.
|
||||
Disable,
|
||||
}
|
||||
|
||||
/// Kinds of errors which can be encountered in the course of LES.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// An RLP decoding error.
|
||||
Rlp(DecoderError),
|
||||
/// A network error.
|
||||
Network(NetworkError),
|
||||
/// Out of buffer.
|
||||
BufferEmpty,
|
||||
/// Unrecognized packet code.
|
||||
UnrecognizedPacket(u8),
|
||||
/// Unexpected handshake.
|
||||
UnexpectedHandshake,
|
||||
/// Peer on wrong network (wrong NetworkId or genesis hash)
|
||||
WrongNetwork,
|
||||
/// Unknown peer.
|
||||
UnknownPeer,
|
||||
/// Unsolicited response.
|
||||
UnsolicitedResponse,
|
||||
/// Not a server.
|
||||
NotServer,
|
||||
/// Unsupported protocol version.
|
||||
UnsupportedProtocolVersion(u8),
|
||||
/// Bad protocol version.
|
||||
BadProtocolVersion,
|
||||
/// Peer is overburdened.
|
||||
Overburdened,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// What level of punishment does this error warrant?
|
||||
pub fn punishment(&self) -> Punishment {
|
||||
match *self {
|
||||
Error::Rlp(_) => Punishment::Disable,
|
||||
Error::Network(_) => Punishment::None,
|
||||
Error::BufferEmpty => Punishment::Disable,
|
||||
Error::UnrecognizedPacket(_) => Punishment::Disconnect,
|
||||
Error::UnexpectedHandshake => Punishment::Disconnect,
|
||||
Error::WrongNetwork => Punishment::Disable,
|
||||
Error::UnknownPeer => Punishment::Disconnect,
|
||||
Error::UnsolicitedResponse => Punishment::Disable,
|
||||
Error::NotServer => Punishment::Disable,
|
||||
Error::UnsupportedProtocolVersion(_) => Punishment::Disable,
|
||||
Error::BadProtocolVersion => Punishment::Disable,
|
||||
Error::Overburdened => Punishment::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecoderError> for Error {
|
||||
fn from(err: DecoderError) -> Self {
|
||||
Error::Rlp(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkError> for Error {
|
||||
fn from(err: NetworkError) -> Self {
|
||||
Error::Network(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Rlp(ref err) => err.fmt(f),
|
||||
Error::Network(ref err) => err.fmt(f),
|
||||
Error::BufferEmpty => write!(f, "Out of buffer"),
|
||||
Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
|
||||
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
|
||||
Error::WrongNetwork => write!(f, "Wrong network"),
|
||||
Error::UnknownPeer => write!(f, "Unknown peer"),
|
||||
Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"),
|
||||
Error::NotServer => write!(f, "Peer not a server."),
|
||||
Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv),
|
||||
Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"),
|
||||
Error::Overburdened => write!(f, "Peer overburdened"),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,565 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Peer status and capabilities.
|
||||
|
||||
use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View};
|
||||
use util::{H256, U256};
|
||||
|
||||
use super::buffer_flow::FlowParams;
|
||||
|
||||
// recognized handshake/announcement keys.
|
||||
// unknown keys are to be skipped, known keys have a defined order.
|
||||
// their string values are defined in the LES spec.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||
enum Key {
|
||||
ProtocolVersion,
|
||||
NetworkId,
|
||||
HeadTD,
|
||||
HeadHash,
|
||||
HeadNum,
|
||||
GenesisHash,
|
||||
ServeHeaders,
|
||||
ServeChainSince,
|
||||
ServeStateSince,
|
||||
TxRelay,
|
||||
BufferLimit,
|
||||
BufferCostTable,
|
||||
BufferRechargeRate,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
// get the string value of this key.
|
||||
fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
Key::ProtocolVersion => "protocolVersion",
|
||||
Key::NetworkId => "networkId",
|
||||
Key::HeadTD => "headTd",
|
||||
Key::HeadHash => "headHash",
|
||||
Key::HeadNum => "headNum",
|
||||
Key::GenesisHash => "genesisHash",
|
||||
Key::ServeHeaders => "serveHeaders",
|
||||
Key::ServeChainSince => "serveChainSince",
|
||||
Key::ServeStateSince => "serveStateSince",
|
||||
Key::TxRelay => "txRelay",
|
||||
Key::BufferLimit => "flowControl/BL",
|
||||
Key::BufferCostTable => "flowControl/MRC",
|
||||
Key::BufferRechargeRate => "flowControl/MRR",
|
||||
}
|
||||
}
|
||||
|
||||
// try to parse the key value from a string.
|
||||
fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"protocolVersion" => Some(Key::ProtocolVersion),
|
||||
"networkId" => Some(Key::NetworkId),
|
||||
"headTd" => Some(Key::HeadTD),
|
||||
"headHash" => Some(Key::HeadHash),
|
||||
"headNum" => Some(Key::HeadNum),
|
||||
"genesisHash" => Some(Key::GenesisHash),
|
||||
"serveHeaders" => Some(Key::ServeHeaders),
|
||||
"serveChainSince" => Some(Key::ServeChainSince),
|
||||
"serveStateSince" => Some(Key::ServeStateSince),
|
||||
"txRelay" => Some(Key::TxRelay),
|
||||
"flowControl/BL" => Some(Key::BufferLimit),
|
||||
"flowControl/MRC" => Some(Key::BufferCostTable),
|
||||
"flowControl/MRR" => Some(Key::BufferRechargeRate),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper for decoding key-value pairs in the handshake or an announcement.
|
||||
struct Parser<'a> {
|
||||
pos: usize,
|
||||
rlp: UntrustedRlp<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
// expect a specific next key, and decode the value.
|
||||
// error on unexpected key or invalid value.
|
||||
fn expect<T: RlpDecodable>(&mut self, key: Key) -> Result<T, DecoderError> {
|
||||
self.expect_raw(key).and_then(|item| item.as_val())
|
||||
}
|
||||
|
||||
// expect a specific next key, and get the value's RLP.
|
||||
// if the key isn't found, the position isn't advanced.
|
||||
fn expect_raw(&mut self, key: Key) -> Result<UntrustedRlp<'a>, DecoderError> {
|
||||
trace!(target: "les", "Expecting key {}", key.as_str());
|
||||
let pre_pos = self.pos;
|
||||
if let Some((k, val)) = self.get_next()? {
|
||||
if k == key { return Ok(val) }
|
||||
}
|
||||
|
||||
self.pos = pre_pos;
|
||||
Err(DecoderError::Custom("Missing expected key"))
|
||||
}
|
||||
|
||||
// get the next key and value RLP.
|
||||
fn get_next(&mut self) -> Result<Option<(Key, UntrustedRlp<'a>)>, DecoderError> {
|
||||
while self.pos < self.rlp.item_count() {
|
||||
let pair = self.rlp.at(self.pos)?;
|
||||
let k: String = pair.val_at(0)?;
|
||||
|
||||
self.pos += 1;
|
||||
match Key::from_str(&k) {
|
||||
Some(key) => return Ok(Some((key , pair.at(1)?))),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for encoding a key-value pair
|
||||
fn encode_pair<T: RlpEncodable>(key: Key, val: &T) -> Vec<u8> {
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(&key.as_str()).append(val);
|
||||
s.out()
|
||||
}
|
||||
|
||||
// Helper for encoding a flag.
|
||||
fn encode_flag(key: Key) -> Vec<u8> {
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(&key.as_str()).append_empty_data();
|
||||
s.out()
|
||||
}
|
||||
|
||||
/// A peer status message.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Status {
|
||||
/// Protocol version.
|
||||
pub protocol_version: u32,
|
||||
/// Network id of this peer.
|
||||
pub network_id: u64,
|
||||
/// Total difficulty of the head of the chain.
|
||||
pub head_td: U256,
|
||||
/// Hash of the best block.
|
||||
pub head_hash: H256,
|
||||
/// Number of the best block.
|
||||
pub head_num: u64,
|
||||
/// Genesis hash
|
||||
pub genesis_hash: H256,
|
||||
/// Last announced chain head and reorg depth to common ancestor.
|
||||
pub last_head: Option<(H256, u64)>,
|
||||
}
|
||||
|
||||
/// Peer capabilities.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Capabilities {
|
||||
/// Whether this peer can serve headers
|
||||
pub serve_headers: bool,
|
||||
/// Earliest block number it can serve block/receipt requests for.
|
||||
/// `None` means no requests will be servable.
|
||||
pub serve_chain_since: Option<u64>,
|
||||
/// Earliest block number it can serve state requests for.
|
||||
/// `None` means no requests will be servable.
|
||||
pub serve_state_since: Option<u64>,
|
||||
/// Whether it can relay transactions to the eth network.
|
||||
pub tx_relay: bool,
|
||||
}
|
||||
|
||||
impl Default for Capabilities {
|
||||
fn default() -> Self {
|
||||
Capabilities {
|
||||
serve_headers: true,
|
||||
serve_chain_since: None,
|
||||
serve_state_since: None,
|
||||
tx_relay: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Update the capabilities from an announcement.
|
||||
pub fn update_from(&mut self, announcement: &Announcement) {
|
||||
self.serve_headers = self.serve_headers || announcement.serve_headers;
|
||||
self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since);
|
||||
self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since);
|
||||
self.tx_relay = self.tx_relay || announcement.tx_relay;
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to parse a handshake message into its three parts:
|
||||
/// - chain status
|
||||
/// - serving capabilities
|
||||
/// - buffer flow parameters
|
||||
pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> {
|
||||
let mut parser = Parser {
|
||||
pos: 0,
|
||||
rlp: rlp,
|
||||
};
|
||||
|
||||
let status = Status {
|
||||
protocol_version: parser.expect(Key::ProtocolVersion)?,
|
||||
network_id: parser.expect(Key::NetworkId)?,
|
||||
head_td: parser.expect(Key::HeadTD)?,
|
||||
head_hash: parser.expect(Key::HeadHash)?,
|
||||
head_num: parser.expect(Key::HeadNum)?,
|
||||
genesis_hash: parser.expect(Key::GenesisHash)?,
|
||||
last_head: None,
|
||||
};
|
||||
|
||||
let capabilities = Capabilities {
|
||||
serve_headers: parser.expect_raw(Key::ServeHeaders).is_ok(),
|
||||
serve_chain_since: parser.expect(Key::ServeChainSince).ok(),
|
||||
serve_state_since: parser.expect(Key::ServeStateSince).ok(),
|
||||
tx_relay: parser.expect_raw(Key::TxRelay).is_ok(),
|
||||
};
|
||||
|
||||
let flow_params = match (
|
||||
parser.expect(Key::BufferLimit),
|
||||
parser.expect(Key::BufferCostTable),
|
||||
parser.expect(Key::BufferRechargeRate)
|
||||
) {
|
||||
(Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok((status, capabilities, flow_params))
|
||||
}
|
||||
|
||||
/// Write a handshake, given status, capabilities, and flow parameters.
|
||||
pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec<u8> {
|
||||
let mut pairs = Vec::new();
|
||||
pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version));
|
||||
pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64)));
|
||||
pairs.push(encode_pair(Key::HeadTD, &status.head_td));
|
||||
pairs.push(encode_pair(Key::HeadHash, &status.head_hash));
|
||||
pairs.push(encode_pair(Key::HeadNum, &status.head_num));
|
||||
pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash));
|
||||
|
||||
if capabilities.serve_headers {
|
||||
pairs.push(encode_flag(Key::ServeHeaders));
|
||||
}
|
||||
if let Some(ref serve_chain_since) = capabilities.serve_chain_since {
|
||||
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
|
||||
}
|
||||
if let Some(ref serve_state_since) = capabilities.serve_state_since {
|
||||
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
|
||||
}
|
||||
if capabilities.tx_relay {
|
||||
pairs.push(encode_flag(Key::TxRelay));
|
||||
}
|
||||
|
||||
if let Some(flow_params) = flow_params {
|
||||
pairs.push(encode_pair(Key::BufferLimit, flow_params.limit()));
|
||||
pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table()));
|
||||
pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate()));
|
||||
}
|
||||
|
||||
let mut stream = RlpStream::new_list(pairs.len());
|
||||
|
||||
for pair in pairs {
|
||||
stream.append_raw(&pair, 1);
|
||||
}
|
||||
|
||||
stream.out()
|
||||
}
|
||||
|
||||
/// An announcement of new chain head or capabilities made by a peer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Announcement {
|
||||
/// Hash of the best block.
|
||||
pub head_hash: H256,
|
||||
/// Number of the best block.
|
||||
pub head_num: u64,
|
||||
/// Head total difficulty
|
||||
pub head_td: U256,
|
||||
/// reorg depth to common ancestor of last announced head.
|
||||
pub reorg_depth: u64,
|
||||
/// optional new header-serving capability. false means "no change"
|
||||
pub serve_headers: bool,
|
||||
/// optional new state-serving capability
|
||||
pub serve_state_since: Option<u64>,
|
||||
/// optional new chain-serving capability
|
||||
pub serve_chain_since: Option<u64>,
|
||||
/// optional new transaction-relay capability. false means "no change"
|
||||
pub tx_relay: bool,
|
||||
// TODO: changes in buffer flow?
|
||||
}
|
||||
|
||||
/// Parse an announcement.
|
||||
pub fn parse_announcement(rlp: UntrustedRlp) -> Result<Announcement, DecoderError> {
|
||||
let mut last_key = None;
|
||||
|
||||
let mut announcement = Announcement {
|
||||
head_hash: rlp.val_at(0)?,
|
||||
head_num: rlp.val_at(1)?,
|
||||
head_td: rlp.val_at(2)?,
|
||||
reorg_depth: rlp.val_at(3)?,
|
||||
serve_headers: false,
|
||||
serve_state_since: None,
|
||||
serve_chain_since: None,
|
||||
tx_relay: false,
|
||||
};
|
||||
|
||||
let mut parser = Parser {
|
||||
pos: 4,
|
||||
rlp: rlp,
|
||||
};
|
||||
|
||||
while let Some((key, item)) = parser.get_next()? {
|
||||
if Some(key) <= last_key { return Err(DecoderError::Custom("Invalid announcement key ordering")) }
|
||||
last_key = Some(key);
|
||||
|
||||
match key {
|
||||
Key::ServeHeaders => announcement.serve_headers = true,
|
||||
Key::ServeStateSince => announcement.serve_state_since = Some(item.as_val()?),
|
||||
Key::ServeChainSince => announcement.serve_chain_since = Some(item.as_val()?),
|
||||
Key::TxRelay => announcement.tx_relay = true,
|
||||
_ => return Err(DecoderError::Custom("Nonsensical key in announcement")),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(announcement)
|
||||
}
|
||||
|
||||
/// Write an announcement out.
|
||||
pub fn write_announcement(announcement: &Announcement) -> Vec<u8> {
|
||||
let mut pairs = Vec::new();
|
||||
if announcement.serve_headers {
|
||||
pairs.push(encode_flag(Key::ServeHeaders));
|
||||
}
|
||||
if let Some(ref serve_chain_since) = announcement.serve_chain_since {
|
||||
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
|
||||
}
|
||||
if let Some(ref serve_state_since) = announcement.serve_state_since {
|
||||
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
|
||||
}
|
||||
if announcement.tx_relay {
|
||||
pairs.push(encode_flag(Key::TxRelay));
|
||||
}
|
||||
|
||||
let mut stream = RlpStream::new_list(4 + pairs.len());
|
||||
stream
|
||||
.append(&announcement.head_hash)
|
||||
.append(&announcement.head_num)
|
||||
.append(&announcement.head_td)
|
||||
.append(&announcement.reorg_depth);
|
||||
|
||||
for item in pairs {
|
||||
stream.append_raw(&item, 1);
|
||||
}
|
||||
|
||||
stream.out()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::buffer_flow::FlowParams;
|
||||
use util::{U256, H256, FixedHash};
|
||||
use rlp::{RlpStream, Stream ,UntrustedRlp, View};
|
||||
|
||||
#[test]
|
||||
fn full_handshake() {
|
||||
let status = Status {
|
||||
protocol_version: 1,
|
||||
network_id: 1,
|
||||
head_td: U256::default(),
|
||||
head_hash: H256::default(),
|
||||
head_num: 10,
|
||||
genesis_hash: H256::zero(),
|
||||
last_head: None,
|
||||
};
|
||||
|
||||
let capabilities = Capabilities {
|
||||
serve_headers: true,
|
||||
serve_chain_since: Some(5),
|
||||
serve_state_since: Some(8),
|
||||
tx_relay: true,
|
||||
};
|
||||
|
||||
let flow_params = FlowParams::new(
|
||||
1_000_000.into(),
|
||||
Default::default(),
|
||||
1000.into(),
|
||||
);
|
||||
|
||||
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
|
||||
let (read_status, read_capabilities, read_flow)
|
||||
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
|
||||
|
||||
assert_eq!(read_status, status);
|
||||
assert_eq!(read_capabilities, capabilities);
|
||||
assert_eq!(read_flow.unwrap(), flow_params);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_handshake() {
|
||||
let status = Status {
|
||||
protocol_version: 1,
|
||||
network_id: 1,
|
||||
head_td: U256::default(),
|
||||
head_hash: H256::default(),
|
||||
head_num: 10,
|
||||
genesis_hash: H256::zero(),
|
||||
last_head: None,
|
||||
};
|
||||
|
||||
let capabilities = Capabilities {
|
||||
serve_headers: false,
|
||||
serve_chain_since: Some(5),
|
||||
serve_state_since: None,
|
||||
tx_relay: true,
|
||||
};
|
||||
|
||||
let flow_params = FlowParams::new(
|
||||
1_000_000.into(),
|
||||
Default::default(),
|
||||
1000.into(),
|
||||
);
|
||||
|
||||
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
|
||||
let (read_status, read_capabilities, read_flow)
|
||||
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
|
||||
|
||||
assert_eq!(read_status, status);
|
||||
assert_eq!(read_capabilities, capabilities);
|
||||
assert_eq!(read_flow.unwrap(), flow_params);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_unknown_keys() {
|
||||
let status = Status {
|
||||
protocol_version: 1,
|
||||
network_id: 1,
|
||||
head_td: U256::default(),
|
||||
head_hash: H256::default(),
|
||||
head_num: 10,
|
||||
genesis_hash: H256::zero(),
|
||||
last_head: None,
|
||||
};
|
||||
|
||||
let capabilities = Capabilities {
|
||||
serve_headers: false,
|
||||
serve_chain_since: Some(5),
|
||||
serve_state_since: None,
|
||||
tx_relay: true,
|
||||
};
|
||||
|
||||
let flow_params = FlowParams::new(
|
||||
1_000_000.into(),
|
||||
Default::default(),
|
||||
1000.into(),
|
||||
);
|
||||
|
||||
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
let interleaved = {
|
||||
let handshake = UntrustedRlp::new(&handshake);
|
||||
let mut stream = RlpStream::new_list(handshake.item_count() * 3);
|
||||
|
||||
for item in handshake.iter() {
|
||||
stream.append_raw(item.as_raw(), 1);
|
||||
let (mut s1, mut s2) = (RlpStream::new_list(2), RlpStream::new_list(2));
|
||||
s1.append(&"foo").append_empty_data();
|
||||
s2.append(&"bar").append_empty_data();
|
||||
stream.append_raw(&s1.out(), 1);
|
||||
stream.append_raw(&s2.out(), 1);
|
||||
}
|
||||
|
||||
stream.out()
|
||||
};
|
||||
|
||||
let (read_status, read_capabilities, read_flow)
|
||||
= parse_handshake(UntrustedRlp::new(&interleaved)).unwrap();
|
||||
|
||||
assert_eq!(read_status, status);
|
||||
assert_eq!(read_capabilities, capabilities);
|
||||
assert_eq!(read_flow.unwrap(), flow_params);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announcement_roundtrip() {
|
||||
let announcement = Announcement {
|
||||
head_hash: H256::random(),
|
||||
head_num: 100_000,
|
||||
head_td: 1_000_000.into(),
|
||||
reorg_depth: 4,
|
||||
serve_headers: false,
|
||||
serve_state_since: Some(99_000),
|
||||
serve_chain_since: Some(1),
|
||||
tx_relay: true,
|
||||
};
|
||||
|
||||
let serialized = write_announcement(&announcement);
|
||||
let read = parse_announcement(UntrustedRlp::new(&serialized)).unwrap();
|
||||
|
||||
assert_eq!(read, announcement);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_out_of_order() {
|
||||
use super::{Key, encode_pair, encode_flag};
|
||||
|
||||
let mut stream = RlpStream::new_list(6);
|
||||
stream
|
||||
.append(&H256::zero())
|
||||
.append(&10u64)
|
||||
.append(&100_000u64)
|
||||
.append(&2u64)
|
||||
.append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1)
|
||||
.append_raw(&encode_flag(Key::ServeHeaders), 1);
|
||||
|
||||
let out = stream.drain();
|
||||
assert!(parse_announcement(UntrustedRlp::new(&out)).is_err());
|
||||
|
||||
let mut stream = RlpStream::new_list(6);
|
||||
stream
|
||||
.append(&H256::zero())
|
||||
.append(&10u64)
|
||||
.append(&100_000u64)
|
||||
.append(&2u64)
|
||||
.append_raw(&encode_flag(Key::ServeHeaders), 1)
|
||||
.append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1);
|
||||
|
||||
let out = stream.drain();
|
||||
assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_flow() {
|
||||
let status = Status {
|
||||
protocol_version: 1,
|
||||
network_id: 1,
|
||||
head_td: U256::default(),
|
||||
head_hash: H256::default(),
|
||||
head_num: 10,
|
||||
genesis_hash: H256::zero(),
|
||||
last_head: None,
|
||||
};
|
||||
|
||||
let capabilities = Capabilities {
|
||||
serve_headers: true,
|
||||
serve_chain_since: Some(5),
|
||||
serve_state_since: Some(8),
|
||||
tx_relay: true,
|
||||
};
|
||||
|
||||
let handshake = write_handshake(&status, &capabilities, None);
|
||||
|
||||
let (read_status, read_capabilities, read_flow)
|
||||
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
|
||||
|
||||
assert_eq!(read_status, status);
|
||||
assert_eq!(read_capabilities, capabilities);
|
||||
assert!(read_flow.is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the `LightProtocol` implementation.
|
||||
//! These don't test of the higher level logic on top of
|
||||
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient};
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
use ethcore::encoded;
|
||||
use network::{PeerId, NodeId};
|
||||
|
||||
use net::buffer_flow::FlowParams;
|
||||
use net::context::IoContext;
|
||||
use net::status::{Capabilities, Status, write_handshake};
|
||||
use net::{encode_request, LightProtocol, Params, packet};
|
||||
use provider::Provider;
|
||||
use request::{self, Request, Headers};
|
||||
|
||||
use rlp::*;
|
||||
use util::{Bytes, H256, U256};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
// expected result from a call.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Expect {
|
||||
/// Expect to have message sent to peer.
|
||||
Send(PeerId, u8, Vec<u8>),
|
||||
/// Expect this response.
|
||||
Respond(u8, Vec<u8>),
|
||||
/// Expect a punishment (disconnect/disable)
|
||||
Punish(PeerId),
|
||||
/// Expect nothing.
|
||||
Nothing,
|
||||
}
|
||||
|
||||
impl IoContext for Expect {
|
||||
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
|
||||
assert_eq!(self, &Expect::Send(peer, packet_id, packet_body));
|
||||
}
|
||||
|
||||
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
|
||||
assert_eq!(self, &Expect::Respond(packet_id, packet_body));
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
assert_eq!(self, &Expect::Punish(peer));
|
||||
}
|
||||
|
||||
fn disable_peer(&self, peer: PeerId) {
|
||||
assert_eq!(self, &Expect::Punish(peer));
|
||||
}
|
||||
|
||||
fn protocol_version(&self, _peer: PeerId) -> Option<u8> {
|
||||
Some(super::MAX_PROTOCOL_VERSION)
|
||||
}
|
||||
|
||||
fn persistent_peer_id(&self, _peer: PeerId) -> Option<NodeId> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// can't implement directly for Arc due to cross-crate orphan rules.
|
||||
struct TestProvider(Arc<TestProviderInner>);
|
||||
|
||||
struct TestProviderInner {
|
||||
client: TestBlockChainClient,
|
||||
}
|
||||
|
||||
impl Provider for TestProvider {
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
self.0.client.chain_info()
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
|
||||
self.0.client.tree_route(a, b).map(|route| route.index as u64)
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||
self.0.client.block_header(id)
|
||||
}
|
||||
|
||||
fn block_body(&self, id: BlockId) -> Option<encoded::Body> {
|
||||
self.0.client.block_body(id)
|
||||
}
|
||||
|
||||
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
||||
self.0.client.block_receipts(&hash)
|
||||
}
|
||||
|
||||
fn state_proof(&self, req: request::StateProof) -> Vec<Bytes> {
|
||||
match req.key2 {
|
||||
Some(_) => vec![::util::sha3::SHA3_NULL_RLP.to_vec()],
|
||||
None => {
|
||||
// sort of a leaf node
|
||||
let mut stream = RlpStream::new_list(2);
|
||||
stream.append(&req.key1).append_empty_data();
|
||||
vec![stream.out()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contract_code(&self, req: request::ContractCode) -> Bytes {
|
||||
req.account_key.iter().chain(req.account_key.iter()).cloned().collect()
|
||||
}
|
||||
|
||||
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
self.0.client.ready_transactions()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_flow_params() -> FlowParams {
|
||||
FlowParams::new(5_000_000.into(), Default::default(), 100_000.into())
|
||||
}
|
||||
|
||||
fn capabilities() -> Capabilities {
|
||||
Capabilities {
|
||||
serve_headers: true,
|
||||
serve_chain_since: Some(1),
|
||||
serve_state_since: Some(1),
|
||||
tx_relay: true,
|
||||
}
|
||||
}
|
||||
|
||||
// helper for setting up the protocol handler and provider.
|
||||
fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc<TestProviderInner>, LightProtocol) {
|
||||
let provider = Arc::new(TestProviderInner {
|
||||
client: TestBlockChainClient::new(),
|
||||
});
|
||||
|
||||
let proto = LightProtocol::new(Arc::new(TestProvider(provider.clone())), Params {
|
||||
network_id: 2,
|
||||
flow_params: flow_params,
|
||||
capabilities: capabilities,
|
||||
});
|
||||
|
||||
(provider, proto)
|
||||
}
|
||||
|
||||
fn status(chain_info: BlockChainInfo) -> Status {
|
||||
Status {
|
||||
protocol_version: 1,
|
||||
network_id: 2,
|
||||
head_td: chain_info.total_difficulty,
|
||||
head_hash: chain_info.best_block_hash,
|
||||
head_num: chain_info.best_block_number,
|
||||
genesis_hash: chain_info.genesis_hash,
|
||||
last_head: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handshake_expected() {
|
||||
let flow_params = make_flow_params();
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let status = status(provider.client.chain_info());
|
||||
|
||||
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn genesis_mismatch() {
|
||||
let flow_params = make_flow_params();
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let mut status = status(provider.client.chain_info());
|
||||
status.genesis_hash = H256::default();
|
||||
|
||||
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_overflow() {
|
||||
let flow_params = make_flow_params();
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
}
|
||||
|
||||
{
|
||||
let my_status = write_handshake(&status, &capabilities, Some(&flow_params));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
|
||||
}
|
||||
|
||||
// 1000 requests is far too many for the default flow params.
|
||||
let request = encode_request(&Request::Headers(Headers {
|
||||
start: 1.into(),
|
||||
max: 1000,
|
||||
skip: 0,
|
||||
reverse: false,
|
||||
}), 111);
|
||||
|
||||
proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request);
|
||||
}
|
||||
|
||||
// test the basic request types -- these just make sure that requests are parsed
|
||||
// and sent to the provider correctly as well as testing response formatting.
|
||||
|
||||
#[test]
|
||||
fn get_block_headers() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
|
||||
provider.client.add_blocks(100, EachBlockWith::Nothing);
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
|
||||
}
|
||||
|
||||
let request = Headers {
|
||||
start: 1.into(),
|
||||
max: 10,
|
||||
skip: 0,
|
||||
reverse: false,
|
||||
};
|
||||
let req_id = 111;
|
||||
|
||||
let request_body = encode_request(&Request::Headers(request.clone()), req_id);
|
||||
let response = {
|
||||
let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect();
|
||||
assert_eq!(headers.len(), 10);
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||
for header in headers {
|
||||
response_stream.append_raw(&header.into_inner(), 1);
|
||||
}
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::BLOCK_HEADERS, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_block_bodies() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
|
||||
provider.client.add_blocks(100, EachBlockWith::Nothing);
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
|
||||
}
|
||||
|
||||
let request = request::Bodies {
|
||||
block_hashes: (0..10).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap()).collect(),
|
||||
};
|
||||
|
||||
let req_id = 111;
|
||||
|
||||
let request_body = encode_request(&Request::Bodies(request.clone()), req_id);
|
||||
let response = {
|
||||
let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockId::Number(i + 1)).unwrap()).collect();
|
||||
assert_eq!(bodies.len(), 10);
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||
for body in bodies {
|
||||
response_stream.append_raw(&body.into_inner(), 1);
|
||||
}
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::BLOCK_BODIES, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_block_receipts() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
|
||||
provider.client.add_blocks(1000, EachBlockWith::Nothing);
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
|
||||
}
|
||||
|
||||
// find the first 10 block hashes starting with `f` because receipts are only provided
|
||||
// by the test client in that case.
|
||||
let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap())
|
||||
.filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect();
|
||||
|
||||
let request = request::Receipts {
|
||||
block_hashes: block_hashes.clone(),
|
||||
};
|
||||
|
||||
let req_id = 111;
|
||||
|
||||
let request_body = encode_request(&Request::Receipts(request.clone()), req_id);
|
||||
let response = {
|
||||
let receipts: Vec<_> = block_hashes.iter()
|
||||
.map(|hash| provider.client.block_receipts(hash).unwrap())
|
||||
.collect();
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(receipts.len());
|
||||
for block_receipts in receipts {
|
||||
response_stream.append_raw(&block_receipts, 1);
|
||||
}
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::RECEIPTS, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_state_proofs() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone()));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body);
|
||||
}
|
||||
|
||||
let req_id = 112;
|
||||
let key1 = U256::from(11223344).into();
|
||||
let key2 = U256::from(99988887).into();
|
||||
|
||||
let request = Request::StateProofs (request::StateProofs {
|
||||
requests: vec![
|
||||
request::StateProof { block: H256::default(), key1: key1, key2: None, from_level: 0 },
|
||||
request::StateProof { block: H256::default(), key1: key1, key2: Some(key2), from_level: 0},
|
||||
]
|
||||
});
|
||||
|
||||
let request_body = encode_request(&request, req_id);
|
||||
let response = {
|
||||
let proofs = vec![
|
||||
{ let mut stream = RlpStream::new_list(2); stream.append(&key1).append_empty_data(); vec![stream.out()] },
|
||||
vec![::util::sha3::SHA3_NULL_RLP.to_vec()],
|
||||
];
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||
for proof in proofs {
|
||||
response_stream.begin_list(proof.len());
|
||||
for node in proof {
|
||||
response_stream.append_raw(&node, 1);
|
||||
}
|
||||
}
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::PROOFS, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_PROOFS, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_contract_code() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone()));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body);
|
||||
}
|
||||
|
||||
let req_id = 112;
|
||||
let key1 = U256::from(11223344).into();
|
||||
let key2 = U256::from(99988887).into();
|
||||
|
||||
let request = Request::Codes (request::ContractCodes {
|
||||
code_requests: vec![
|
||||
request::ContractCode { block_hash: H256::default(), account_key: key1 },
|
||||
request::ContractCode { block_hash: H256::default(), account_key: key2 },
|
||||
],
|
||||
});
|
||||
|
||||
let request_body = encode_request(&request, req_id);
|
||||
let response = {
|
||||
let codes: Vec<Vec<_>> = vec![
|
||||
key1.iter().chain(key1.iter()).cloned().collect(),
|
||||
key2.iter().chain(key2.iter()).cloned().collect(),
|
||||
];
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||
for code in codes {
|
||||
response_stream.append(&code);
|
||||
}
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::CONTRACT_CODES, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body);
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A provider for the LES protocol. This is typically a full node, who can
|
||||
//! give as much data as necessary to its peers.
|
||||
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::client::{BlockChainClient, ProvingBlockChainClient};
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::encoded;
|
||||
|
||||
use util::{Bytes, H256};
|
||||
|
||||
use request;
|
||||
|
||||
/// Defines the operations that a provider for `LES` must fulfill.
|
||||
///
|
||||
/// These are defined at [1], but may be subject to change.
|
||||
/// Requests which can't be fulfilled should return either an empty RLP list
|
||||
/// or empty vector where appropriate.
|
||||
///
|
||||
/// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES)
|
||||
#[cfg_attr(feature = "ipc", ipc(client_ident="LightProviderClient"))]
|
||||
pub trait Provider: Send + Sync {
|
||||
/// Provide current blockchain info.
|
||||
fn chain_info(&self) -> BlockChainInfo;
|
||||
|
||||
/// Find the depth of a common ancestor between two blocks.
|
||||
/// If either block is unknown or an ancestor can't be found
|
||||
/// then return `None`.
|
||||
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>;
|
||||
|
||||
/// Earliest block where state queries are available.
|
||||
/// If `None`, no state queries are servable.
|
||||
fn earliest_state(&self) -> Option<u64>;
|
||||
|
||||
/// Provide a list of headers starting at the requested block,
|
||||
/// possibly in reverse and skipping `skip` at a time.
|
||||
///
|
||||
/// The returned vector may have any length in the range [0, `max`], but the
|
||||
/// results within must adhere to the `skip` and `reverse` parameters.
|
||||
fn block_headers(&self, req: request::Headers) -> Vec<encoded::Header> {
|
||||
use request::HashOrNumber;
|
||||
|
||||
if req.max == 0 { return Vec::new() }
|
||||
|
||||
let best_num = self.chain_info().best_block_number;
|
||||
let start_num = match req.start {
|
||||
HashOrNumber::Number(start_num) => start_num,
|
||||
HashOrNumber::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
|
||||
None => {
|
||||
trace!(target: "les_provider", "Unknown block hash {} requested", hash);
|
||||
return Vec::new();
|
||||
}
|
||||
Some(header) => {
|
||||
let num = header.number();
|
||||
let canon_hash = self.block_header(BlockId::Number(num))
|
||||
.map(|h| h.hash());
|
||||
|
||||
if req.max == 1 || canon_hash != Some(hash) {
|
||||
// Non-canonical header or single header requested.
|
||||
return vec![header];
|
||||
}
|
||||
|
||||
num
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(0u64..req.max as u64)
|
||||
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
||||
.take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x })
|
||||
.map(|x| if req.reverse { start_num - x } else { start_num + x })
|
||||
.map(|x| self.block_header(BlockId::Number(x)))
|
||||
.take_while(|x| x.is_some())
|
||||
.flat_map(|x| x)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a block header by id.
|
||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
|
||||
|
||||
/// Provide as many as possible of the requested blocks (minus the headers) encoded
|
||||
/// in RLP format.
|
||||
fn block_bodies(&self, req: request::Bodies) -> Vec<Option<encoded::Body>> {
|
||||
req.block_hashes.into_iter()
|
||||
.map(|hash| self.block_body(BlockId::Hash(hash)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a block body by id.
|
||||
fn block_body(&self, id: BlockId) -> Option<encoded::Body>;
|
||||
|
||||
/// Provide the receipts as many as possible of the requested blocks.
|
||||
/// Returns a vector of RLP-encoded lists of receipts.
|
||||
fn receipts(&self, req: request::Receipts) -> Vec<Bytes> {
|
||||
req.block_hashes.into_iter()
|
||||
.map(|hash| self.block_receipts(&hash))
|
||||
.map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a block's receipts as an RLP-encoded list by block hash.
|
||||
fn block_receipts(&self, hash: &H256) -> Option<Bytes>;
|
||||
|
||||
/// Provide a set of merkle proofs, as requested. Each request is a
|
||||
/// block hash and request parameters.
|
||||
///
|
||||
/// Returns a vector of RLP-encoded lists satisfying the requests.
|
||||
fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> {
|
||||
use rlp::{RlpStream, Stream};
|
||||
|
||||
let mut results = Vec::with_capacity(req.requests.len());
|
||||
|
||||
for request in req.requests {
|
||||
let proof = self.state_proof(request);
|
||||
|
||||
let mut stream = RlpStream::new_list(proof.len());
|
||||
for node in proof {
|
||||
stream.append_raw(&node, 1);
|
||||
}
|
||||
|
||||
results.push(stream.out());
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Get a state proof from a request. Each proof should be a vector
|
||||
/// of rlp-encoded trie nodes, in ascending order by distance from the root.
|
||||
fn state_proof(&self, req: request::StateProof) -> Vec<Bytes>;
|
||||
|
||||
/// Provide contract code for the specified (block_hash, account_hash) pairs.
|
||||
/// Each item in the resulting vector is either the raw bytecode or empty.
|
||||
fn contract_codes(&self, req: request::ContractCodes) -> Vec<Bytes> {
|
||||
req.code_requests.into_iter()
|
||||
.map(|req| self.contract_code(req))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get contract code by request. Either the raw bytecode or empty.
|
||||
fn contract_code(&self, req: request::ContractCode) -> Bytes;
|
||||
|
||||
/// Provide header proofs from the Canonical Hash Tries as well as the headers
|
||||
/// they correspond to -- each element in the returned vector is a 2-tuple.
|
||||
/// The first element is a block header and the second a merkle proof of
|
||||
/// the header in a requested CHT.
|
||||
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> {
|
||||
use rlp::{self, RlpStream, Stream};
|
||||
|
||||
req.requests.into_iter()
|
||||
.map(|req| self.header_proof(req))
|
||||
.map(|maybe_proof| match maybe_proof {
|
||||
None => rlp::EMPTY_LIST_RLP.to_vec(),
|
||||
Some((header, proof)) => {
|
||||
let mut stream = RlpStream::new_list(2);
|
||||
stream.append_raw(&header.into_inner(), 1).begin_list(proof.len());
|
||||
|
||||
for node in proof {
|
||||
stream.append_raw(&node, 1);
|
||||
}
|
||||
|
||||
stream.out()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Provide a header proof from a given Canonical Hash Trie as well as the
|
||||
/// corresponding header. The first element is the block header and the
|
||||
/// second is a merkle proof of the CHT.
|
||||
fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)>;
|
||||
|
||||
/// Provide pending transactions.
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction>;
|
||||
}
|
||||
|
||||
// Implementation of a light client data provider for a client.
|
||||
impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
BlockChainClient::chain_info(self)
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
|
||||
self.tree_route(a, b).map(|route| route.index as u64)
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
Some(self.pruning_info().earliest_state)
|
||||
}
|
||||
|
||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||
BlockChainClient::block_header(self, id)
|
||||
}
|
||||
|
||||
fn block_body(&self, id: BlockId) -> Option<encoded::Body> {
|
||||
BlockChainClient::block_body(self, id)
|
||||
}
|
||||
|
||||
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
||||
BlockChainClient::block_receipts(self, hash)
|
||||
}
|
||||
|
||||
fn state_proof(&self, req: request::StateProof) -> Vec<Bytes> {
|
||||
match req.key2 {
|
||||
Some(key2) => self.prove_storage(req.key1, key2, req.from_level, BlockId::Hash(req.block)),
|
||||
None => self.prove_account(req.key1, req.from_level, BlockId::Hash(req.block)),
|
||||
}
|
||||
}
|
||||
|
||||
fn contract_code(&self, req: request::ContractCode) -> Bytes {
|
||||
self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash))
|
||||
}
|
||||
|
||||
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
BlockChainClient::ready_transactions(self)
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! LES request types.
|
||||
|
||||
use util::H256;
|
||||
|
||||
/// Either a hash or a number.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub enum HashOrNumber {
|
||||
/// Block hash variant.
|
||||
Hash(H256),
|
||||
/// Block number variant.
|
||||
Number(u64),
|
||||
}
|
||||
|
||||
impl From<H256> for HashOrNumber {
|
||||
fn from(hash: H256) -> Self {
|
||||
HashOrNumber::Hash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for HashOrNumber {
|
||||
fn from(num: u64) -> Self {
|
||||
HashOrNumber::Number(num)
|
||||
}
|
||||
}
|
||||
|
||||
/// A request for block headers.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct Headers {
|
||||
/// Starting block number or hash.
|
||||
pub start: HashOrNumber,
|
||||
/// The maximum amount of headers which can be returned.
|
||||
pub max: usize,
|
||||
/// The amount of headers to skip between each response entry.
|
||||
pub skip: u64,
|
||||
/// Whether the headers should proceed in falling number from the initial block.
|
||||
pub reverse: bool,
|
||||
}
|
||||
|
||||
/// A request for specific block bodies.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct Bodies {
|
||||
/// Hashes which bodies are being requested for.
|
||||
pub block_hashes: Vec<H256>
|
||||
}
|
||||
|
||||
/// A request for transaction receipts.
|
||||
///
|
||||
/// This request is answered with a list of transaction receipts for each block
|
||||
/// requested.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct Receipts {
|
||||
/// Block hashes to return receipts for.
|
||||
pub block_hashes: Vec<H256>,
|
||||
}
|
||||
|
||||
/// A request for a state proof
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct StateProof {
|
||||
/// Block hash to query state from.
|
||||
pub block: H256,
|
||||
/// Key of the state trie -- corresponds to account hash.
|
||||
pub key1: H256,
|
||||
/// Key in that account's storage trie; if empty, then the account RLP should be
|
||||
/// returned.
|
||||
pub key2: Option<H256>,
|
||||
/// if greater than zero, trie nodes beyond this level may be omitted.
|
||||
pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep.
|
||||
}
|
||||
|
||||
/// A request for state proofs.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct StateProofs {
|
||||
/// All the proof requests.
|
||||
pub requests: Vec<StateProof>,
|
||||
}
|
||||
|
||||
/// A request for contract code.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct ContractCode {
|
||||
/// Block hash
|
||||
pub block_hash: H256,
|
||||
/// Account key (== sha3(address))
|
||||
pub account_key: H256,
|
||||
}
|
||||
|
||||
/// A request for contract code.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct ContractCodes {
|
||||
/// Block hash and account key (== sha3(address)) pairs to fetch code for.
|
||||
pub code_requests: Vec<ContractCode>,
|
||||
}
|
||||
|
||||
/// A request for a header proof from the Canonical Hash Trie.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct HeaderProof {
|
||||
/// Number of the CHT.
|
||||
pub cht_number: u64,
|
||||
/// Block number requested.
|
||||
pub block_number: u64,
|
||||
/// If greater than zero, trie nodes beyond this level may be omitted.
|
||||
pub from_level: u32,
|
||||
}
|
||||
|
||||
/// A request for header proofs from the CHT.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct HeaderProofs {
|
||||
/// All the proof requests.
|
||||
pub requests: Vec<HeaderProof>,
|
||||
}
|
||||
|
||||
/// Kinds of requests.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub enum Kind {
|
||||
/// Requesting headers.
|
||||
Headers,
|
||||
/// Requesting block bodies.
|
||||
Bodies,
|
||||
/// Requesting transaction receipts.
|
||||
Receipts,
|
||||
/// Requesting proofs of state trie nodes.
|
||||
StateProofs,
|
||||
/// Requesting contract code by hash.
|
||||
Codes,
|
||||
/// Requesting header proofs (from the CHT).
|
||||
HeaderProofs,
|
||||
}
|
||||
|
||||
/// Encompasses all possible types of requests in a single structure.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub enum Request {
|
||||
/// Requesting headers.
|
||||
Headers(Headers),
|
||||
/// Requesting block bodies.
|
||||
Bodies(Bodies),
|
||||
/// Requesting transaction receipts.
|
||||
Receipts(Receipts),
|
||||
/// Requesting state proofs.
|
||||
StateProofs(StateProofs),
|
||||
/// Requesting contract codes.
|
||||
Codes(ContractCodes),
|
||||
/// Requesting header proofs.
|
||||
HeaderProofs(HeaderProofs),
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Get the kind of request this is.
|
||||
pub fn kind(&self) -> Kind {
|
||||
match *self {
|
||||
Request::Headers(_) => Kind::Headers,
|
||||
Request::Bodies(_) => Kind::Bodies,
|
||||
Request::Receipts(_) => Kind::Receipts,
|
||||
Request::StateProofs(_) => Kind::StateProofs,
|
||||
Request::Codes(_) => Kind::Codes,
|
||||
Request::HeaderProofs(_) => Kind::HeaderProofs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the amount of requests being made.
|
||||
pub fn amount(&self) -> usize {
|
||||
match *self {
|
||||
Request::Headers(ref req) => req.max,
|
||||
Request::Bodies(ref req) => req.block_hashes.len(),
|
||||
Request::Receipts(ref req) => req.block_hashes.len(),
|
||||
Request::StateProofs(ref req) => req.requests.len(),
|
||||
Request::Codes(ref req) => req.code_requests.len(),
|
||||
Request::HeaderProofs(ref req) => req.requests.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Types used in the public (IPC) api which require custom code generation.
|
||||
#![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs.in"));
|
||||
|
||||
#[cfg(not(feature = "ipc"))]
|
||||
include!("mod.rs.in");
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "TestAuthorityRound",
|
||||
"engine": {
|
||||
"authorityRound": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"stepDuration": 1,
|
||||
"startStep": 2,
|
||||
"validators": {
|
||||
"list": [
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
|
||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x69"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"authorityRound": {
|
||||
"step": "0x0",
|
||||
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x20000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x2fefd8"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user