Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70389b2888 | ||
|
|
8f63f6d32a | ||
|
|
f02f99cd3f | ||
|
|
bbbd6f01fc | ||
|
|
ddbce9f014 | ||
|
|
423c5e87da | ||
|
|
a15d2882d0 | ||
|
|
6d5809757f | ||
|
|
e179b0e0c2 | ||
|
|
37eb0fb04e | ||
|
|
32b0a80a18 | ||
|
|
9df9937922 | ||
|
|
eae66568a9 | ||
|
|
ece348d173 | ||
|
|
cc0c54a45c | ||
|
|
3f139116b2 | ||
|
|
771e3998a9 | ||
|
|
03fbde9723 | ||
|
|
3303aa73ff | ||
|
|
8367be6992 | ||
|
|
e6237ed05e | ||
|
|
0abdcedc03 | ||
|
|
59a735e2c4 | ||
|
|
bf24355480 | ||
|
|
4ed15931af | ||
|
|
c5553c1f21 | ||
|
|
33d633eed1 | ||
|
|
a3a0de096d | ||
|
|
aed708ffd8 | ||
|
|
66d3d7ec11 | ||
|
|
8503aeb2df | ||
|
|
a7ebc1dd25 | ||
|
|
584d89f964 | ||
|
|
4aeaab3a2f | ||
|
|
9750a31336 | ||
|
|
404005cc93 | ||
|
|
9b5bd37dc5 | ||
|
|
f96e5bc0d5 | ||
|
|
444c9c4375 | ||
|
|
2a2c24855c | ||
|
|
712302391c | ||
|
|
3023d76a46 | ||
|
|
f60d74b231 | ||
|
|
3e192ab486 | ||
|
|
fe313677b3 | ||
|
|
20f3f356e5 | ||
|
|
b9141b830b | ||
|
|
a938385692 | ||
|
|
c9ac0392d8 | ||
|
|
02dfc8632e | ||
|
|
ac2a62707a | ||
|
|
6f78523bf0 | ||
|
|
5dbbf9db13 | ||
|
|
0d6ffa2a97 | ||
|
|
3afb6370f9 | ||
|
|
dad4bfecdf | ||
|
|
902b44c94f | ||
|
|
54bf237e4f | ||
|
|
f835ce8253 | ||
|
|
4d04668c48 | ||
|
|
b4d95089d4 | ||
|
|
cca30f964e | ||
|
|
f11cc80561 | ||
|
|
4f71fd5d4d | ||
|
|
0340466f5f | ||
|
|
8847819b31 | ||
|
|
ca91fd5e76 | ||
|
|
df2619a37d | ||
|
|
4840371635 | ||
|
|
68e4156143 | ||
|
|
83f812b176 | ||
|
|
36c5525e3f | ||
|
|
78b5c12cf9 | ||
|
|
0564e7c168 | ||
|
|
7a4e91d26d | ||
|
|
ec26827f58 | ||
|
|
01b71bf9ba | ||
|
|
42040438ed | ||
|
|
799828b51e | ||
|
|
f78e8c3a38 | ||
|
|
4f242996d1 | ||
|
|
daecf5db3e | ||
|
|
9a09f52679 | ||
|
|
841a996fa6 | ||
|
|
ff99ccaf82 | ||
|
|
60f2aadb8e | ||
|
|
c143b1b783 | ||
|
|
2377d46c59 | ||
|
|
10d361aec5 | ||
|
|
bf4e4df4c8 | ||
|
|
3b766f01f2 | ||
|
|
2aa3864d67 | ||
|
|
04082d1514 | ||
|
|
85fa07728c | ||
|
|
a36ac996a0 | ||
|
|
6e666ecac7 | ||
|
|
08dc83205c | ||
|
|
bff142828d | ||
|
|
6d1ae888ca | ||
|
|
d5cc2c9e67 | ||
|
|
048dba7ef3 | ||
|
|
280c5e8a99 | ||
|
|
1257ad2993 | ||
|
|
5c72e60b14 | ||
|
|
aa6b6e477c | ||
|
|
245ef854dd | ||
|
|
c2cffd1e06 | ||
|
|
ddab958e61 | ||
|
|
a10160bd56 | ||
|
|
49be60e984 | ||
|
|
2b8a151b82 | ||
|
|
9bcc6c9e69 | ||
|
|
0fc605face | ||
|
|
8f1618687c | ||
|
|
cb246c26c0 | ||
|
|
086e45671b | ||
|
|
f3ad643b69 | ||
|
|
c5a4993e39 | ||
|
|
32cc0bddf6 | ||
|
|
d87286c5c3 | ||
|
|
ac3acdef71 | ||
|
|
dffa9bcef6 | ||
|
|
9ce75a173e | ||
|
|
755faf37e3 | ||
|
|
de21fef868 | ||
|
|
39aa02ed26 | ||
|
|
7a0dfec115 | ||
|
|
1fa019d92a | ||
|
|
7105ed3f61 | ||
|
|
2f411c93fd | ||
|
|
808f959a94 | ||
|
|
2aae862330 | ||
|
|
b18a7bde9a | ||
|
|
a0cdf5c420 | ||
|
|
b3dd72ab0a | ||
|
|
4d984ead4f | ||
|
|
fbc85e31c2 | ||
|
|
177d26a25e | ||
|
|
1f363b22fc | ||
|
|
5aed421fa3 | ||
|
|
f4df9c8dfd | ||
|
|
144385e67d | ||
|
|
9b6abb5861 | ||
|
|
6c81f1fb74 | ||
|
|
7371beb26c | ||
|
|
7acdc80b27 | ||
|
|
3847f8a9fc | ||
|
|
56e977b425 | ||
|
|
2f8e6be2ea | ||
|
|
180debb260 | ||
|
|
b79c7f05c0 | ||
|
|
d17c629b71 | ||
|
|
4be4a1c151 | ||
|
|
b0d7d48391 | ||
|
|
164cd268ad | ||
|
|
3a16ef19c1 | ||
|
|
74ea9cc74c | ||
|
|
2e88183df4 | ||
|
|
fca8eb5810 | ||
|
|
df4b326cb3 | ||
|
|
4db397ecb1 | ||
|
|
e767fd88f6 | ||
|
|
fae7039fda | ||
|
|
69d6f3ed3e | ||
|
|
4292e75dca | ||
|
|
2b4640c94c | ||
|
|
42a6954ec9 | ||
|
|
114bab66c4 | ||
|
|
13d0d6d006 | ||
|
|
c15e78cd95 | ||
|
|
24993b8d4e | ||
|
|
69d89583ca | ||
|
|
3c8fb60cbd | ||
|
|
9f8a131fa4 | ||
|
|
31f4a214a9 | ||
|
|
81f8f939b9 | ||
|
|
28e4847b59 | ||
|
|
30ecff3959 | ||
|
|
8add773b12 | ||
|
|
c20e761051 | ||
|
|
35be0e9943 | ||
|
|
3bf0a00ba4 | ||
|
|
570d2c21d3 | ||
|
|
b9fc5bdda0 | ||
|
|
1964ed7eb3 | ||
|
|
60bcab3e48 | ||
|
|
ebb7699da2 | ||
|
|
78821bb935 | ||
|
|
e8a21116f7 | ||
|
|
9f81b07224 | ||
|
|
8eeda2b1a3 | ||
|
|
0087568c16 | ||
|
|
75a11ba9be | ||
|
|
4ac9807bc1 | ||
|
|
1c26ffa208 | ||
|
|
f8b18d5d7b | ||
|
|
b327e79b42 | ||
|
|
8e2f35856a | ||
|
|
a029551b0b | ||
|
|
320581a3e4 | ||
|
|
f77b4119da |
@@ -1,2 +0,0 @@
|
||||
[target.armv7-unknown-linux-gnueabihf]
|
||||
linker= "arm-linux-gnueabihf-gcc"
|
||||
@@ -9,8 +9,3 @@ trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
||||
[.travis.yml]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=8
|
||||
end_of_line=lf
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -30,5 +30,3 @@
|
||||
|
||||
# Build artifacts
|
||||
out/
|
||||
|
||||
.vscode
|
||||
|
||||
429
.gitlab-ci.yml
429
.gitlab-ci.yml
@@ -1,429 +0,0 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- build
|
||||
variables:
|
||||
GIT_DEPTH: "3"
|
||||
SIMPLECOV: "true"
|
||||
RUST_BACKTRACE: "1"
|
||||
RUSTFLAGS: ""
|
||||
CARGOFLAGS: ""
|
||||
cache:
|
||||
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
image: ethcore/rust:stable
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip 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
|
||||
- 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
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-beta:
|
||||
stage: build
|
||||
image: ethcore/rust:beta
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
- rust-beta
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "beta-x86_64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-nightly:
|
||||
stage: build
|
||||
image: ethcore/rust:nightly
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
- rust-nightly
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "nigthly-x86_64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-centos:
|
||||
stage: build
|
||||
image: ethcore/rust-centos:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- export CXX="g++"
|
||||
- export CC="gcc"
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- strip 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
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
|
||||
tags:
|
||||
- rust
|
||||
- rust-centos
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: ethcore/rust-i686:latest
|
||||
only:
|
||||
- beta
|
||||
# - tags
|
||||
- stable
|
||||
script:
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- 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/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"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-i686
|
||||
artifacts:
|
||||
paths:
|
||||
- target/i686-unknown-linux-gnu/release/parity
|
||||
name: "i686-unknown-linux-gnu"
|
||||
allow_failure: true
|
||||
linux-armv7:
|
||||
stage: build
|
||||
image: ethcore/rust-armv7:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- 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/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"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
name: "armv7_unknown_linux_gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-arm:
|
||||
stage: build
|
||||
image: ethcore/rust-arm:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- 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/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"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/arm-unknown-linux-gnueabihf/release/parity
|
||||
name: "arm-unknown-linux-gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-armv6:
|
||||
stage: build
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
# - beta
|
||||
# - tags
|
||||
- stable
|
||||
script:
|
||||
- export CC=arm-linux-gnueabi-gcc
|
||||
- export CXX=arm-linux-gnueabi-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build --target arm-unknown-linux-gnueabi --release $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
|
||||
- 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
|
||||
artifacts:
|
||||
paths:
|
||||
- target/arm-unknown-linux-gnueabi/release/parity
|
||||
name: "arm-unknown-linux-gnueabi_parity"
|
||||
allow_failure: true
|
||||
linux-aarch64:
|
||||
stage: build
|
||||
image: ethcore/rust-aarch64:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- export CC=aarch64-linux-gnu-gcc
|
||||
- export CXX=aarch64-linux-gnu-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config
|
||||
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build --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/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_arm64.deb"
|
||||
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- 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
|
||||
artifacts:
|
||||
paths:
|
||||
- target/aarch64-unknown-linux-gnu/release/parity
|
||||
name: "aarch64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
darwin:
|
||||
stage: build
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- cargo build --release -p ethstore $CARGOFLAGS
|
||||
- cargo build --release $CARGOFLAGS
|
||||
- rm -rf parity.md5
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- packagesbuild -v mac/Parity.pkgproj
|
||||
- mv target/release/Parity\ Ethereum.pkg parity-osx-installer-EXPERIMENTAL.pkg
|
||||
- md5sum parity-osx-installer-EXPERIMENTAL.pkg > parity-osx-installer-EXPERIMENTAL.pkg.md5
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity-osx-installer-EXPERIMENTAL.pkg --body parity-osx-installer-EXPERIMENTAL.pkg
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity-osx-installer-EXPERIMENTAL.pkg.md5 --body parity-osx-installer-EXPERIMENTAL.pkg.md5
|
||||
tags:
|
||||
- osx
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-apple-darwin_parity"
|
||||
windows:
|
||||
cache:
|
||||
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
|
||||
untracked: true
|
||||
stage: build
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
script:
|
||||
- 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% -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
|
||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
|
||||
- cd nsis
|
||||
- makensis.exe installer.nsi
|
||||
- copy installer.exe InstallParity.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
|
||||
- md5sums InstallParity.exe > InstallParity.exe.md5
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
- md5sums win-installer.zip > win-installer.zip.md5
|
||||
- cd ..\target\release\
|
||||
- md5sums parity.exe parity.pdb > parity.md5
|
||||
- md5sums parity.exe > parity.exe.md5
|
||||
- zip parity.zip parity.exe parity.pdb parity.md5
|
||||
- md5sums parity.zip > parity.zip.md5
|
||||
- cd ..\..
|
||||
- aws configure set aws_access_key_id %s3_key%
|
||||
- aws configure set aws_secret_access_key %s3_secret%
|
||||
- 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:
|
||||
paths:
|
||||
- target/release/parity.exe
|
||||
- target/release/parity.pdb
|
||||
- nsis/InstallParity.exe
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
test-darwin:
|
||||
stage: test
|
||||
only:
|
||||
# - beta
|
||||
# - tags
|
||||
- stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- ./test.sh $CARGOFLAGS --no-release
|
||||
tags:
|
||||
- osx
|
||||
test-windows:
|
||||
stage: test
|
||||
only:
|
||||
# - beta
|
||||
# - tags
|
||||
- stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
script:
|
||||
- set RUST_BACKTRACE=1
|
||||
- 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
|
||||
test-rust-stable:
|
||||
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@{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
|
||||
- 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-tests:
|
||||
stage: test
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- ./js/scripts/install-deps.sh
|
||||
script:
|
||||
- ./js/scripts/lint.sh
|
||||
- ./js/scripts/test.sh
|
||||
- ./js/scripts/build.sh
|
||||
tags:
|
||||
- javascript-test
|
||||
js-release:
|
||||
stage: js-build
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- stable
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- ./js/scripts/install-deps.sh
|
||||
script:
|
||||
- ./js/scripts/build.sh
|
||||
- ./js/scripts/release.sh
|
||||
tags:
|
||||
- javascript
|
||||
109
.travis.yml
109
.travis.yml
@@ -1,7 +1,6 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: rust
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -9,78 +8,96 @@ branches:
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
include:
|
||||
- rust: stable
|
||||
env: RUN_TESTS="true" TEST_OPTIONS="--no-release"
|
||||
- rust: beta
|
||||
env: RUN_COVERAGE="true"
|
||||
env: FEATURES="--features travis-beta" RUN_TESTS="true"
|
||||
# - rust: beta
|
||||
# env: FEATURES="--features travis-beta" RUN_TESTS="true"
|
||||
- rust: stable
|
||||
env: RUN_DOCS="true"
|
||||
|
||||
env: FEATURES="--features travis-beta" RUN_BUILD="true"
|
||||
- rust: beta
|
||||
env: FEATURES="--features travis-beta" RUN_BUILD="true"
|
||||
- rust: stable
|
||||
env: FEATURES="--features travis-beta" RUN_COVERAGE="true"
|
||||
# - rust: nightly
|
||||
# env: FEATURES="--features travis-nightly" RUN_BENCHES="true"
|
||||
- rust: nightly
|
||||
env: FEATURES="--features travis-nightly" RUN_TESTS="true"
|
||||
env:
|
||||
global:
|
||||
- CXX="g++-4.8"
|
||||
- CC="gcc-4.8"
|
||||
- RUST_BACKTRACE="1"
|
||||
# GH_TOKEN
|
||||
- secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw=
|
||||
- CVER=5
|
||||
- NUM_JOBS=1
|
||||
# - TARGETS="-p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer"
|
||||
- TARGETS="-p ethcore-util"
|
||||
- ARCHIVE_SUFFIX="-${TRAVIS_OS_NAME}-${TRAVIS_TAG}"
|
||||
- KCOV_FEATURES=""
|
||||
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /usr/,/.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov"
|
||||
- RUN_TESTS="false"
|
||||
- RUN_COVERAGE="false"
|
||||
- RUN_DOCS="false"
|
||||
- TEST_OPTIONS=""
|
||||
- RUSTFLAGS="-D warnings"
|
||||
- TRAVIS_NODE_VERSION="6"
|
||||
# GH_TOKEN for documentation
|
||||
- secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw=
|
||||
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov"
|
||||
|
||||
- RUN_BUILD="false"
|
||||
- RUN_BENCHES="false"
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- $TRAVIS_BUILD_DIR/target
|
||||
- $TRAVIS_BUILD_DIR/kcov-master
|
||||
- $TRAVIS_BUILD_DIR/js/node_modules
|
||||
- $HOME/.cargo
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
- gcc-4.8
|
||||
- g++-4.8
|
||||
|
||||
install:
|
||||
- ([ "$RUN_COVERAGE" = "false" ]) || (test -x $KCOV_CMD) || (
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
tar xzf master.tar.gz &&
|
||||
mkdir -p kcov-master/build &&
|
||||
cd kcov-master/build &&
|
||||
cmake .. &&
|
||||
make && make install DESTDIR=../tmp &&
|
||||
cd
|
||||
)
|
||||
- nvm install $TRAVIS_NODE_VERSION && nvm use $TRAVIS_NODE_VERSION && ./js/scripts/install-deps.sh
|
||||
|
||||
script:
|
||||
- if [ "$RUN_TESTS" = "true" ]; then
|
||||
./js/scripts/lint.sh &&
|
||||
./js/scripts/test.sh &&
|
||||
./test.sh $TEST_OPTIONS --verbose;
|
||||
fi
|
||||
- if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi
|
||||
- echo "$TRAVIS_BUILD_DIR/target"
|
||||
- ls "$TRAVIS_BUILD_DIR/target"
|
||||
- if [ "$RUN_TESTS" = "true" ]; then cargo test --release --verbose ${FEATURES} ${TARGETS}; fi
|
||||
- if [ "$RUN_BENCHES" = "true" ]; then cargo bench --no-run ${FEATURES} ${TARGETS}; fi
|
||||
- if [ "$RUN_BUILD" = "true" ]; then cargo build --release --verbose ${FEATURES}; fi
|
||||
- if [ "$RUN_BUILD" = "true" ]; then tar cvzf parity${ARCHIVE_SUFFIX}.tar.gz -C target/release parity; fi
|
||||
|
||||
after_success: |
|
||||
[ "$RUN_COVERAGE" = "true" ] &&
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && cmake .. && make && make install DESTDIR=../tmp && cd ../.. &&
|
||||
cargo test --no-run ${KCOV_FEATURES} ${TARGETS} &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_util-* &&
|
||||
$KCOV_CMD target/debug/deps/ethash-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore-* &&
|
||||
$KCOV_CMD target/debug/deps/ethsync-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_rpc-* &&
|
||||
$KCOV_CMD target/debug/deps/ethminer-* &&
|
||||
$KCOV_CMD target/debug/parity-* &&
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
[ "$RUN_DOCS" = "true" ] &&
|
||||
./scripts/doc.sh &&
|
||||
[ $TRAVIS_RUST_VERSION = stable ] &&
|
||||
cargo doc --no-deps --verbose ${KCOV_FEATURES} ${TARGETS} &&
|
||||
echo '<meta http-equiv=refresh content=0;url=ethcore/index.html>' > target/doc/index.html &&
|
||||
pip install --user ghp-import &&
|
||||
/home/travis/.local/bin/ghp-import -n target/doc &&
|
||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "t+oGT/4lsy7IScw5s86Dpntl5Nyck4qG6nhHwMScc6FYzwLldgwgJaafL8Ej+HG+b7nFLriN+Snoa4YQ5o74X5ZlSWubVREOYQlL/fq7vcPB0DwAZ0Jufq1QW2R1M+3SwwF1eAwTv2W3G7A2K7dxjCVvENcy/gdxnZ36NeUPsqaCC9UcI2Yc7+4jyQwvx6ZfBvQeu+HbKENA0eUNs2ZQOID/1IPy0LJBvSyxAQYsysXdjTzGdNu4+Iba20E8uWYe4fAbgz+gwGarXg1L6D6gKyMlWkViqWjvXWBuDJJqMQZ3rw41AwZOoh3mKd2Lc0l6l4oZcEqPuob0yKTNjz1tuJy9xKTC2F2bDzsvUgk1IRfMK5ukXXXS09ZCZWuA9/GtnsqJ1xGTiwX+DhQzpVBHaBiseSNlYE1YN/3jNyGY+iSts1qut+1BwE7swmcTLsAPoAy8Ue+f7ErNoCg1lm71vq7VO2DLn7x2NqHyHUEuJ+7olDHSdE84G7d9otDRu/+TfMOw7GXwTaha6yJRInuNsnj4CFMLNVvYACzCC2idB7f7nUZoSFi9jf18S9fCMPVmazMrFj4g95HWrVHkjpV5zRTeUdTWw6DJl6pC9HFqORHdCvLv4Rc4dm5r3CmOcAQ0ZuiccV2oKzw4/Wic96daae8M5f5KSQ/WTr+h0wXZKp0="
|
||||
skip_cleanup: true
|
||||
file: parity${ARCHIVE_SUFFIX}.tar.gz
|
||||
on:
|
||||
tags: true
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://hooks.slack.com/services/${SLACK_WEBHOOK}
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: never
|
||||
|
||||
1764
Cargo.lock
generated
1764
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
72
Cargo.toml
72
Cargo.toml
@@ -1,15 +1,13 @@
|
||||
[package]
|
||||
description = "Ethcore client."
|
||||
name = "parity"
|
||||
version = "1.4.2"
|
||||
version = "1.0.2"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.1"
|
||||
ethcore-ipc-codegen = { path = "ipc/codegen" }
|
||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
@@ -17,69 +15,26 @@ env_logger = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
docopt = "0.6"
|
||||
time = "0.1"
|
||||
ctrlc = { git = "https://github.com/tomusdrw/rust-ctrlc.git" }
|
||||
fdlimit = { path = "util/fdlimit" }
|
||||
daemonize = "0.2"
|
||||
num_cpus = "0.2"
|
||||
number_prefix = "0.2"
|
||||
rpassword = "0.2.1"
|
||||
semver = "0.2"
|
||||
ansi_term = "0.7"
|
||||
lazy_static = "0.2"
|
||||
regex = "0.1"
|
||||
isatty = "0.1"
|
||||
toml = "0.2"
|
||||
serde = "0.8.0"
|
||||
serde_json = "0.8.0"
|
||||
hyper = { version = "0.9", default-features = false }
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" }
|
||||
fdlimit = { path = "util/fdlimit" }
|
||||
rpassword = "0.1"
|
||||
clippy = { version = "0.0.54", optional = true }
|
||||
ethcore = { path = "ethcore" }
|
||||
ethcore-util = { path = "util" }
|
||||
ethsync = { path = "sync" }
|
||||
ethcore-io = { path = "util/io" }
|
||||
ethminer = { path = "miner" }
|
||||
ethcore-devtools = { path = "devtools" }
|
||||
ethcore-rpc = { path = "rpc" }
|
||||
ethcore-signer = { path = "signer" }
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
||||
ethcore-logger = { path = "logger" }
|
||||
rlp = { path = "util/rlp" }
|
||||
ethcore-stratum = { path = "stratum" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
daemonize = "0.2"
|
||||
ethcore-rpc = { path = "rpc", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
|
||||
ui = [
|
||||
"dapps",
|
||||
"ethcore-dapps/ui",
|
||||
"ethcore-signer/ui",
|
||||
]
|
||||
ui-precompiled = [
|
||||
"dapps",
|
||||
"ethcore-signer/ui-precompiled",
|
||||
"ethcore-dapps/ui-precompiled",
|
||||
]
|
||||
|
||||
dapps = ["ethcore-dapps"]
|
||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||
jit = ["ethcore/jit"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
|
||||
json-tests = ["ethcore/json-tests"]
|
||||
test-heavy = ["ethcore/test-heavy"]
|
||||
stratum = ["ipc"]
|
||||
ethkey-cli = ["ethcore/ethkey-cli"]
|
||||
ethstore-cli = ["ethcore/ethstore-cli"]
|
||||
evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
default = ["rpc"]
|
||||
rpc = ["ethcore-rpc"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev"]
|
||||
travis-beta = ["ethcore/json-tests"]
|
||||
travis-nightly = ["ethcore/json-tests", "dev"]
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
@@ -88,4 +43,3 @@ name = "parity"
|
||||
[profile.release]
|
||||
debug = false
|
||||
lto = false
|
||||
|
||||
|
||||
110
README.md
110
README.md
@@ -1,17 +1,6 @@
|
||||
# [Parity](https://ethcore.io/parity.html)
|
||||
### Fast, light, and robust Ethereum implementation
|
||||
# ethcore
|
||||
|
||||
[![Build Status][travis-image]][travis-url] [](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
|
||||
|
||||
### Join the chat!
|
||||
|
||||
Parity [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] and
|
||||
parity.js [](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
[Internal Documentation][doc-url]
|
||||
|
||||
|
||||
Be sure to check out [our wiki][wiki-url] for more information.
|
||||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/trogdoro/xiki][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url]
|
||||
|
||||
[travis-image]: https://travis-ci.org/ethcore/parity.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/ethcore/parity
|
||||
@@ -20,97 +9,32 @@ Be sure to check out [our wiki][wiki-url] for more information.
|
||||
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
|
||||
[gitter-url]: https://gitter.im/ethcore/parity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
[license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg
|
||||
[license-url]: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
[doc-url]: https://ethcore.github.io/parity/ethcore/index.html
|
||||
[wiki-url]: https://github.com/ethcore/parity/wiki
|
||||
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
**Parity requires Rust version 1.12.0 to build**
|
||||
[Documentation](http://ethcore.github.io/parity/ethcore/index.html)
|
||||
|
||||
----
|
||||
### Building from source
|
||||
|
||||
First (if you don't already have it) get multirust:
|
||||
|
||||
## About Parity
|
||||
|
||||
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and
|
||||
cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
|
||||
|
||||
Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It
|
||||
includes various functionality allowing you to:
|
||||
- create and manage your Ethereum accounts;
|
||||
- manage your Ether and any Ethereum tokens;
|
||||
- create and register your own tokens;
|
||||
- and much more.
|
||||
|
||||
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
|
||||
of RPC APIs.
|
||||
|
||||
If you run into an issue while using parity, feel free to file one in this repository
|
||||
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
|
||||
|
||||
Parity's current release is 1.3. You can download it at https://ethcore.io/parity.html or follow the instructions
|
||||
below to build from source.
|
||||
|
||||
----
|
||||
|
||||
## Build dependencies
|
||||
|
||||
Parity 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 and OSX:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
- Windows
|
||||
|
||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
|
||||
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the msvc toolchain:
|
||||
```
|
||||
$ rustup default stable-x86_64-pc-windows-msvc
|
||||
```
|
||||
|
||||
Once you have rustup, install parity or download and build from source
|
||||
|
||||
----
|
||||
|
||||
## Quick install
|
||||
|
||||
- Linux:
|
||||
```bash
|
||||
cargo install --git https://github.com/ethcore/parity.git parity
|
||||
curl -sf https://raw.githubusercontent.com/brson/multirust/master/quick-install.sh | sudo sh -s -- --yes
|
||||
```
|
||||
|
||||
----
|
||||
- OSX with Homebrew:
|
||||
```bash
|
||||
brew update && brew install multirust
|
||||
multirust default stable
|
||||
```
|
||||
|
||||
## Build from source
|
||||
Then, download and build Parity:
|
||||
|
||||
```bash
|
||||
# download Parity code
|
||||
$ git clone https://github.com/ethcore/parity
|
||||
$ cd parity
|
||||
git clone https://github.com/ethcore/parity
|
||||
cd parity
|
||||
|
||||
# build in release mode
|
||||
$ cargo build --release
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This will produce an executable in the `./target/release` subdirectory.
|
||||
|
||||
## Start Parity
|
||||
### Manually
|
||||
To start Parity manually, just run
|
||||
```bash
|
||||
$ ./target/release/parity
|
||||
```
|
||||
|
||||
and Parity will begin syncing the Ethereum blockchain.
|
||||
|
||||
### Using systemd service file
|
||||
To start Parity as a regular user using systemd init:
|
||||
|
||||
1. Copy `parity/scripts/parity.service` to your
|
||||
systemd user directory (usually `~/.config/systemd/user`).
|
||||
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
|
||||
`ARGS="ARG1 ARG2 ARG3"`.
|
||||
|
||||
Example: `ARGS="ui --geth --identity MyMachine"`.
|
||||
|
||||
52
appveyor.yml
52
appveyor.yml
@@ -1,52 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
cert:
|
||||
secure: ESPpYVVAMG1fbJx6kq4ct/g9SQTXac4Hs6xXr6Oh4Zrk2dwYglNjxmzErdPnvu7gs/gekzrJ6KEQHYRc+5+4dKg6rRADQ681NLVx9vOggBs=
|
||||
certpass:
|
||||
secure: 0BgXJqxq9Ei34/hZ7121FQ==
|
||||
keyfile: C:\users\appveyor\Certificates.p12
|
||||
RUSTFLAGS: -Zorbit=off -D warnings
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^beta-.*$/
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- ps: Install-Product node 6
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.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.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
|
||||
- node -v
|
||||
- npm -v
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose --release
|
||||
|
||||
after_test:
|
||||
- cargo build --verbose --release
|
||||
- ps: if($env:cert) { Start-FileDownload $env:cert -FileName $env:keyfile }
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass target\release\parity.exe }
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\x64\release\ptray.exe }
|
||||
- makensis.exe nsis\installer.nsi
|
||||
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass nsis\installer.exe }
|
||||
|
||||
artifacts:
|
||||
- path: nsis\installer.exe
|
||||
name: Windows Installer (x86_64)
|
||||
|
||||
cache:
|
||||
- target
|
||||
- C:\users\appveyor\.cargo -> appveyor.yml
|
||||
37
cov.sh
Executable file
37
cov.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
# Installing KCOV under ubuntu
|
||||
# https://users.rust-lang.org/t/tutorial-how-to-collect-test-coverages-for-rust-project/650#
|
||||
### Install deps
|
||||
# $ sudo apt-get install libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
#
|
||||
### Compile kcov
|
||||
# $ wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && tar xf master.tar.gz
|
||||
# $ cd kcov-master && mkdir build && cd build
|
||||
# $ cmake .. && make && sudo make install
|
||||
|
||||
### Running coverage
|
||||
if ! type kcov > /dev/null; then
|
||||
echo "Install kcov first (details inside this file). Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo test \
|
||||
-p ethash \
|
||||
-p ethcore-util \
|
||||
-p ethcore \
|
||||
-p ethsync \
|
||||
-p ethcore-rpc \
|
||||
-p parity \
|
||||
-p ethminer \
|
||||
--no-run || exit $?
|
||||
rm -rf target/coverage
|
||||
mkdir -p target/coverage
|
||||
|
||||
EXCLUDE="~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests"
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethash-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethsync-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethminer-*
|
||||
xdg-open target/coverage/index.html
|
||||
@@ -1,48 +0,0 @@
|
||||
[package]
|
||||
description = "Parity Dapps crate"
|
||||
name = "ethcore-dapps"
|
||||
version = "1.4.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io"]
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3.14"
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
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"
|
||||
time = "0.1.35"
|
||||
serde_macros = { version = "0.8", optional = true }
|
||||
zip = { version = "0.1", default-features = false }
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-rpc = { path = "../rpc" }
|
||||
ethcore-util = { path = "../util" }
|
||||
fetch = { path = "../util/fetch" }
|
||||
parity-ui = { path = "./ui" }
|
||||
|
||||
mime_guess = { version = "1.6.1" }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.8", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["serde_codegen"]
|
||||
nightly = ["serde_macros"]
|
||||
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
|
||||
|
||||
ui = ["parity-ui/no-precompiled-js"]
|
||||
ui-precompiled = ["parity-ui/use-precompiled-js"]
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
mod inner {
|
||||
extern crate serde_codegen;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
let src = Path::new("./src/api/types.rs.in");
|
||||
let dst = Path::new(&out_dir).join("types.rs");
|
||||
|
||||
serde_codegen::expand(&src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[package]
|
||||
description = "Base Package for all Parity built-in dapps"
|
||||
name = "parity-dapps-glue"
|
||||
version = "1.4.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
quasi_codegen = { version = "0.11", optional = true }
|
||||
syntex = { version = "0.33", optional = true }
|
||||
|
||||
[dependencies]
|
||||
glob = { version = "0.2.11" }
|
||||
mime_guess = { version = "1.6.1" }
|
||||
aster = { version = "0.17", default-features = false }
|
||||
quasi = { version = "0.11", default-features = false }
|
||||
quasi_macros = { version = "0.11", optional = true }
|
||||
syntex = { version = "0.33", optional = true }
|
||||
syntex_syntax = { version = "0.33", optional = true }
|
||||
clippy = { version = "0.0.90", optional = true }
|
||||
|
||||
[features]
|
||||
dev = ["clippy"]
|
||||
default = ["with-syntex"]
|
||||
nightly = ["quasi_macros"]
|
||||
nightly-testing = ["clippy"]
|
||||
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
|
||||
use-precompiled-js = []
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Parity Dapps (JS-glue)
|
||||
|
||||
Code generator to simplify creating a built-in Parity Dapp
|
||||
|
||||
# How to create new builtin Dapp.
|
||||
1. Clone this repository.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/ethcore/parity.git
|
||||
```
|
||||
|
||||
1. Create a new directory for your Dapp. (`./myapp`)
|
||||
|
||||
```bash
|
||||
$ mkdir -p ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Copy your frontend files to `./dapps/myapp/src/web` (bundled ones)
|
||||
|
||||
```bash
|
||||
$ cp -r ./myapp-src/* ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Instead of creating `web3` in your app. Load (as the first script tag in `head`):
|
||||
|
||||
```html
|
||||
<script src="/parity-utils/inject.js"></script>
|
||||
```
|
||||
|
||||
The `inject.js` script will create global `web3` instance with proper provider that should be used by your dapp.
|
||||
|
||||
1. Create `./parity/dapps/myapp/Cargo.toml` with you apps details. See example here: [parity-status Cargo.toml](https://github.com/ethcore/parity-ui/blob/master/status/Cargo.toml).
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/ethcore/parity-ui.git
|
||||
$ cd ./parity-ui/
|
||||
$ cp ./home/Cargo.toml ../parity/dapps/myapp/Cargo.toml
|
||||
$ cp ./home/build.rs ../parity/dapps/myapp/build.rs
|
||||
$ cp ./home/src/lib.rs ../parity/dapps/myapp/src/lib.rs
|
||||
$ cp ./home/src/lib.rs.in ../parity/dapps/myapp/src/lib.rs.in
|
||||
# And edit the details of your app
|
||||
$ vim ../parity/dapps/myapp/Cargo.toml # Edit the details
|
||||
$ vim ./parity/dapps/myapp/src/lib.rs.in # Edit the details
|
||||
```
|
||||
# How to include your Dapp into `Parity`?
|
||||
1. Edit `dapps/Cargo.toml` and add dependency to your application (it can be optional)
|
||||
|
||||
```toml
|
||||
# Use git repo and version
|
||||
parity-dapps-myapp = { path="./myapp" }
|
||||
```
|
||||
|
||||
1. Edit `dapps/src/apps.rs` and add your application to `all_pages` (if it's optional you need to specify two functions - see `parity-dapps-wallet` example)
|
||||
|
||||
1. Compile parity.
|
||||
|
||||
```bash
|
||||
$ cargo build --release # While inside `parity`
|
||||
```
|
||||
|
||||
1. Commit the results.
|
||||
|
||||
```bash
|
||||
$ git add myapp && git commit -am "My first Parity Dapp".
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
mod inner {
|
||||
extern crate syntex;
|
||||
extern crate quasi_codegen;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut registry = syntex::Registry::new();
|
||||
quasi_codegen::register(&mut registry);
|
||||
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
pub mod inner {
|
||||
use syntex;
|
||||
use codegen;
|
||||
use syntax::{ast, fold};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
fn strip_attributes(krate: ast::Crate) -> ast::Crate {
|
||||
/// Helper folder that strips the serde attributes after the extensions have been expanded.
|
||||
struct StripAttributeFolder;
|
||||
|
||||
impl fold::Folder for StripAttributeFolder {
|
||||
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
|
||||
match attr.node.value.node {
|
||||
ast::MetaItemKind::List(ref n, _) if n == &"webapp" => { return None; }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Some(attr)
|
||||
}
|
||||
|
||||
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
|
||||
fold::noop_fold_mac(mac, self)
|
||||
}
|
||||
}
|
||||
|
||||
fold::Folder::fold_crate(&mut StripAttributeFolder, krate)
|
||||
}
|
||||
|
||||
pub fn register(reg: &mut syntex::Registry) {
|
||||
reg.add_attr("feature(custom_derive)");
|
||||
reg.add_attr("feature(custom_attribute)");
|
||||
|
||||
reg.add_decorator("derive_WebAppFiles", codegen::expand_webapp_implementation);
|
||||
reg.add_post_expansion_pass(strip_attributes);
|
||||
}
|
||||
|
||||
pub fn generate() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut registry = syntex::Registry::new();
|
||||
register(&mut registry);
|
||||
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
pub mod inner {
|
||||
use codegen;
|
||||
|
||||
pub fn register(reg: &mut rustc_plugin::Registry) {
|
||||
reg.register_syntax_extension(
|
||||
syntax::parse::token::intern("derive_WebAppFiles"),
|
||||
syntax::ext::base::MultiDecorator(
|
||||
Box::new(codegen::expand_webapp_implementation)));
|
||||
|
||||
reg.register_attribute("webapp".to_owned(), AttributeType::Normal);
|
||||
}
|
||||
|
||||
pub fn generate() {}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate aster;
|
||||
extern crate glob;
|
||||
extern crate mime_guess;
|
||||
|
||||
use self::mime_guess::guess_mime_type;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::ops::Deref;
|
||||
|
||||
use syntax::ast::{MetaItem, Item};
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::attr;
|
||||
use syntax::codemap::Span;
|
||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||
use syntax::ptr::P;
|
||||
use syntax::print::pprust::{lit_to_string};
|
||||
use syntax::parse::token::{InternedString};
|
||||
|
||||
|
||||
pub fn expand_webapp_implementation(
|
||||
cx: &mut ExtCtxt,
|
||||
span: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotatable: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)
|
||||
) {
|
||||
let item = match *annotatable {
|
||||
Annotatable::Item(ref item) => item,
|
||||
_ => {
|
||||
cx.span_err(meta_item.span, "`#[derive(WebAppFiles)]` may only be applied to struct implementations");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let builder = aster::AstBuilder::new().span(span);
|
||||
implement_webapp(cx, &builder, &item, push);
|
||||
}
|
||||
|
||||
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
|
||||
let static_files_dir = extract_path(cx, item);
|
||||
|
||||
let src = Path::new("src");
|
||||
let static_files = {
|
||||
let mut buf = src.to_path_buf();
|
||||
buf.push(static_files_dir.deref());
|
||||
buf
|
||||
};
|
||||
|
||||
let search_location = {
|
||||
let mut buf = static_files.to_path_buf();
|
||||
buf.push("**");
|
||||
buf.push("*");
|
||||
buf
|
||||
};
|
||||
|
||||
let files = glob::glob(search_location.to_str().expect("Valid UTF8 path"))
|
||||
.expect("The sources directory is missing.")
|
||||
.collect::<Result<Vec<PathBuf>, glob::GlobError>>()
|
||||
.expect("There should be no error when reading a list of files.");
|
||||
|
||||
let statements = files
|
||||
.iter()
|
||||
.filter(|path_buf| path_buf.is_file())
|
||||
.map(|path_buf| {
|
||||
let path = path_buf.as_path();
|
||||
let filename = path.file_name().and_then(|s| s.to_str()).expect("Only UTF8 paths.");
|
||||
let mime_type = guess_mime_type(filename).to_string();
|
||||
let file_path = as_uri(path.strip_prefix(&static_files).ok().expect("Prefix is always there, cause it's absolute path;qed"));
|
||||
let file_path_in_source = path.to_str().expect("Only UTF8 paths.");
|
||||
|
||||
let path_lit = builder.expr().str(file_path.as_str());
|
||||
let mime_lit = builder.expr().str(mime_type.as_str());
|
||||
let web_path_lit = builder.expr().str(file_path_in_source);
|
||||
let separator_lit = builder.expr().str(path::MAIN_SEPARATOR.to_string().as_str());
|
||||
let concat_id = builder.id("concat!");
|
||||
let env_id = builder.id("env!");
|
||||
let macro_id = builder.id("include_bytes!");
|
||||
|
||||
let content = quote_expr!(
|
||||
cx,
|
||||
$macro_id($concat_id($env_id("CARGO_MANIFEST_DIR"), $separator_lit, $web_path_lit))
|
||||
);
|
||||
quote_stmt!(
|
||||
cx,
|
||||
files.insert($path_lit, File { path: $path_lit, content_type: $mime_lit, content: $content });
|
||||
).expect("The statement is always ok, because it just uses literals.")
|
||||
}).collect::<Vec<ast::Stmt>>();
|
||||
|
||||
let type_name = item.ident;
|
||||
|
||||
let files_impl = quote_item!(cx,
|
||||
impl $type_name {
|
||||
fn files() -> ::std::collections::HashMap<&'static str, File> {
|
||||
let mut files = ::std::collections::HashMap::new();
|
||||
$statements
|
||||
files
|
||||
}
|
||||
}
|
||||
).unwrap();
|
||||
|
||||
push(Annotatable::Item(files_impl));
|
||||
}
|
||||
|
||||
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
|
||||
for meta_items in item.attrs().iter().filter_map(webapp_meta_items) {
|
||||
for meta_item in meta_items {
|
||||
match meta_item.node {
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"path" => {
|
||||
if let Some(s) = get_str_from_lit(cx, name, lit) {
|
||||
return s.deref().to_owned();
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
"web".to_owned()
|
||||
}
|
||||
|
||||
fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<InternedString> {
|
||||
match lit.node {
|
||||
ast::LitKind::Str(ref s, _) => Some(s.clone()),
|
||||
_ => {
|
||||
cx.span_err(
|
||||
lit.span,
|
||||
&format!("webapp annotation `{}` must be a string, not `{}`",
|
||||
name,
|
||||
lit_to_string(lit)
|
||||
)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn webapp_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
|
||||
match attr.node.value.node {
|
||||
ast::MetaItemKind::List(ref name, ref items) if name == &"webapp" => {
|
||||
attr::mark_used(&attr);
|
||||
Some(items)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_uri(path: &Path) -> String {
|
||||
let mut s = String::new();
|
||||
for component in path.iter() {
|
||||
s.push_str(component.to_str().expect("Only UTF-8 filenames are supported."));
|
||||
s.push('/');
|
||||
}
|
||||
s[0..s.len()-1].into()
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_convert_path_separators_on_all_platforms() {
|
||||
// given
|
||||
let p = {
|
||||
let mut p = PathBuf::new();
|
||||
p.push("web");
|
||||
p.push("src");
|
||||
p.push("index.html");
|
||||
p
|
||||
};
|
||||
|
||||
// when
|
||||
let path = as_uri(&p);
|
||||
|
||||
// then
|
||||
assert_eq!(path, "web/src/index.html".to_owned());
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(dead_code))]
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(unused_imports))]
|
||||
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
mod platform {
|
||||
use std::process::Command;
|
||||
|
||||
pub static NPM_CMD: &'static str = "npm";
|
||||
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod platform {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub static NPM_CMD: &'static str = "npm.cmd";
|
||||
// NOTE [ToDr] For some reason on windows
|
||||
// We cannot have any file descriptors open when running a child process
|
||||
// during build phase.
|
||||
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
}
|
||||
}
|
||||
|
||||
fn die<T : fmt::Debug>(s: &'static str, e: T) -> ! {
|
||||
panic!("Error: {}: {:?}", s, e);
|
||||
}
|
||||
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
pub fn test(_path: &str) {
|
||||
}
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
pub fn build(_path: &str, _dest: &str) {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use-precompiled-js"))]
|
||||
pub fn build(path: &str, dest: &str) {
|
||||
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
|
||||
.arg("install")
|
||||
.arg("--no-progress")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Installing node.js dependencies with npm", e));
|
||||
assert!(child.success(), "There was an error installing dependencies.");
|
||||
|
||||
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
|
||||
.arg("run")
|
||||
.arg("build")
|
||||
.env("NODE_ENV", "production")
|
||||
.env("BUILD_DEST", dest)
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Building JS code", e));
|
||||
assert!(child.success(), "There was an error build JS code.");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use-precompiled-js"))]
|
||||
pub fn test(path: &str) {
|
||||
let child = Command::new(platform::NPM_CMD)
|
||||
.arg("run")
|
||||
.arg("test")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Running test command", e));
|
||||
assert!(child.success(), "There was an error while running JS tests.");
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#![cfg_attr(not(feature = "with-syntex"), feature(rustc_private, plugin))]
|
||||
#![cfg_attr(not(feature = "with-syntex"), plugin(quasi_macros))]
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
extern crate syntex;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
#[macro_use]
|
||||
extern crate syntex_syntax as syntax;
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
#[macro_use]
|
||||
extern crate syntax;
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
extern crate rustc_plugin;
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
include!("lib.rs.in");
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate quasi;
|
||||
|
||||
mod codegen;
|
||||
mod build;
|
||||
pub mod js;
|
||||
pub use build::inner::generate;
|
||||
|
||||
use std::default::Default;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File {
|
||||
pub path: &'static str,
|
||||
pub content: &'static [u8],
|
||||
// TODO: use strongly-typed MIME.
|
||||
pub content_type: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Info {
|
||||
pub name: &'static str,
|
||||
pub version: &'static str,
|
||||
pub author: &'static str,
|
||||
pub description: &'static str,
|
||||
pub icon_url: &'static str,
|
||||
}
|
||||
|
||||
pub trait WebApp : Default + Send + Sync {
|
||||
fn file(&self, path: &str) -> Option<&File>;
|
||||
fn info(&self) -> Info;
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use unicase::UniCase;
|
||||
use hyper::{server, net, Decoder, Encoder, Next, Control};
|
||||
use hyper::header;
|
||||
use hyper::method::Method;
|
||||
use hyper::header::AccessControlAllowOrigin;
|
||||
|
||||
use api::types::{App, ApiError};
|
||||
use api::response;
|
||||
use apps::fetcher::ContentFetcher;
|
||||
|
||||
use handlers::extract_url;
|
||||
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
|
||||
use jsonrpc_http_server::cors;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetcher: Arc<ContentFetcher>,
|
||||
}
|
||||
|
||||
impl RestApi {
|
||||
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,
|
||||
fetcher: fetcher,
|
||||
})
|
||||
}
|
||||
|
||||
fn list_apps(&self) -> Vec<App> {
|
||||
self.endpoints.iter().filter_map(|(ref k, ref e)| {
|
||||
e.info().map(|ref info| App::from_info(k, info))
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for RestApi {
|
||||
fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
|
||||
Box::new(RestApiRouter::new(self.clone(), path, control))
|
||||
}
|
||||
}
|
||||
|
||||
struct RestApiRouter {
|
||||
api: RestApi,
|
||||
origin: Option<String>,
|
||||
path: Option<EndpointPath>,
|
||||
control: Option<Control>,
|
||||
handler: Box<Handler>,
|
||||
}
|
||||
|
||||
impl RestApiRouter {
|
||||
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
|
||||
RestApiRouter {
|
||||
path: Some(path),
|
||||
origin: None,
|
||||
control: Some(control),
|
||||
api: api,
|
||||
handler: response::as_json_error(&ApiError {
|
||||
code: "404".into(),
|
||||
title: "Not Found".into(),
|
||||
detail: "Resource you requested has not been found.".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, control: Control) -> Option<Box<Handler>> {
|
||||
match hash {
|
||||
Some(hash) if self.api.fetcher.contains(hash) => {
|
||||
Some(self.api.fetcher.to_async_handler(path, control))
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns basic headers for a response (it may be overwritten by the handler)
|
||||
fn response_headers(&self) -> header::Headers {
|
||||
let mut headers = header::Headers::new();
|
||||
headers.set(header::AccessControlAllowCredentials);
|
||||
headers.set(header::AccessControlAllowMethods(vec![
|
||||
Method::Options,
|
||||
Method::Post,
|
||||
Method::Get,
|
||||
]));
|
||||
headers.set(header::AccessControlAllowHeaders(vec![
|
||||
UniCase("origin".to_owned()),
|
||||
UniCase("content-type".to_owned()),
|
||||
UniCase("accept".to_owned()),
|
||||
]));
|
||||
|
||||
if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) {
|
||||
headers.set(cors_header);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<net::HttpStream> for RestApiRouter {
|
||||
|
||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||
self.origin = cors::read_origin(&request);
|
||||
|
||||
if let Method::Options = *request.method() {
|
||||
self.handler = response::empty();
|
||||
return Next::write();
|
||||
}
|
||||
|
||||
let url = extract_url(&request);
|
||||
if url.is_none() {
|
||||
// Just return 404 if we can't parse URL
|
||||
return Next::write();
|
||||
}
|
||||
|
||||
let url = url.expect("Check for None early-exists above; qed");
|
||||
let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed");
|
||||
let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed");
|
||||
|
||||
let endpoint = url.path.get(1).map(|v| v.as_str());
|
||||
let hash = url.path.get(2).map(|v| v.as_str());
|
||||
// at this point path.app_id contains 'api', adjust it to the hash properly, otherwise
|
||||
// we will try and retrieve 'api' as the hash when doing the /api/content route
|
||||
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
|
||||
|
||||
let handler = endpoint.and_then(|v| match v {
|
||||
"apps" => Some(response::as_json(&self.api.list_apps())),
|
||||
"ping" => Some(response::ping()),
|
||||
"content" => self.resolve_content(hash, path, control),
|
||||
_ => None
|
||||
});
|
||||
|
||||
// Overwrite default
|
||||
if let Some(h) = handler {
|
||||
self.handler = h;
|
||||
}
|
||||
|
||||
self.handler.on_request(request)
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<net::HttpStream>) -> Next {
|
||||
self.handler.on_request_readable(decoder)
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
*res.headers_mut() = self.response_headers();
|
||||
self.handler.on_response(res)
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
|
||||
self.handler.on_response_writable(encoder)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use endpoint::Handler;
|
||||
use handlers::{ContentHandler, EchoHandler};
|
||||
|
||||
pub fn empty() -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
Box::new(ContentHandler::not_found(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn ping() -> Box<Handler> {
|
||||
Box::new(EchoHandler::default())
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
include!("types.rs.in");
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
include!(concat!(env!("OUT_DIR"), "/types.rs"));
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct App {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
#[serde(rename="iconUrl")]
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates `App` instance from `EndpointInfo` and `id`.
|
||||
pub fn from_info(id: &str, info: &EndpointInfo) -> Self {
|
||||
App {
|
||||
id: id.to_owned(),
|
||||
name: info.name.to_owned(),
|
||||
description: info.description.to_owned(),
|
||||
version: info.version.to_owned(),
|
||||
author: info.author.to_owned(),
|
||||
icon_url: info.icon_url.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<EndpointInfo> for App {
|
||||
fn into(self) -> EndpointInfo {
|
||||
EndpointInfo {
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
version: self.version,
|
||||
author: self.author,
|
||||
icon_url: self.icon_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ApiError {
|
||||
pub code: String,
|
||||
pub title: String,
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use std::fs;
|
||||
use std::sync::{Arc};
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use page::LocalPageEndpoint;
|
||||
use handlers::FetchControl;
|
||||
|
||||
pub enum ContentStatus {
|
||||
Fetching(Arc<FetchControl>),
|
||||
Ready(LocalPageEndpoint),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContentCache {
|
||||
cache: LinkedHashMap<String, ContentStatus>,
|
||||
}
|
||||
|
||||
impl ContentCache {
|
||||
pub fn insert(&mut self, content_id: String, status: ContentStatus) -> Option<ContentStatus> {
|
||||
self.cache.insert(content_id, status)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, content_id: &str) -> Option<ContentStatus> {
|
||||
self.cache.remove(content_id)
|
||||
}
|
||||
|
||||
pub fn get(&mut self, content_id: &str) -> Option<&mut ContentStatus> {
|
||||
self.cache.get_refresh(content_id)
|
||||
}
|
||||
|
||||
pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> {
|
||||
let len = self.cache.len();
|
||||
|
||||
if len <= expected_size {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut removed = Vec::with_capacity(len - expected_size);
|
||||
|
||||
while self.cache.len() > expected_size {
|
||||
let entry = self.cache.pop_front().expect("expected_size bounded at 0, len is greater; qed");
|
||||
|
||||
match entry.1 {
|
||||
ContentStatus::Fetching(ref fetch) => {
|
||||
trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
|
||||
// Mark as aborted
|
||||
fetch.abort()
|
||||
},
|
||||
ContentStatus::Ready(ref endpoint) => {
|
||||
trace!(target: "dapps", "Removing {} because of limit.", entry.0);
|
||||
// Remove path (dir or file)
|
||||
let res = fs::remove_dir_all(&endpoint.path()).or_else(|_| fs::remove_file(&endpoint.path()));
|
||||
if let Err(e) = res {
|
||||
warn!(target: "dapps", "Unable to remove dapp/content from cache: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removed.push(entry);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn only_keys(data: Vec<(String, ContentStatus)>) -> Vec<String> {
|
||||
data.into_iter().map(|x| x.0).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_least_recently_used() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["a"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_update_lru_if_accessed() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
cache.get("a");
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["b"]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
// 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_on: Option<(String, 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_on: Option<(String, 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_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_on.clone()))
|
||||
},
|
||||
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_on: self.embeddable_on.clone(),
|
||||
},
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
|
||||
(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_on.clone(),
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(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
|
||||
}
|
||||
}
|
||||
|
||||
#[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_on: Option<(String, 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_on.clone());
|
||||
|
||||
// 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,109 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use page::{LocalPageEndpoint, PageCache};
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
|
||||
struct LocalDapp {
|
||||
id: String,
|
||||
path: PathBuf,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
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, e);
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let files = files.expect("Check is done earlier");
|
||||
files.map(|dir| {
|
||||
let entry = try!(dir);
|
||||
let file_type = try!(entry.file_type());
|
||||
|
||||
// skip files
|
||||
if file_type.is_file() {
|
||||
return Err(io::Error::new(io::ErrorKind::NotFound, "Not a file"));
|
||||
}
|
||||
|
||||
// take directory name and path
|
||||
entry.file_name().into_string()
|
||||
.map(|name| (name, entry.path()))
|
||||
.map_err(|e| {
|
||||
info!(target: "dapps", "Unable to load dapp: {:?}. Reason: {:?}", entry.path(), e);
|
||||
io::Error::new(io::ErrorKind::NotFound, "Invalid name")
|
||||
})
|
||||
})
|
||||
.filter_map(|m| {
|
||||
if let Err(ref e) = m {
|
||||
debug!(target: "dapps", "Ignoring local dapp: {:?}", e);
|
||||
}
|
||||
m.ok()
|
||||
})
|
||||
.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_address: Option<(String, 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_address.clone()))
|
||||
);
|
||||
}
|
||||
pages
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde_json;
|
||||
pub use api::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
pub fn deserialize_manifest(manifest: String) -> Result<Manifest, String> {
|
||||
serde_json::from_str::<Manifest>(&manifest).map_err(|e| format!("{:?}", e))
|
||||
// TODO [todr] Manifest validation (especialy: id (used as path))
|
||||
}
|
||||
|
||||
pub fn serialize_manifest(manifest: &Manifest) -> Result<String, String> {
|
||||
serde_json::to_string_pretty(manifest).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use endpoint::{Endpoints, Endpoint};
|
||||
use page::PageEndpoint;
|
||||
use proxypac::ProxyPac;
|
||||
use parity_dapps::WebApp;
|
||||
|
||||
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 fn utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
|
||||
// fetch fs dapps at first to avoid overwriting builtins
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
|
||||
|
||||
// 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));
|
||||
|
||||
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::No => PageEndpoint::new(T::default()),
|
||||
}));
|
||||
}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<(String, u16)>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[
|
||||
{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"},
|
||||
{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"},
|
||||
{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"key","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_url","type":"string"}],"name":"hintURL","outputs":[],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_accountSlashRepo","type":"string"},{"name":"_commit","type":"bytes20"}],"name":"hint","outputs":[],"type":"function"},
|
||||
{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"entries","outputs":[{"name":"accountSlashRepo","type":"string"},{"name":"commit","type":"bytes20"},{"name":"owner","type":"address"}],"type":"function"},
|
||||
{"constant":false,"inputs":[{"name":"_content","type":"bytes32"}],"name":"unhint","outputs":[],"type":"function"}
|
||||
]
|
||||
@@ -1,399 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use mime_guess;
|
||||
|
||||
use ethabi::{Interface, Contract, Token};
|
||||
use util::{Address, Bytes, Hashable};
|
||||
|
||||
const COMMIT_LEN: usize = 20;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct GithubApp {
|
||||
pub account: String,
|
||||
pub repo: String,
|
||||
pub commit: [u8;COMMIT_LEN],
|
||||
pub owner: Address,
|
||||
}
|
||||
|
||||
impl GithubApp {
|
||||
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())
|
||||
format!("https://codeload.github.com/{}/{}/zip/{}", self.account, self.repo, self.commit.to_hex())
|
||||
}
|
||||
|
||||
fn commit(bytes: &[u8]) -> Option<[u8;COMMIT_LEN]> {
|
||||
if bytes.len() < COMMIT_LEN {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut commit = [0; COMMIT_LEN];
|
||||
for i in 0..COMMIT_LEN {
|
||||
commit[i] = bytes[i];
|
||||
}
|
||||
|
||||
Some(commit)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Content {
|
||||
pub url: String,
|
||||
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 {
|
||||
/// Dapp
|
||||
Dapp(GithubApp),
|
||||
/// Content
|
||||
Content(Content),
|
||||
}
|
||||
|
||||
/// URLHint Contract interface
|
||||
pub trait URLHint {
|
||||
/// Resolves given id to registrar entry.
|
||||
fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
|
||||
}
|
||||
|
||||
pub struct URLHintContract {
|
||||
urlhint: Contract,
|
||||
registrar: Contract,
|
||||
client: Arc<ContractClient>,
|
||||
}
|
||||
|
||||
impl URLHintContract {
|
||||
pub fn new(client: Arc<ContractClient>) -> Self {
|
||||
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),
|
||||
registrar: Contract::new(registrar),
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
fn urlhint_address(&self) -> Option<Address> {
|
||||
let res = || {
|
||||
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 = 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()),
|
||||
Some(&Token::Address(_)) => Err(format!("Contract not found.")),
|
||||
e => Err(format!("Invalid result: {:?}", e)),
|
||||
}
|
||||
};
|
||||
|
||||
match res() {
|
||||
Ok(res) => Some(res),
|
||||
Err(e) => {
|
||||
warn!(target: "dapps", "Error while calling registrar: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_urlhint_call(&self, id: Bytes) -> Option<Bytes> {
|
||||
let call = self.urlhint
|
||||
.function("entries".into())
|
||||
.and_then(|f| f.encode_call(vec![Token::FixedBytes(id)]));
|
||||
|
||||
match call {
|
||||
Ok(res) => {
|
||||
Some(res)
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(target: "dapps", "Error while encoding urlhint call: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_urlhint_output(&self, output: Bytes) -> Option<URLHintResult> {
|
||||
trace!(target: "dapps", "Output: {:?}", output.to_hex());
|
||||
let output = self.urlhint
|
||||
.function("entries".into())
|
||||
.and_then(|f| f.decode_output(output));
|
||||
|
||||
if let Ok(vec) = output {
|
||||
if vec.len() != 3 {
|
||||
warn!(target: "dapps", "Invalid contract output: {:?}", vec);
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut it = vec.into_iter();
|
||||
let account_slash_repo = it.next().expect("element 0 of 3-len vector known to exist; qed");
|
||||
let commit = it.next().expect("element 1 of 3-len vector known to exist; qed");
|
||||
let owner = it.next().expect("element 2 of 3-len vector known to exist qed");
|
||||
|
||||
match (account_slash_repo, commit, owner) {
|
||||
(Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => {
|
||||
let owner = owner.into();
|
||||
if owner == Address::default() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let commit = GithubApp::commit(&commit);
|
||||
if commit == Some(Default::default()) {
|
||||
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,
|
||||
owner: owner,
|
||||
}));
|
||||
}
|
||||
|
||||
let (account, repo) = {
|
||||
let mut it = account_slash_repo.split('/');
|
||||
match (it.next(), it.next()) {
|
||||
(Some(account), Some(repo)) => (account.into(), repo.into()),
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
|
||||
commit.map(|commit| URLHintResult::Dapp(GithubApp {
|
||||
account: account,
|
||||
repo: repo,
|
||||
commit: commit,
|
||||
owner: owner,
|
||||
}))
|
||||
},
|
||||
e => {
|
||||
warn!(target: "dapps", "Invalid contract output parameters: {:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
} else {
|
||||
warn!(target: "dapps", "Invalid contract output: {:?}", output);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl URLHint for URLHintContract {
|
||||
fn resolve(&self, id: Bytes) -> Option<URLHintResult> {
|
||||
self.urlhint_address().and_then(|address| {
|
||||
// Prepare contract call
|
||||
self.encode_urlhint_call(id)
|
||||
.and_then(|data| {
|
||||
let call = self.client.call(address, data);
|
||||
if let Err(ref e) = call {
|
||||
warn!(target: "dapps", "Error while calling urlhint: {:?}", e);
|
||||
}
|
||||
call.ok()
|
||||
})
|
||||
.and_then(|output| self.decode_urlhint_output(output))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_mime_type(url: &str) -> Option<String> {
|
||||
const CONTENT_TYPE: &'static str = "content-type=";
|
||||
|
||||
let mut it = url.split('#');
|
||||
// skip url
|
||||
let url = it.next();
|
||||
// get meta headers
|
||||
let metas = it.next();
|
||||
if let Some(metas) = metas {
|
||||
for meta in metas.split('&') {
|
||||
let meta = meta.to_lowercase();
|
||||
if meta.starts_with(CONTENT_TYPE) {
|
||||
return Some(meta[CONTENT_TYPE.len()..].to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
url.and_then(|url| {
|
||||
url.split('.').last()
|
||||
}).and_then(|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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
|
||||
use super::*;
|
||||
use util::{Bytes, Address, Mutex, ToPretty};
|
||||
|
||||
struct FakeRegistrar {
|
||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
||||
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
||||
}
|
||||
|
||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_call_registrar_and_urlhint_contracts() {
|
||||
// given
|
||||
let registrar = FakeRegistrar::new();
|
||||
let calls = registrar.calls.clone();
|
||||
let urlhint = URLHintContract::new(Arc::new(registrar));
|
||||
|
||||
// when
|
||||
let res = urlhint.resolve("test".bytes().collect());
|
||||
let calls = calls.lock();
|
||||
let call0 = calls.get(0).expect("Registrar resolve called");
|
||||
let call1 = calls.get(1).expect("URLHint Resolve called");
|
||||
|
||||
// then
|
||||
assert!(res.is_none());
|
||||
assert_eq!(call0.0, REGISTRAR);
|
||||
assert_eq!(call0.1,
|
||||
"6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".to_owned()
|
||||
);
|
||||
assert_eq!(call1.0, URLHINT);
|
||||
assert_eq!(call1.1,
|
||||
"267b69227465737400000000000000000000000000000000000000000000000000000000".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_decode_urlhint_output() {
|
||||
// given
|
||||
let mut registrar = FakeRegistrar::new();
|
||||
registrar.responses = Mutex::new(vec![
|
||||
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
|
||||
Ok("0000000000000000000000000000000000000000000000000000000000000060ec4c1fe06c808fe3739858c347109b1f5f1ed4b5000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff0000000000000000000000000000000000000000000000000000000000000011657468636f72652f64616f2e636c61696d000000000000000000000000000000".from_hex().unwrap()),
|
||||
]);
|
||||
let urlhint = URLHintContract::new(Arc::new(registrar));
|
||||
|
||||
// when
|
||||
let res = urlhint.resolve("test".bytes().collect());
|
||||
|
||||
// then
|
||||
assert_eq!(res, Some(URLHintResult::Dapp(GithubApp {
|
||||
account: "ethcore".into(),
|
||||
repo: "dao.claim".into(),
|
||||
commit: GithubApp::commit(&"ec4c1fe06c808fe3739858c347109b1f5f1ed4b5".from_hex().unwrap()).unwrap(),
|
||||
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
|
||||
})))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_decode_urlhint_content_output() {
|
||||
// given
|
||||
let mut registrar = FakeRegistrar::new();
|
||||
registrar.responses = Mutex::new(vec![
|
||||
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
|
||||
Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()),
|
||||
]);
|
||||
let urlhint = URLHintContract::new(Arc::new(registrar));
|
||||
|
||||
// when
|
||||
let res = urlhint.resolve("test".bytes().collect());
|
||||
|
||||
// then
|
||||
assert_eq!(res, Some(URLHintResult::Content(Content {
|
||||
url: "https://ethcore.io/assets/images/ethcore-black-horizontal.png".into(),
|
||||
mime: "image/png".into(),
|
||||
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
|
||||
})))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_valid_url() {
|
||||
// given
|
||||
let app = GithubApp {
|
||||
account: "test".into(),
|
||||
repo: "xyz".into(),
|
||||
commit: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
|
||||
owner: Address::default(),
|
||||
};
|
||||
|
||||
// when
|
||||
let url = app.url();
|
||||
|
||||
// then
|
||||
assert_eq!(url, "https://codeload.github.com/test/xyz/zip/000102030405060708090a0b0c0d0e0f10111213".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_guess_mime_type_from_url() {
|
||||
let url1 = "https://ethcore.io/parity";
|
||||
let url2 = "https://ethcore.io/parity#content-type=image/png";
|
||||
let url3 = "https://ethcore.io/parity#something&content-type=image/png";
|
||||
let url4 = "https://ethcore.io/parity.png#content-type=image/jpeg";
|
||||
let url5 = "https://ethcore.io/parity.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,52 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! URL Endpoint traits
|
||||
|
||||
use hyper::{self, server, net};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Clone)]
|
||||
pub struct EndpointPath {
|
||||
pub app_id: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub using_dapps_domains: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct EndpointInfo {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
|
||||
pub type Handler = server::Handler<net::HttpStream> + Send;
|
||||
|
||||
pub trait Endpoint : Send + Sync {
|
||||
fn info(&self) -> Option<&EndpointInfo> { None }
|
||||
|
||||
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
|
||||
panic!("This Endpoint is asynchronous and requires Control object.");
|
||||
}
|
||||
|
||||
fn to_async_handler(&self, path: EndpointPath, _control: hyper::Control) -> Box<Handler> {
|
||||
self.to_handler(path)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/parity-utils/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="parity-navbar">
|
||||
</div>
|
||||
<div class="parity-box">
|
||||
<h1>{title}</h1>
|
||||
<h3>{message}</h3>
|
||||
<p><code>{details}</code></p>
|
||||
</div>
|
||||
<div class="parity-status">
|
||||
<small>{version}</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Authorization Handlers
|
||||
|
||||
use hyper::{server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
pub struct AuthRequiredHandler;
|
||||
|
||||
impl server::Handler<HttpStream> for AuthRequiredHandler {
|
||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
res.set_status(StatusCode::Unauthorized);
|
||||
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Simple Content Handler
|
||||
|
||||
use std::io::Write;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::mime::Mime;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use util::version;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContentHandler {
|
||||
code: StatusCode,
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
write_pos: usize,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
pub fn ok(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::Ok, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn not_found(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::NotFound, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
version=version(),
|
||||
), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self {
|
||||
Self::new_embeddable(code, content, mimetype, None)
|
||||
}
|
||||
|
||||
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
ContentHandler {
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0,
|
||||
safe_to_embed_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for ContentHandler {
|
||||
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.code);
|
||||
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 {
|
||||
let bytes = self.content.as_bytes();
|
||||
if self.write_pos == bytes.len() {
|
||||
return Next::end();
|
||||
}
|
||||
|
||||
match encoder.write(&bytes[self.write_pos..]) {
|
||||
Ok(bytes) => {
|
||||
self.write_pos += bytes;
|
||||
Next::write()
|
||||
},
|
||||
Err(e) => match e.kind() {
|
||||
::std::io::ErrorKind::WouldBlock => Next::write(),
|
||||
_ => Next::end()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Echo Handler
|
||||
|
||||
use std::io::Read;
|
||||
use hyper::{server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use super::ContentHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EchoHandler {
|
||||
content: String,
|
||||
handler: Option<ContentHandler>,
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for EchoHandler {
|
||||
fn on_request(&mut self, _: server::Request<HttpStream>) -> Next {
|
||||
Next::read()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
match decoder.read_to_string(&mut self.content) {
|
||||
Ok(0) => {
|
||||
self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
|
||||
Next::write()
|
||||
},
|
||||
Ok(_) => Next::read(),
|
||||
Err(e) => match e.kind() {
|
||||
::std::io::ErrorKind::WouldBlock => Next::read(),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
self.handler.as_mut()
|
||||
.expect("handler always set in on_request, which is before now; qed")
|
||||
.on_response(res)
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
self.handler.as_mut()
|
||||
.expect("handler always set in on_request, which is before now; qed")
|
||||
.on_response_writable(encoder)
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Hyper Server Handler that fetches a file during a request (proxy).
|
||||
|
||||
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 util::Mutex;
|
||||
use url::Url;
|
||||
use fetch::{Client, Fetch, FetchResult};
|
||||
|
||||
use hyper::{server, Decoder, Encoder, Next, Method, Control};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use handlers::{ContentHandler, Redirection, extract_url};
|
||||
use page::LocalPageEndpoint;
|
||||
|
||||
const FETCH_TIMEOUT: u64 = 30;
|
||||
|
||||
enum FetchState {
|
||||
NotStarted(String),
|
||||
Error(ContentHandler),
|
||||
InProgress(mpsc::Receiver<FetchResult>),
|
||||
Done(String, LocalPageEndpoint, Redirection),
|
||||
}
|
||||
|
||||
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>);
|
||||
}
|
||||
|
||||
pub struct FetchControl {
|
||||
abort: Arc<AtomicBool>,
|
||||
listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
impl Default for FetchControl {
|
||||
fn default() -> Self {
|
||||
FetchControl {
|
||||
abort: Arc::new(AtomicBool::new(false)),
|
||||
listeners: Mutex::new(Vec::new()),
|
||||
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchControl {
|
||||
fn notify<F: Fn() -> FetchState>(&self, status: F) {
|
||||
let mut listeners = self.listeners.lock();
|
||||
for (control, sender) in listeners.drain(..) {
|
||||
if let Err(e) = sender.send(status()) {
|
||||
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
|
||||
} else {
|
||||
let _ = control.ready(Next::read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_status(&self, status: &FetchState) {
|
||||
match *status {
|
||||
FetchState::Error(ref handler) => self.notify(|| 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 abort(&self) {
|
||||
self.abort.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
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: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitingHandler {
|
||||
receiver: mpsc::Receiver<FetchState>,
|
||||
state: Option<FetchState>,
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for WaitingHandler {
|
||||
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
|
||||
Next::wait()
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response_writable(encoder),
|
||||
Some(FetchState::Error(ref mut handler)) => handler.on_response_writable(encoder),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContentFetcherHandler<H: ContentValidator> {
|
||||
fetch_control: Arc<FetchControl>,
|
||||
control: Option<Control>,
|
||||
status: FetchState,
|
||||
client: Option<Client>,
|
||||
installer: H,
|
||||
request_url: Option<Url>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
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,
|
||||
control: Control,
|
||||
handler: H,
|
||||
embeddable_on: Option<(String, 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: handler,
|
||||
request_url: None,
|
||||
embeddable_on: embeddable_on,
|
||||
};
|
||||
|
||||
(handler, fetch_control)
|
||||
}
|
||||
|
||||
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(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());
|
||||
})).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Some(match *request.method() {
|
||||
// Start fetching content
|
||||
Method::Get => {
|
||||
trace!(target: "dapps", "Fetching content from: {:?}", url);
|
||||
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_on.clone(),
|
||||
)),
|
||||
}
|
||||
},
|
||||
// or return error
|
||||
_ => FetchState::Error(ContentHandler::error(
|
||||
StatusCode::MethodNotAllowed,
|
||||
"Method Not Allowed",
|
||||
"Only <code>GET</code> requests are allowed.",
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
)),
|
||||
})
|
||||
} else { None };
|
||||
|
||||
if let Some(status) = status {
|
||||
self.fetch_control.set_status(&status);
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
Next::read()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
let (status, next) = match self.status {
|
||||
// Request may time out
|
||||
FetchState::InProgress(_) if self.fetch_control.deadline < Instant::now() => {
|
||||
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
|
||||
let timeout = ContentHandler::error(
|
||||
StatusCode::GatewayTimeout,
|
||||
"Download Timeout",
|
||||
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
|
||||
None,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
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 {
|
||||
// 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_on.clone(),
|
||||
))
|
||||
},
|
||||
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_on.clone(),
|
||||
);
|
||||
(Some(FetchState::Error(error)), Next::write())
|
||||
},
|
||||
// wait some more
|
||||
_ => (None, Next::wait())
|
||||
}
|
||||
},
|
||||
FetchState::Error(ref mut handler) => (None, handler.on_request_readable(decoder)),
|
||||
_ => (None, Next::write()),
|
||||
};
|
||||
|
||||
if let Some(status) = status {
|
||||
self.fetch_control.set_status(&status);
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
match self.status {
|
||||
FetchState::Done(_, _, ref mut handler) => handler.on_response(res),
|
||||
FetchState::Error(ref mut handler) => handler.on_response(res),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.status {
|
||||
FetchState::Done(_, _, ref mut handler) => handler.on_response_writable(encoder),
|
||||
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
_ => Next::end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Hyper handlers implementations.
|
||||
|
||||
mod auth;
|
||||
mod echo;
|
||||
mod content;
|
||||
mod redirect;
|
||||
mod fetch;
|
||||
|
||||
pub use self::auth::AuthRequiredHandler;
|
||||
pub use self::echo::EchoHandler;
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::redirect::Redirection;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
|
||||
|
||||
use url::Url;
|
||||
use hyper::{server, header, net, uri};
|
||||
use address;
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) {
|
||||
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
|
||||
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
|
||||
|
||||
// Embedding header:
|
||||
if let Some(embeddable_on) = embeddable_on {
|
||||
headers.set_raw(
|
||||
"X-Frame-Options",
|
||||
vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
|
||||
);
|
||||
} else {
|
||||
// TODO [ToDr] Should we be more strict here (DENY?)?
|
||||
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts URL part from the Request.
|
||||
pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> {
|
||||
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) => {
|
||||
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
|
||||
let url_string = match req.headers().get::<header::Host>() {
|
||||
Some(ref host) => {
|
||||
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
|
||||
match Url::parse(&url_string) {
|
||||
Ok(url) => Some(url),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! HTTP Redirection hyper handler
|
||||
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Redirection {
|
||||
to_url: String
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new(url: &str) -> Self {
|
||||
Redirection {
|
||||
to_url: url.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boxed(url: &str) -> Box<Self> {
|
||||
Box::new(Self::new(url))
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for Redirection {
|
||||
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 {
|
||||
// Don't use `MovedPermanently` here to prevent browser from caching the redirections.
|
||||
res.set_status(StatusCode::Found);
|
||||
res.headers_mut().set(header::Location(self.to_url.to_owned()));
|
||||
Next::write()
|
||||
}
|
||||
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
341
dapps/src/lib.rs
341
dapps/src/lib.rs
@@ -1,341 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore Webapplications for Parity
|
||||
//! ```
|
||||
//! extern crate jsonrpc_core;
|
||||
//! extern crate ethcore_dapps;
|
||||
//!
|
||||
//! use std::sync::Arc;
|
||||
//! use jsonrpc_core::IoHandler;
|
||||
//! use ethcore_dapps::*;
|
||||
//!
|
||||
//! struct SayHello;
|
||||
//! impl MethodCommand for SayHello {
|
||||
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
|
||||
//! Ok(Value::String("hello".to_string()))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let io = IoHandler::new();
|
||||
//! io.add_method("say_hello", SayHello);
|
||||
//! let _server = Server::start_unsecure_http(
|
||||
//! &"127.0.0.1:3030".parse().unwrap(),
|
||||
//! Arc::new(io)
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||
|
||||
extern crate hyper;
|
||||
extern crate time;
|
||||
extern crate url as url_lib;
|
||||
extern crate unicase;
|
||||
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 linked_hash_map;
|
||||
extern crate fetch;
|
||||
extern crate parity_dapps_glue as parity_dapps;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate mime;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
mod endpoint;
|
||||
mod apps;
|
||||
mod page;
|
||||
mod router;
|
||||
mod handlers;
|
||||
mod rpc;
|
||||
mod api;
|
||||
mod proxypac;
|
||||
mod url;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::apps::urlhint::ContractClient;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use jsonrpc_core::{IoHandler, IoDelegate};
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use ethcore_rpc::Extendable;
|
||||
|
||||
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
|
||||
/// Indicates sync status
|
||||
pub trait SyncStatus: Send + Sync {
|
||||
/// Returns true if there is a major sync happening.
|
||||
fn is_major_importing(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
|
||||
fn is_major_importing(&self) -> bool { self() }
|
||||
}
|
||||
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder {
|
||||
dapps_path: String,
|
||||
handler: Arc<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Extendable for ServerBuilder {
|
||||
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
||||
self.handler.add_delegate(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerBuilder {
|
||||
/// Construct new dapps server
|
||||
pub fn new(dapps_path: String, registrar: Arc<ContractClient>) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path,
|
||||
handler: Arc::new(IoHandler::new()),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(|| false),
|
||||
signer_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change default sync status.
|
||||
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
|
||||
self.sync_status = status;
|
||||
}
|
||||
|
||||
/// Change default signer port.
|
||||
pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) {
|
||||
self.signer_address = signer_address;
|
||||
}
|
||||
|
||||
/// Asynchronously start server with no authentication,
|
||||
/// returns result with `Server` handle on success or an error.
|
||||
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
NoAuth,
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||
/// return result with `Server` handle on success or an error.
|
||||
pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
hosts,
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
server: Option<hyper::server::Listening>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Returns a list of allowed hosts or `None` if all hosts are allowed.
|
||||
fn allowed_hosts(hosts: Option<Vec<String>>, bind_address: String) -> Option<Vec<String>> {
|
||||
let mut allowed = Vec::new();
|
||||
|
||||
match hosts {
|
||||
Some(hosts) => allowed.extend_from_slice(&hosts),
|
||||
None => return None,
|
||||
}
|
||||
|
||||
// Add localhost domain as valid too if listening on loopback interface.
|
||||
allowed.push(bind_address.replace("127.0.0.1", "localhost").into());
|
||||
allowed.push(bind_address.into());
|
||||
Some(allowed)
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
||||
match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}", address(signer_address)),
|
||||
],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http<A: Authorization + 'static>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: String,
|
||||
signer_address: Option<(String, u16)>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
) -> 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(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone()));
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone()));
|
||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
||||
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Api,
|
||||
api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone())
|
||||
);
|
||||
special
|
||||
});
|
||||
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
|
||||
|
||||
try!(hyper::Server::http(addr))
|
||||
.handle(move |ctrl| router::Router::new(
|
||||
ctrl,
|
||||
signer_address.clone(),
|
||||
content_fetcher.clone(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
authorization.clone(),
|
||||
hosts.clone(),
|
||||
))
|
||||
.map(|(l, srv)| {
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
srv.run();
|
||||
});
|
||||
|
||||
Server {
|
||||
server: Some(l),
|
||||
panic_handler: panic_handler,
|
||||
}
|
||||
})
|
||||
.map_err(ServerError::from)
|
||||
}
|
||||
|
||||
/// Set callback for panics.
|
||||
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
|
||||
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns address that this server is bound to.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
self.server.as_ref().expect("server is always Some at the start; it's consumed only when object is dropped; qed").addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.server.take().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapp Server startup error
|
||||
#[derive(Debug)]
|
||||
pub enum ServerError {
|
||||
/// Wrapped `std::io::Error`
|
||||
IoError(std::io::Error),
|
||||
/// Other `hyper` error
|
||||
Other(hyper::error::Error),
|
||||
}
|
||||
|
||||
impl From<hyper::error::Error> for ServerError {
|
||||
fn from(err: hyper::error::Error) -> Self {
|
||||
match err {
|
||||
hyper::error::Error::Io(e) => ServerError::IoError(e),
|
||||
e => ServerError::Other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Random filename
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod util_tests {
|
||||
use super::Server;
|
||||
|
||||
#[test]
|
||||
fn should_return_allowed_hosts() {
|
||||
// given
|
||||
let bind_address = "127.0.0.1".to_owned();
|
||||
|
||||
// when
|
||||
let all = Server::allowed_hosts(None, bind_address.clone());
|
||||
let address = Server::allowed_hosts(Some(Vec::new()), bind_address.clone());
|
||||
let some = Server::allowed_hosts(Some(vec!["ethcore.io".into()]), bind_address.clone());
|
||||
|
||||
// then
|
||||
assert_eq!(all, None);
|
||||
assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()]));
|
||||
assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_cors_domains() {
|
||||
// given
|
||||
|
||||
// when
|
||||
let none = Server::cors_domains(None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]);
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use page::{handler, PageCache};
|
||||
use std::sync::Arc;
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use parity_dapps::{WebApp, File, Info};
|
||||
|
||||
pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Content of the files
|
||||
pub app: Arc<T>,
|
||||
/// 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)>,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
/// Creates new `PageEndpoint` for builtin (compile time) Dapp.
|
||||
pub fn new(app: T) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `PageEndpoint` and specify prefix that should be removed before looking for a file.
|
||||
/// It's used only for special endpoints (i.e. `/parity-utils/`)
|
||||
/// So `/parity-utils/inject.js` will be resolved to `/inject.js` is prefix is set.
|
||||
pub fn with_prefix(app: T, prefix: String) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_on: address,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
|
||||
fn info(&self) -> Option<&EndpointInfo> {
|
||||
Some(&self.info)
|
||||
}
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
Box::new(handler::PageHandler {
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||
cache: PageCache::Disabled,
|
||||
safe_to_embed_on: self.safe_to_embed_on.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Info> for EndpointInfo {
|
||||
fn from(info: Info) -> Self {
|
||||
EndpointInfo {
|
||||
name: info.name.into(),
|
||||
description: info.description.into(),
|
||||
author: info.author.into(),
|
||||
icon_url: info.icon_url.into(),
|
||||
version: info.version.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinDapp<T: WebApp + 'static> {
|
||||
app: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> BuiltinDapp<T> {
|
||||
fn new(app: Arc<T>) -> Self {
|
||||
BuiltinDapp {
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
|
||||
type DappFile = BuiltinDappFile<T>;
|
||||
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile> {
|
||||
self.app.file(path).map(|_| {
|
||||
BuiltinDappFile {
|
||||
app: self.app.clone(),
|
||||
path: path.into(),
|
||||
write_pos: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinDappFile<T: WebApp + 'static> {
|
||||
app: Arc<T>,
|
||||
path: String,
|
||||
write_pos: usize,
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> BuiltinDappFile<T> {
|
||||
fn file(&self) -> &File {
|
||||
self.app.file(&self.path).expect("Check is done when structure is created.")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WebApp + 'static> handler::DappFile for BuiltinDappFile<T> {
|
||||
fn content_type(&self) -> &str {
|
||||
self.file().content_type
|
||||
}
|
||||
|
||||
fn is_drained(&self) -> bool {
|
||||
self.write_pos == self.file().content.len()
|
||||
}
|
||||
|
||||
fn next_chunk(&mut self) -> &[u8] {
|
||||
&self.file().content[self.write_pos..]
|
||||
}
|
||||
|
||||
fn bytes_written(&mut self, bytes: usize) {
|
||||
self.write_pos += bytes;
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::Write;
|
||||
use time::{self, Duration};
|
||||
|
||||
use hyper::header;
|
||||
use hyper::server;
|
||||
use hyper::uri::RequestUri;
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
use hyper::{Decoder, Encoder, Next};
|
||||
use endpoint::EndpointPath;
|
||||
use handlers::{ContentHandler, add_security_headers};
|
||||
|
||||
/// Represents a file that can be sent to client.
|
||||
/// Implementation should keep track of bytes already sent internally.
|
||||
pub trait DappFile: Send {
|
||||
/// Returns a content-type of this file.
|
||||
fn content_type(&self) -> &str;
|
||||
|
||||
/// Checks if all bytes from that file were written.
|
||||
fn is_drained(&self) -> bool;
|
||||
|
||||
/// Fetch next chunk to write to the client.
|
||||
fn next_chunk(&mut self) -> &[u8];
|
||||
|
||||
/// How many files have been written to the client.
|
||||
fn bytes_written(&mut self, bytes: usize);
|
||||
}
|
||||
|
||||
/// Dapp as a (dynamic) set of files.
|
||||
pub trait Dapp: Send + 'static {
|
||||
/// File type
|
||||
type DappFile: DappFile;
|
||||
|
||||
/// Returns file under given path.
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile>;
|
||||
}
|
||||
|
||||
/// Currently served by `PageHandler` file
|
||||
pub enum ServedFile<T: Dapp> {
|
||||
/// File from dapp
|
||||
File(T::DappFile),
|
||||
/// Error (404)
|
||||
Error(ContentHandler),
|
||||
}
|
||||
|
||||
impl<T: Dapp> ServedFile<T> {
|
||||
pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
|
||||
ServedFile::Error(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested dapp resource was not found.",
|
||||
None,
|
||||
embeddable_on,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines what cache headers should be appended to returned resources.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PageCache {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl Default for PageCache {
|
||||
fn default() -> Self {
|
||||
PageCache::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
/// A 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 (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)>,
|
||||
/// Cache settings for this page.
|
||||
pub cache: PageCache,
|
||||
}
|
||||
|
||||
impl<T: Dapp> PageHandler<T> {
|
||||
fn extract_path(&self, path: &str) -> String {
|
||||
let app_id = &self.path.app_id;
|
||||
let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
|
||||
let prefix_with_slash = prefix.clone() + "/";
|
||||
let query_pos = path.find('?').unwrap_or_else(|| path.len());
|
||||
|
||||
// Index file support
|
||||
match path == "/" || path == &prefix || path == &prefix_with_slash {
|
||||
true => "index.html".to_owned(),
|
||||
false => if path.starts_with(&prefix_with_slash) {
|
||||
path[prefix_with_slash.len()..query_pos].to_owned()
|
||||
} else if path.starts_with("/") {
|
||||
path[1..query_pos].to_owned()
|
||||
} else {
|
||||
path[0..query_pos].to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
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_on.clone()), |f| ServedFile::File(f));
|
||||
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 {
|
||||
match self.file {
|
||||
ServedFile::File(ref f) => {
|
||||
res.set_status(StatusCode::Ok);
|
||||
|
||||
if let PageCache::Enabled = self.cache {
|
||||
let mut headers = res.headers_mut();
|
||||
let validity = Duration::days(365);
|
||||
headers.set(header::CacheControl(vec![
|
||||
header::CacheDirective::Public,
|
||||
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
|
||||
]));
|
||||
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
|
||||
}
|
||||
|
||||
match f.content_type().parse() {
|
||||
Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
|
||||
Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
|
||||
}
|
||||
|
||||
// Security headers:
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
handler.on_response(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.file {
|
||||
ServedFile::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||
ServedFile::File(ref f) if f.is_drained() => Next::end(),
|
||||
ServedFile::File(ref mut f) => match encoder.write(f.next_chunk()) {
|
||||
Ok(bytes) => {
|
||||
f.bytes_written(bytes);
|
||||
Next::write()
|
||||
},
|
||||
Err(e) => match e.kind() {
|
||||
::std::io::ErrorKind::WouldBlock => Next::write(),
|
||||
_ => Next::end(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
pub struct TestWebAppFile;
|
||||
|
||||
impl DappFile for TestWebAppFile {
|
||||
fn content_type(&self) -> &str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_drained(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn next_chunk(&mut self) -> &[u8] {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn bytes_written(&mut self, _bytes: usize) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TestWebapp;
|
||||
|
||||
impl Dapp for TestWebapp {
|
||||
type DappFile = TestWebAppFile;
|
||||
|
||||
fn file(&self, _path: &str) -> Option<Self::DappFile> {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_path_with_appid() {
|
||||
|
||||
// given
|
||||
let path1 = "/";
|
||||
let path2= "/test.css";
|
||||
let path3 = "/app/myfile.txt";
|
||||
let path4 = "/app/myfile.txt?query=123";
|
||||
let page_handler = PageHandler {
|
||||
app: test::TestWebapp,
|
||||
prefix: None,
|
||||
path: EndpointPath {
|
||||
app_id: "app".to_owned(),
|
||||
host: "".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: true,
|
||||
},
|
||||
file: ServedFile::new(None),
|
||||
cache: Default::default(),
|
||||
safe_to_embed_on: None,
|
||||
};
|
||||
|
||||
// when
|
||||
let res1 = page_handler.extract_path(path1);
|
||||
let res2 = page_handler.extract_path(path2);
|
||||
let res3 = page_handler.extract_path(path3);
|
||||
let res4 = page_handler.extract_path(path4);
|
||||
|
||||
// then
|
||||
assert_eq!(&res1, "index.html");
|
||||
assert_eq!(&res2, "test.css");
|
||||
assert_eq!(&res3, "myfile.txt");
|
||||
assert_eq!(&res4, "myfile.txt");
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use mime_guess;
|
||||
use std::io::{Seek, Read, SeekFrom};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use page::handler::{self, PageCache};
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalPageEndpoint {
|
||||
path: PathBuf,
|
||||
mime: Option<String>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl LocalPageEndpoint {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: None,
|
||||
info: Some(info),
|
||||
cache: cache,
|
||||
embeddable_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
mime: Some(mime),
|
||||
info: None,
|
||||
cache: cache,
|
||||
embeddable_on: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for LocalPageEndpoint {
|
||||
fn info(&self) -> Option<&EndpointInfo> {
|
||||
self.info.as_ref()
|
||||
}
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
if let Some(ref mime) = self.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_on: self.embeddable_on.clone(),
|
||||
cache: self.cache,
|
||||
})
|
||||
} else {
|
||||
Box::new(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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalSingleFile {
|
||||
path: PathBuf,
|
||||
mime: String,
|
||||
}
|
||||
|
||||
impl handler::Dapp for LocalSingleFile {
|
||||
type DappFile = LocalFile;
|
||||
|
||||
fn file(&self, _path: &str) -> Option<Self::DappFile> {
|
||||
LocalFile::from_path(&self.path, Some(&self.mime))
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalDapp {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl handler::Dapp for LocalDapp {
|
||||
type DappFile = LocalFile;
|
||||
|
||||
fn file(&self, file_path: &str) -> Option<Self::DappFile> {
|
||||
let mut path = self.path.clone();
|
||||
for part in file_path.split('/') {
|
||||
path.push(part);
|
||||
}
|
||||
LocalFile::from_path(&path, None)
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalFile {
|
||||
content_type: String,
|
||||
buffer: [u8; 4096],
|
||||
file: fs::File,
|
||||
len: u64,
|
||||
pos: u64,
|
||||
}
|
||||
|
||||
impl LocalFile {
|
||||
fn from_path<P: AsRef<Path>>(path: P, mime: Option<&str>) -> Option<Self> {
|
||||
// Check if file exists
|
||||
fs::File::open(&path).ok().map(|file| {
|
||||
let content_type = mime.map(|mime| mime.to_owned())
|
||||
.unwrap_or_else(|| mime_guess::guess_mime_type(path).to_string());
|
||||
let len = file.metadata().ok().map_or(0, |meta| meta.len());
|
||||
LocalFile {
|
||||
content_type: content_type,
|
||||
buffer: [0; 4096],
|
||||
file: file,
|
||||
pos: 0,
|
||||
len: len,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl handler::DappFile for LocalFile {
|
||||
fn content_type(&self) -> &str {
|
||||
&self.content_type
|
||||
}
|
||||
|
||||
fn is_drained(&self) -> bool {
|
||||
self.pos == self.len
|
||||
}
|
||||
|
||||
fn next_chunk(&mut self) -> &[u8] {
|
||||
let _ = self.file.seek(SeekFrom::Start(self.pos));
|
||||
if let Ok(n) = self.file.read(&mut self.buffer) {
|
||||
&self.buffer[0..n]
|
||||
} else {
|
||||
&self.buffer[0..0]
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_written(&mut self, bytes: usize) {
|
||||
self.pos += bytes as u64;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
|
||||
mod builtin;
|
||||
mod local;
|
||||
mod handler;
|
||||
|
||||
pub use self::local::LocalPageEndpoint;
|
||||
pub use self::builtin::PageEndpoint;
|
||||
pub use self::handler::PageCache;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Serving ProxyPac file
|
||||
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
use address;
|
||||
|
||||
pub struct ProxyPac {
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac {
|
||||
signer_address: signer_address
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for ProxyPac {
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
let signer = self.signer_address.clone()
|
||||
.map(address)
|
||||
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
|
||||
|
||||
let content = format!(
|
||||
r#"
|
||||
function FindProxyForURL(url, host) {{
|
||||
if (shExpMatch(host, "{0}{1}"))
|
||||
{{
|
||||
return "PROXY {4}";
|
||||
}}
|
||||
|
||||
if (shExpMatch(host, "*{1}"))
|
||||
{{
|
||||
return "PROXY {2}:{3}";
|
||||
}}
|
||||
|
||||
return "DIRECT";
|
||||
}}
|
||||
"#,
|
||||
HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer);
|
||||
|
||||
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! HTTP Authorization implementations
|
||||
|
||||
use std::collections::HashMap;
|
||||
use hyper::{server, net, header, status};
|
||||
use endpoint::Handler;
|
||||
use handlers::{AuthRequiredHandler, ContentHandler};
|
||||
|
||||
/// Authorization result
|
||||
pub enum Authorized {
|
||||
/// Authorization was successful.
|
||||
Yes,
|
||||
/// Unsuccessful authorization. Handler for further work is returned.
|
||||
No(Box<Handler>),
|
||||
}
|
||||
|
||||
/// Authorization interface
|
||||
pub trait Authorization : Send + Sync {
|
||||
/// Checks if authorization is valid.
|
||||
fn is_authorized(&self, req: &server::Request<net::HttpStream>)-> Authorized;
|
||||
}
|
||||
|
||||
/// HTTP Basic Authorization handler
|
||||
pub struct HttpBasicAuth {
|
||||
users: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// No-authorization implementation (authorization disabled)
|
||||
pub struct NoAuth;
|
||||
|
||||
impl Authorization for NoAuth {
|
||||
fn is_authorized(&self, _req: &server::Request<net::HttpStream>)-> Authorized {
|
||||
Authorized::Yes
|
||||
}
|
||||
}
|
||||
|
||||
impl Authorization for HttpBasicAuth {
|
||||
fn is_authorized(&self, req: &server::Request<net::HttpStream>) -> Authorized {
|
||||
let auth = self.check_auth(&req);
|
||||
|
||||
match auth {
|
||||
Access::Denied => {
|
||||
Authorized::No(Box::new(ContentHandler::error(
|
||||
status::StatusCode::Unauthorized,
|
||||
"Unauthorized",
|
||||
"You need to provide valid credentials to access this page.",
|
||||
None,
|
||||
None,
|
||||
)))
|
||||
},
|
||||
Access::AuthRequired => {
|
||||
Authorized::No(Box::new(AuthRequiredHandler))
|
||||
},
|
||||
Access::Granted => {
|
||||
Authorized::Yes
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Access {
|
||||
Granted,
|
||||
Denied,
|
||||
AuthRequired,
|
||||
}
|
||||
|
||||
impl HttpBasicAuth {
|
||||
/// Creates `HttpBasicAuth` instance with only one user.
|
||||
pub fn single_user(username: &str, password: &str) -> Self {
|
||||
let mut users = HashMap::new();
|
||||
users.insert(username.to_owned(), password.to_owned());
|
||||
HttpBasicAuth {
|
||||
users: users
|
||||
}
|
||||
}
|
||||
|
||||
fn is_authorized(&self, username: &str, password: &str) -> bool {
|
||||
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
|
||||
}
|
||||
|
||||
fn check_auth(&self, req: &server::Request<net::HttpStream>) -> Access {
|
||||
match req.headers().get::<header::Authorization<header::Basic>>() {
|
||||
Some(&header::Authorization(
|
||||
header::Basic { ref username, password: Some(ref password) }
|
||||
)) if self.is_authorized(username, password) => Access::Granted,
|
||||
Some(_) => Access::Denied,
|
||||
None => Access::AuthRequired,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use apps::DAPPS_DOMAIN;
|
||||
use hyper::{server, header, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
use jsonrpc_http_server::{is_host_header_valid};
|
||||
use handlers::ContentHandler;
|
||||
|
||||
pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String], endpoints: Vec<String>) -> bool {
|
||||
let mut endpoints = endpoints.iter()
|
||||
.map(|endpoint| format!("{}{}", endpoint, DAPPS_DOMAIN))
|
||||
.collect::<Vec<String>>();
|
||||
endpoints.extend_from_slice(allowed_hosts);
|
||||
|
||||
let header_valid = is_host_header_valid(request, &endpoints);
|
||||
|
||||
match (header_valid, request.headers().get::<header::Host>()) {
|
||||
(true, _) => true,
|
||||
(_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
|
||||
Box::new(ContentHandler::error(StatusCode::Forbidden,
|
||||
"Current Host Is Disallowed",
|
||||
"You are trying to access your node using incorrect address.",
|
||||
Some("Use allowed URL or specify different <code>hosts</code> CLI options."),
|
||||
None,
|
||||
))
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Router implementation
|
||||
//! Processes request handling authorization and dispatching it to proper application.
|
||||
|
||||
pub mod auth;
|
||||
mod host_validation;
|
||||
|
||||
use address;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::{Url, Host};
|
||||
use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode};
|
||||
use hyper::net::HttpStream;
|
||||
use apps::{self, DAPPS_DOMAIN};
|
||||
use apps::fetcher::ContentFetcher;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use handlers::{Redirection, extract_url, ContentHandler};
|
||||
use self::auth::{Authorization, Authorized};
|
||||
|
||||
/// Special endpoints are accessible on every domain (every dapp)
|
||||
#[derive(Debug, PartialEq, Hash, Eq)]
|
||||
pub enum SpecialEndpoint {
|
||||
Rpc,
|
||||
Api,
|
||||
Utils,
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Router<A: Authorization + 'static> {
|
||||
control: Option<Control>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetch: Arc<ContentFetcher>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
handler: Box<server::Handler<HttpStream> + Send>,
|
||||
}
|
||||
|
||||
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
|
||||
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
|
||||
|
||||
// Choose proper handler depending on path / domain
|
||||
let url = extract_url(&req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
|
||||
|
||||
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
|
||||
|
||||
// Validate Host header
|
||||
if let Some(ref hosts) = self.allowed_hosts {
|
||||
trace!(target: "dapps", "Validating host headers against: {:?}", hosts);
|
||||
let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect());
|
||||
if !is_valid {
|
||||
debug!(target: "dapps", "Rejecting invalid host header.");
|
||||
self.handler = host_validation::host_invalid_response();
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "dapps", "Checking authorization.");
|
||||
// Check authorization
|
||||
let auth = self.authorization.is_authorized(&req);
|
||||
if let Authorized::No(handler) = auth {
|
||||
debug!(target: "dapps", "Authorization denied.");
|
||||
self.handler = handler;
|
||||
return self.handler.on_request(req);
|
||||
}
|
||||
|
||||
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
|
||||
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
|
||||
self.handler = match endpoint {
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
|
||||
trace!(target: "dapps", "Resolving to special endpoint.");
|
||||
self.special.get(endpoint)
|
||||
.expect("special known to contain key; qed")
|
||||
.to_async_handler(path.clone().unwrap_or_default(), control)
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
trace!(target: "dapps", "Resolving to local/builtin dapp.");
|
||||
self.endpoints.get(&path.app_id)
|
||||
.expect("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) => {
|
||||
trace!(target: "dapps", "Resolving to fetchable content.");
|
||||
self.fetch.to_async_handler(path.clone(), control)
|
||||
},
|
||||
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached
|
||||
// (in the past we used 301 instead of 302)
|
||||
// It should be safe to remove it in (near) future.
|
||||
//
|
||||
// 404 for non-existent content
|
||||
(Some(ref path), _) if *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(),
|
||||
))
|
||||
},
|
||||
// Redirect any other GET request to signer.
|
||||
_ if *req.method() == hyper::Method::Get => {
|
||||
if let Some(signer_address) = self.signer_address.clone() {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
Redirection::boxed(&format!("http://{}", address(signer_address)))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."),
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
trace!(target: "dapps", "Resolving to RPC call.");
|
||||
self.special.get(&SpecialEndpoint::Rpc)
|
||||
.expect("RPC endpoint always stored; qed")
|
||||
.to_async_handler(EndpointPath::default(), control)
|
||||
}
|
||||
};
|
||||
|
||||
// Delegate on_request to proper handler
|
||||
self.handler.on_request(req)
|
||||
}
|
||||
|
||||
/// This event occurs each time the `Request` is ready to be read from.
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
self.handler.on_request_readable(decoder)
|
||||
}
|
||||
|
||||
/// This event occurs after the first time this handled signals `Next::write()`.
|
||||
fn on_response(&mut self, response: &mut server::Response) -> Next {
|
||||
self.handler.on_response(response)
|
||||
}
|
||||
|
||||
/// This event occurs each time the `Response` is ready to be written to.
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
self.handler.on_response_writable(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Authorization> Router<A> {
|
||||
pub fn new(
|
||||
control: Control,
|
||||
signer_address: Option<(String, u16)>,
|
||||
content_fetcher: Arc<ContentFetcher>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
) -> Self {
|
||||
|
||||
let handler = special.get(&SpecialEndpoint::Utils)
|
||||
.expect("Utils endpoint always stored; qed")
|
||||
.to_handler(EndpointPath::default());
|
||||
Router {
|
||||
control: Some(control),
|
||||
signer_address: signer_address,
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
authorization: authorization,
|
||||
allowed_hosts: allowed_hosts,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
|
||||
fn special_endpoint(url: &Url) -> SpecialEndpoint {
|
||||
if url.path.len() <= 1 {
|
||||
return SpecialEndpoint::None;
|
||||
}
|
||||
|
||||
match url.path[0].as_ref() {
|
||||
apps::RPC_PATH => SpecialEndpoint::Rpc,
|
||||
apps::API_PATH => SpecialEndpoint::Api,
|
||||
apps::UTILS_PATH => SpecialEndpoint::Utils,
|
||||
_ => SpecialEndpoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
match *url {
|
||||
Some(ref url) => match url.host {
|
||||
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
||||
let len = domain.len() - DAPPS_DOMAIN.len();
|
||||
let id = domain[0..len].to_owned();
|
||||
|
||||
(Some(EndpointPath {
|
||||
app_id: id,
|
||||
host: domain.clone(),
|
||||
port: url.port,
|
||||
using_dapps_domains: true,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ if url.path.len() > 1 => {
|
||||
let id = url.path[0].clone();
|
||||
(Some(EndpointPath {
|
||||
app_id: id.clone(),
|
||||
host: format!("{}", url.host),
|
||||
port: url.port,
|
||||
using_dapps_domains: false,
|
||||
}), special_endpoint(url))
|
||||
},
|
||||
_ => (None, special_endpoint(url)),
|
||||
},
|
||||
_ => (None, SpecialEndpoint::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_extract_endpoint() {
|
||||
assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None));
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "status".to_owned(),
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// With path prefix
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "rpc".to_owned(),
|
||||
host: "localhost".to_owned(),
|
||||
port: 8080,
|
||||
using_dapps_domains: false,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Utils)
|
||||
);
|
||||
|
||||
// By Subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::None)
|
||||
);
|
||||
|
||||
// RPC by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Rpc)
|
||||
);
|
||||
|
||||
// API by subdomain
|
||||
assert_eq!(
|
||||
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
|
||||
(Some(EndpointPath {
|
||||
app_id: "my.status".to_owned(),
|
||||
host: "my.status.parity".to_owned(),
|
||||
port: 80,
|
||||
using_dapps_domains: true,
|
||||
}), SpecialEndpoint::Api)
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use hyper;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
|
||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||
|
||||
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
|
||||
Box::new(RpcEndpoint {
|
||||
handler: handler,
|
||||
panic_handler: panic_handler,
|
||||
cors_domain: 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<IoHandler>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Endpoint for RpcEndpoint {
|
||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
||||
Box::new(ServerHandler::new(
|
||||
self.handler.clone(),
|
||||
self.cors_domain.clone(),
|
||||
self.allowed_hosts.clone(),
|
||||
panic_handler,
|
||||
control,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_return_error() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/empty HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#));
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_apps() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/apps HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert!(response.body.contains("Parity UI"), response.body);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_ping() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
|
||||
assert_eq!(response.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_try_to_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/content/1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d HTTP/1.1\r\n\
|
||||
Host: home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://127.0.0.1:18180\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"),
|
||||
"CORS header for signer missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_signer_port_cors_headers_for_home_parity() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://home.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"),
|
||||
"CORS header for home.parity missing: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_require_authorization() {
|
||||
// given
|
||||
let server = serve_with_auth("test", "test");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "WWW-Authenticate: Basic realm=\"Parity\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_reject_on_invalid_auth() {
|
||||
// given
|
||||
let server = serve_with_auth("test", "test");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
|
||||
assert!(response.body.contains("Unauthorized"), response.body);
|
||||
assert_eq!(response.headers_raw.contains("WWW-Authenticate"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_on_valid_auth() {
|
||||
// given
|
||||
let server = serve_with_auth("Aladdin", "OpenSesame");
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /ui/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
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);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::env;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use env_logger::LogBuilder;
|
||||
|
||||
use ServerBuilder;
|
||||
use Server;
|
||||
use apps::urlhint::ContractClient;
|
||||
use util::{Bytes, Address, Mutex, ToPretty};
|
||||
use devtools::http_client;
|
||||
|
||||
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);
|
||||
builder.init().expect("Logger is initialized only once.");
|
||||
}
|
||||
}
|
||||
|
||||
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 mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
|
||||
builder.with_sync_status(Arc::new(move || is_syncing));
|
||||
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
|
||||
(
|
||||
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
|
||||
registrar,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serve_with_auth(user: &str, pass: &str) -> Server {
|
||||
init_logger();
|
||||
let registrar = Arc::new(FakeRegistrar::new());
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar);
|
||||
builder.with_signer_address(Some(("127.0.0.1".into(), 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, false).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(None, false)
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||
init_server(None, true)
|
||||
}
|
||||
|
||||
pub fn serve() -> Server {
|
||||
init_server(None, false).0
|
||||
}
|
||||
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
http_client::request(server.addr(), request)
|
||||
}
|
||||
|
||||
pub fn assert_security_headers(headers: &[String]) {
|
||||
http_client::assert_security_headers_present(headers, None)
|
||||
}
|
||||
pub fn assert_security_headers_for_embed(headers: &[String]) {
|
||||
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve, request, assert_security_headers, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /app HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_for_users_with_cached_redirection() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /home/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_display_404_on_invalid_dapp() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /invaliddapp/ HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: invaliddapp.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Content-Type: application/json\r\n
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc_at_slash_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /rpc HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Content-Type: application/json\r\n
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_serve_proxy_pac() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /proxy/proxy.pac HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_utils() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/inject.js HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body.contains("function(){"), true);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_hosts, request};
|
||||
|
||||
#[test]
|
||||
fn should_reject_invalid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
|
||||
assert!(response.body.contains("Current Host Is Disallowed"), response.body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_valid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /ui/ HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_dapps_domains() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: ui.parity\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// NOTE [todr] This is required for error pages to be styled properly.
|
||||
fn should_allow_parity_utils_even_on_invalid_domain() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/styles.css HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_return_cors_headers_for_rpc() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /rpc HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: null\r\n\
|
||||
Content-Type: application/json\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert!(
|
||||
!response.headers_raw.contains("Access-Control-Allow-Origin"),
|
||||
"CORS headers were not expected: {:?}",
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
145
dapps/src/url.rs
145
dapps/src/url.rs
@@ -1,145 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! HTTP/HTTPS URL type. Based on URL type from Iron library.
|
||||
|
||||
use url_lib::{self};
|
||||
pub use url_lib::Host;
|
||||
|
||||
/// HTTP/HTTPS URL type for Iron.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct Url {
|
||||
/// Raw url of url
|
||||
pub raw: url_lib::Url,
|
||||
|
||||
/// The host field of the URL, probably a domain.
|
||||
pub host: Host,
|
||||
|
||||
/// The connection port.
|
||||
pub port: u16,
|
||||
|
||||
/// The URL path, the resource to be accessed.
|
||||
///
|
||||
/// A *non-empty* vector encoding the parts of the URL path.
|
||||
/// Empty entries of `""` correspond to trailing slashes.
|
||||
pub path: Vec<String>,
|
||||
|
||||
/// The URL username field, from the userinfo section of the URL.
|
||||
///
|
||||
/// `None` if the `@` character was not part of the input OR
|
||||
/// if a blank username was provided.
|
||||
/// Otherwise, a non-empty string.
|
||||
pub username: Option<String>,
|
||||
|
||||
/// The URL password field, from the userinfo section of the URL.
|
||||
///
|
||||
/// `None` if the `@` character was not part of the input OR
|
||||
/// if a blank password was provided.
|
||||
/// Otherwise, a non-empty string.
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
/// Create a URL from a string.
|
||||
///
|
||||
/// The input must be a valid URL with a special scheme for this to succeed.
|
||||
///
|
||||
/// HTTP and HTTPS are special schemes.
|
||||
///
|
||||
/// See: http://url.spec.whatwg.org/#special-scheme
|
||||
pub fn parse(input: &str) -> Result<Url, String> {
|
||||
// Parse the string using rust-url, then convert.
|
||||
match url_lib::Url::parse(input) {
|
||||
Ok(raw_url) => Url::from_generic_url(raw_url),
|
||||
Err(e) => Err(format!("{}", e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Url` from a `rust-url` `Url`.
|
||||
pub fn from_generic_url(raw_url: url_lib::Url) -> Result<Url, String> {
|
||||
// Map empty usernames to None.
|
||||
let username = match raw_url.username() {
|
||||
"" => None,
|
||||
username => Some(username.to_owned())
|
||||
};
|
||||
|
||||
// Map empty passwords to None.
|
||||
let password = match raw_url.password() {
|
||||
Some(password) if !password.is_empty() => Some(password.to_owned()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
Ok(Url {
|
||||
port: port,
|
||||
host: host,
|
||||
path: path,
|
||||
raw: raw_url,
|
||||
username: username,
|
||||
password: password,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Url;
|
||||
|
||||
#[test]
|
||||
fn test_default_port() {
|
||||
assert_eq!(Url::parse("http://example.com/wow").unwrap().port, 80u16);
|
||||
assert_eq!(Url::parse("https://example.com/wow").unwrap().port, 443u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_port() {
|
||||
assert_eq!(Url::parse("http://localhost:3097").unwrap().port, 3097u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_username() {
|
||||
assert!(Url::parse("http://@example.com").unwrap().username.is_none());
|
||||
assert!(Url::parse("http://:password@example.com").unwrap().username.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_empty_username() {
|
||||
let user = Url::parse("http://john:pass@example.com").unwrap().username;
|
||||
assert_eq!(user.unwrap(), "john");
|
||||
|
||||
let user = Url::parse("http://john:@example.com").unwrap().username;
|
||||
assert_eq!(user.unwrap(), "john");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_password() {
|
||||
assert!(Url::parse("http://michael@example.com").unwrap().password.is_none());
|
||||
assert!(Url::parse("http://:@example.com").unwrap().password.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_empty_password() {
|
||||
let pass = Url::parse("http://michael:pass@example.com").unwrap().password;
|
||||
assert_eq!(pass.unwrap(), "pass");
|
||||
|
||||
let pass = Url::parse("http://:pass@example.com").unwrap().password;
|
||||
assert_eq!(pass.unwrap(), "pass");
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
description = "Ethcore Parity UI"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "parity-ui"
|
||||
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", optional = true, branch = "beta" }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev"]
|
||||
use-precompiled-js = ["parity-ui-precompiled"]
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#[cfg(feature = "parity-ui-dev")]
|
||||
mod inner {
|
||||
extern crate parity_ui_dev;
|
||||
|
||||
pub use self::parity_ui_dev::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "parity-ui-precompiled")]
|
||||
mod inner {
|
||||
extern crate parity_ui_precompiled;
|
||||
|
||||
pub use self::parity_ui_precompiled::*;
|
||||
}
|
||||
|
||||
|
||||
pub use self::inner::*;
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
description = "Ethcore Database"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-db"
|
||||
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.96", optional = true}
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
semver = "0.2"
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
crossbeam = "0.2"
|
||||
ethcore-util = { path = "../util" }
|
||||
|
||||
[features]
|
||||
dev = ["clippy"]
|
||||
38
db/build.rs
38
db/build.rs
@@ -1,38 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethcore_ipc_codegen as codegen;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
// ipc pass
|
||||
{
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
codegen::expand(&src, &dst);
|
||||
}
|
||||
|
||||
// binary serialization pass
|
||||
{
|
||||
let src = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
codegen::expand(&src, &dst);
|
||||
}
|
||||
}
|
||||
@@ -1,566 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore rocksdb ipc service
|
||||
|
||||
use traits::*;
|
||||
use rocksdb::{DB, Writable, WriteBatch, IteratorMode, DBIterator, IndexType, Options, DBCompactionStyle, BlockBasedOptions, Direction};
|
||||
use std::sync::{RwLock, Arc};
|
||||
use std::convert::From;
|
||||
use ipc::IpcConfig;
|
||||
use std::mem;
|
||||
use ipc::binary::BinaryConvertError;
|
||||
use std::collections::{VecDeque, HashMap, BTreeMap};
|
||||
|
||||
enum WriteCacheEntry {
|
||||
Remove,
|
||||
Write(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct WriteCache {
|
||||
entries: HashMap<Vec<u8>, WriteCacheEntry>,
|
||||
preferred_len: usize,
|
||||
}
|
||||
|
||||
const FLUSH_BATCH_SIZE: usize = 4096;
|
||||
|
||||
impl WriteCache {
|
||||
fn new(cache_len: usize) -> WriteCache {
|
||||
WriteCache {
|
||||
entries: HashMap::new(),
|
||||
preferred_len: cache_len,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, key: Vec<u8>, val: Vec<u8>) {
|
||||
self.entries.insert(key, WriteCacheEntry::Write(val));
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: Vec<u8>) {
|
||||
self.entries.insert(key, WriteCacheEntry::Remove);
|
||||
}
|
||||
|
||||
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.entries.get(key).and_then(
|
||||
|vec_ref| match vec_ref {
|
||||
&WriteCacheEntry::Write(ref val) => Some(val.clone()),
|
||||
&WriteCacheEntry::Remove => None
|
||||
})
|
||||
}
|
||||
|
||||
/// WriteCache should be locked for this
|
||||
fn flush(&mut self, db: &DB, amount: usize) -> Result<(), Error> {
|
||||
let batch = WriteBatch::new();
|
||||
let mut removed_so_far = 0;
|
||||
while removed_so_far < amount {
|
||||
if self.entries.len() == 0 { break; }
|
||||
let removed_key = {
|
||||
let (key, cache_entry) = self.entries.iter().nth(0)
|
||||
.expect("if entries.len == 0, we should have break in the loop, still we got here somehow");
|
||||
|
||||
match *cache_entry {
|
||||
WriteCacheEntry::Write(ref val) => {
|
||||
try!(batch.put(&key, val));
|
||||
},
|
||||
WriteCacheEntry::Remove => {
|
||||
try!(batch.delete(&key));
|
||||
},
|
||||
}
|
||||
key.clone()
|
||||
};
|
||||
|
||||
self.entries.remove(&removed_key);
|
||||
|
||||
removed_so_far = removed_so_far + 1;
|
||||
}
|
||||
if removed_so_far > 0 {
|
||||
try!(db.write(batch));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// flushes until cache is empty
|
||||
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
|
||||
while !self.is_empty() { try!(self.flush(db, FLUSH_BATCH_SIZE)); }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
fn try_shrink(&mut self, db: &DB) -> Result<(), Error> {
|
||||
if self.entries.len() > self.preferred_len {
|
||||
try!(self.flush(db, FLUSH_BATCH_SIZE));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
db: RwLock<Option<DB>>,
|
||||
/// Iterators - dont't use between threads!
|
||||
iterators: RwLock<BTreeMap<IteratorHandle, DBIterator>>,
|
||||
write_cache: RwLock<WriteCache>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Database {}
|
||||
unsafe impl Sync for Database {}
|
||||
|
||||
impl Database {
|
||||
pub fn new() -> Database {
|
||||
Database {
|
||||
db: RwLock::new(None),
|
||||
iterators: RwLock::new(BTreeMap::new()),
|
||||
write_cache: RwLock::new(WriteCache::new(DEFAULT_CACHE_LEN)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&self) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
let db_lock = self.db.read();
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().unwrap();
|
||||
|
||||
try!(cache_lock.try_shrink(&db));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn flush_all(&self) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
let db_lock = self.db.read();
|
||||
if db_lock.is_none() { return Ok(()); }
|
||||
let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step");
|
||||
|
||||
try!(cache_lock.flush_all(&db));
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Database {
|
||||
fn drop(&mut self) {
|
||||
self.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[ipc]
|
||||
impl DatabaseService for Database {
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error> {
|
||||
let mut db = self.db.write();
|
||||
if db.is_some() { return Err(Error::AlreadyOpen); }
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.set_max_open_files(256);
|
||||
opts.create_if_missing(true);
|
||||
opts.set_use_fsync(false);
|
||||
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
|
||||
if let Some(size) = config.prefix_size {
|
||||
let mut block_opts = BlockBasedOptions::new();
|
||||
block_opts.set_index_type(IndexType::HashSearch);
|
||||
opts.set_block_based_table_factory(&block_opts);
|
||||
opts.set_prefix_extractor_fixed_size(size);
|
||||
}
|
||||
*db = Some(try!(DB::open(&opts, &path)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Opens database in the specified path with the default config
|
||||
fn open_default(&self, path: String) -> Result<(), Error> {
|
||||
self.open(DatabaseConfig::default(), path)
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
try!(self.flush_all());
|
||||
|
||||
let mut db = self.db.write();
|
||||
if db.is_none() { return Err(Error::IsClosed); }
|
||||
|
||||
*db = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
cache_lock.write(key.to_vec(), value.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete(&self, key: &[u8]) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
cache_lock.remove(key.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write();
|
||||
|
||||
let mut writes = transaction.writes.borrow_mut();
|
||||
for kv in writes.drain(..) {
|
||||
cache_lock.write(kv.key, kv.value);
|
||||
}
|
||||
|
||||
let mut removes = transaction.removes.borrow_mut();
|
||||
for k in removes.drain(..) {
|
||||
cache_lock.remove(k);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
{
|
||||
let key_vec = key.to_vec();
|
||||
let cache_hit = self.write_cache.read().get(&key_vec);
|
||||
|
||||
if cache_hit.is_some() {
|
||||
return Ok(Some(cache_hit.expect("cache_hit.is_some() = true, still there is none somehow here")))
|
||||
}
|
||||
}
|
||||
let db_lock = self.db.read();
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
match try!(db.get(key)) {
|
||||
Some(db_vec) => {
|
||||
Ok(Some(db_vec.to_vec()))
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward));
|
||||
match iter.next() {
|
||||
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
|
||||
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Ok(Some(v.to_vec())) } else { Ok(None) },
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> Result<bool, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
Ok(db.iterator(IteratorMode::Start).next().is_none())
|
||||
}
|
||||
|
||||
fn iter(&self) -> Result<IteratorHandle, Error> {
|
||||
let db_lock = self.db.read();
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
let mut iterators = self.iterators.write();
|
||||
let next_iterator = iterators.keys().last().unwrap_or(&0) + 1;
|
||||
iterators.insert(next_iterator, db.iterator(IteratorMode::Start));
|
||||
Ok(next_iterator)
|
||||
}
|
||||
|
||||
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue>
|
||||
{
|
||||
let mut iterators = self.iterators.write();
|
||||
let mut iterator = match iterators.get_mut(&handle) {
|
||||
Some(some_iterator) => some_iterator,
|
||||
None => { return None; },
|
||||
};
|
||||
|
||||
iterator.next().and_then(|(some_key, some_val)| {
|
||||
Some(KeyValue {
|
||||
key: some_key.to_vec(),
|
||||
value: some_val.to_vec(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error> {
|
||||
let mut iterators = self.iterators.write();
|
||||
iterators.remove(&handle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : put proper at compile-time
|
||||
impl IpcConfig for Database {}
|
||||
|
||||
/// Database iterator
|
||||
pub struct DatabaseIterator {
|
||||
client: Arc<DatabaseClient<::nanomsg::Socket>>,
|
||||
handle: IteratorHandle,
|
||||
}
|
||||
|
||||
impl Iterator for DatabaseIterator {
|
||||
type Item = (Vec<u8>, Vec<u8>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.client.iter_next(self.handle).and_then(|kv| Some((kv.key, kv.value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DatabaseIterator {
|
||||
fn drop(&mut self) {
|
||||
self.client.dispose_iter(self.handle).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::Database;
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
|
||||
#[test]
|
||||
fn can_be_created() {
|
||||
let db = Database::new();
|
||||
assert!(db.is_empty().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_open_empty() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
assert!(db.is_empty().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_store_key() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.flush_all().unwrap();
|
||||
assert!(!db.is_empty().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_retrieve() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.close().unwrap();
|
||||
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(db.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod write_cache_tests {
|
||||
use super::Database;
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
|
||||
#[test]
|
||||
fn cache_write_flush() {
|
||||
let db = Database::new();
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
db.open_default(path.as_str().to_owned()).unwrap();
|
||||
db.put("100500".as_bytes(), "1".as_bytes()).unwrap();
|
||||
db.delete("100500".as_bytes()).unwrap();
|
||||
db.flush_all().unwrap();
|
||||
|
||||
let val = db.get("100500".as_bytes()).unwrap();
|
||||
assert!(val.is_none());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod client_tests {
|
||||
use super::{DatabaseClient, Database};
|
||||
use traits::*;
|
||||
use devtools::*;
|
||||
use nanoipc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{Ordering, AtomicBool};
|
||||
use crossbeam;
|
||||
use run_worker;
|
||||
|
||||
fn init_worker(addr: &str) -> nanoipc::Worker<Database> {
|
||||
let mut worker = nanoipc::Worker::<Database>::new(&Arc::new(Database::new()));
|
||||
worker.add_duplex(addr).unwrap();
|
||||
worker
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_call_handshake() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-10.ipc";
|
||||
let worker_should_exit = Arc::new(AtomicBool::new(false));
|
||||
let worker_is_ready = Arc::new(AtomicBool::new(false));
|
||||
let c_worker_should_exit = worker_should_exit.clone();
|
||||
let c_worker_is_ready = worker_is_ready.clone();
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
let mut worker = init_worker(url);
|
||||
while !c_worker_should_exit.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
c_worker_is_ready.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
while !worker_is_ready.load(Ordering::Relaxed) { }
|
||||
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
let hs = client.handshake();
|
||||
|
||||
worker_should_exit.store(true, Ordering::Relaxed);
|
||||
assert!(hs.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_open_db() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-20.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
let worker_should_exit = Arc::new(AtomicBool::new(false));
|
||||
let worker_is_ready = Arc::new(AtomicBool::new(false));
|
||||
let c_worker_should_exit = worker_should_exit.clone();
|
||||
let c_worker_is_ready = worker_is_ready.clone();
|
||||
|
||||
::std::thread::spawn(move || {
|
||||
let mut worker = init_worker(url);
|
||||
while !c_worker_should_exit.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
c_worker_is_ready.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
while !worker_is_ready.load(Ordering::Relaxed) { }
|
||||
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert!(client.is_empty().unwrap());
|
||||
worker_should_exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_put() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-30.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
client.close().unwrap();
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_put_and_read() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-40.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_empty() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-45.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert!(client.get("xxx".as_bytes()).unwrap().is_none());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn can_commit_client_transaction() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-60.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(move |scope| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
run_worker(scope, stop.clone(), url);
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
|
||||
let transaction = DBTransaction::new();
|
||||
transaction.put("xxx".as_bytes(), "1".as_bytes());
|
||||
client.write(transaction).unwrap();
|
||||
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_write_read_ipc() {
|
||||
let url = "ipc:///tmp/parity-db-ipc-test-70.ipc";
|
||||
let path = RandomTempPath::create_dir();
|
||||
|
||||
crossbeam::scope(|scope| {
|
||||
let stop = StopGuard::new();
|
||||
run_worker(&scope, stop.share(), url);
|
||||
|
||||
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
let mut batch = Vec::new();
|
||||
for _ in 0..100 {
|
||||
batch.push((random_str(256).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
|
||||
batch.push((random_str(256).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
|
||||
batch.push((random_str(2048).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
|
||||
batch.push((random_str(2048).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
|
||||
}
|
||||
|
||||
for &(ref k, ref v) in batch.iter() {
|
||||
client.put(k, v).unwrap();
|
||||
}
|
||||
client.close().unwrap();
|
||||
|
||||
client.open_default(path.as_str().to_owned()).unwrap();
|
||||
for &(ref k, ref v) in batch.iter() {
|
||||
assert_eq!(v, &client.get(k).unwrap().unwrap());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Database ipc service
|
||||
|
||||
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethcore_ipc as ipc;
|
||||
extern crate rocksdb;
|
||||
extern crate ethcore_devtools as devtools;
|
||||
extern crate semver;
|
||||
extern crate ethcore_ipc_nano as nanoipc;
|
||||
extern crate nanomsg;
|
||||
extern crate crossbeam;
|
||||
extern crate ethcore_util as util;
|
||||
|
||||
pub mod database;
|
||||
pub mod traits;
|
||||
|
||||
pub use traits::{DatabaseService, DBTransaction, Error};
|
||||
pub use database::{Database, DatabaseClient, DatabaseIterator};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type DatabaseNanoClient = DatabaseClient<::nanomsg::Socket>;
|
||||
pub type DatabaseConnection = nanoipc::GuardedSocket<DatabaseNanoClient>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServiceError {
|
||||
Io(std::io::Error),
|
||||
Socket(nanoipc::SocketError),
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for ServiceError {
|
||||
fn from(io_error: std::io::Error) -> ServiceError { ServiceError::Io(io_error) }
|
||||
}
|
||||
|
||||
impl std::convert::From<nanoipc::SocketError> for ServiceError {
|
||||
fn from(socket_error: nanoipc::SocketError) -> ServiceError { ServiceError::Socket(socket_error) }
|
||||
}
|
||||
|
||||
pub fn blocks_service_url(db_path: &str) -> Result<String, std::io::Error> {
|
||||
let mut path = PathBuf::from(db_path);
|
||||
try!(::std::fs::create_dir_all(db_path));
|
||||
path.push("blocks.ipc");
|
||||
Ok(format!("ipc://{}", path.to_str().unwrap()))
|
||||
}
|
||||
|
||||
pub fn extras_service_url(db_path: &str) -> Result<String, ::std::io::Error> {
|
||||
let mut path = PathBuf::from(db_path);
|
||||
try!(::std::fs::create_dir_all(db_path));
|
||||
path.push("extras.ipc");
|
||||
Ok(format!("ipc://{}", path.to_str().unwrap()))
|
||||
}
|
||||
|
||||
pub fn blocks_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = try!(blocks_service_url(db_path));
|
||||
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
|
||||
let url = try!(extras_service_url(db_path));
|
||||
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
// for tests
|
||||
pub fn run_worker(scope: &crossbeam::Scope, stop: Arc<AtomicBool>, socket_path: &str) {
|
||||
let socket_path = socket_path.to_owned();
|
||||
scope.spawn(move || {
|
||||
let mut worker = nanoipc::Worker::new(&Arc::new(Database::new()));
|
||||
worker.add_reqrep(&socket_path).unwrap();
|
||||
while !stop.load(Ordering::Relaxed) {
|
||||
worker.poll();
|
||||
}
|
||||
});
|
||||
}
|
||||
135
db/src/traits.rs
135
db/src/traits.rs
@@ -1,135 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore database trait
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub type IteratorHandle = u32;
|
||||
|
||||
pub const DEFAULT_CACHE_LEN: usize = 12288;
|
||||
|
||||
#[derive(Binary)]
|
||||
pub struct KeyValue {
|
||||
pub key: Vec<u8>,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Binary)]
|
||||
pub enum Error {
|
||||
AlreadyOpen,
|
||||
IsClosed,
|
||||
RocksDb(String),
|
||||
TransactionUnknown,
|
||||
IteratorUnknown,
|
||||
UncommitedTransactions,
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Error {
|
||||
Error::RocksDb(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Database configuration
|
||||
#[derive(Binary)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Optional prefix size in bytes. Allows lookup by partial key.
|
||||
pub prefix_size: Option<usize>,
|
||||
/// write cache length
|
||||
pub cache: usize,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: None,
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
fn with_prefix(prefix: usize) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: Some(prefix),
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatabaseService : Sized {
|
||||
/// Opens database in the specified path
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Opens database in the specified path with the default config
|
||||
fn open_default(&self, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Closes database
|
||||
fn close(&self) -> Result<(), Error>;
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten.
|
||||
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Delete value by key.
|
||||
fn delete(&self, key: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Get value by key.
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Get value by partial key. Prefix size should match configured prefix size.
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Check if there is anything in the database.
|
||||
fn is_empty(&self) -> Result<bool, Error>;
|
||||
|
||||
/// Get handle to iterate through keys
|
||||
fn iter(&self) -> Result<IteratorHandle, Error>;
|
||||
|
||||
/// Next key-value for the the given iterator
|
||||
fn iter_next(&self, iterator: IteratorHandle) -> Option<KeyValue>;
|
||||
|
||||
/// Dispose iteration that is no longer needed
|
||||
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error>;
|
||||
|
||||
/// Write client transaction
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Binary)]
|
||||
pub struct DBTransaction {
|
||||
pub writes: RefCell<Vec<KeyValue>>,
|
||||
pub removes: RefCell<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl DBTransaction {
|
||||
pub fn new() -> DBTransaction {
|
||||
DBTransaction {
|
||||
writes: RefCell::new(Vec::new()),
|
||||
removes: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &[u8], value: &[u8]) {
|
||||
let mut brw = self.writes.borrow_mut();
|
||||
brw.push(KeyValue { key: key.to_vec(), value: value.to_vec() });
|
||||
}
|
||||
|
||||
pub fn delete(&self, key: &[u8]) {
|
||||
let mut brw = self.removes.borrow_mut();
|
||||
brw.push(key.to_vec());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
8
|
||||
@@ -1,13 +0,0 @@
|
||||
Package: parity
|
||||
Version: 1.4.0
|
||||
Source: parity
|
||||
Section: science
|
||||
Priority: extra
|
||||
Maintainer: Ethcore <devops@ethcore.io>
|
||||
Build-Depends: debhelper (>=9)
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://ethcore.io
|
||||
Vcs-Git: git://github.com/ethcore/parity.git
|
||||
Vcs-Browser: https://github.com/ethcore/parity
|
||||
Architecture: armhf
|
||||
Description: Ethereum network client by Ethcore
|
||||
@@ -1 +0,0 @@
|
||||
https://github.com/ethcore/parity/wiki
|
||||
@@ -1 +0,0 @@
|
||||
d3a6fd5582dd14ad718b2eb1a0662c3e817a63cf
|
||||
@@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-devtools"
|
||||
version = "1.4.0"
|
||||
version = "1.0.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
1
devtools/README.md
Normal file
1
devtools/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# ethcore dev tools
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::io::{Read, Write};
|
||||
use std::str::{self, Lines};
|
||||
use std::net::{TcpStream, SocketAddr};
|
||||
|
||||
pub struct Response {
|
||||
pub status: String,
|
||||
pub headers: Vec<String>,
|
||||
pub headers_raw: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
pub fn read_block(lines: &mut Lines, all: bool) -> String {
|
||||
let mut block = String::new();
|
||||
loop {
|
||||
let line = lines.next();
|
||||
match line {
|
||||
None => break,
|
||||
Some("") if !all => break,
|
||||
Some(v) => {
|
||||
block.push_str(v);
|
||||
block.push_str("\n");
|
||||
},
|
||||
}
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
pub fn request(address: &SocketAddr, request: &str) -> Response {
|
||||
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();
|
||||
let _ = req.read_to_string(&mut response);
|
||||
|
||||
let mut lines = response.lines();
|
||||
let status = lines.next().unwrap().to_owned();
|
||||
let headers_raw = read_block(&mut lines, false);
|
||||
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
|
||||
let body = read_block(&mut lines, true);
|
||||
|
||||
Response {
|
||||
status: status,
|
||||
headers: headers,
|
||||
headers_raw: headers_raw,
|
||||
body: body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if all required security headers are present
|
||||
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
|
||||
if let Some(port) = port {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(),
|
||||
"X-Frame-Options: ALLOW-FROM missing: {:?}", headers
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
|
||||
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(),
|
||||
"X-XSS-Protection missing: {:?}", headers
|
||||
);
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(),
|
||||
"X-Content-Type-Options missing: {:?}", headers
|
||||
);
|
||||
}
|
||||
@@ -19,11 +19,6 @@
|
||||
|
||||
extern crate rand;
|
||||
|
||||
mod random_path;
|
||||
mod test_socket;
|
||||
mod stop_guard;
|
||||
pub mod http_client;
|
||||
pub mod random_path;
|
||||
|
||||
pub use random_path::*;
|
||||
pub use test_socket::*;
|
||||
pub use stop_guard::*;
|
||||
|
||||
@@ -19,20 +19,14 @@
|
||||
use std::path::*;
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use rand::random;
|
||||
|
||||
pub struct RandomTempPath {
|
||||
path: PathBuf,
|
||||
pub panic_on_drop_failure: bool,
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
pub fn random_filename() -> String {
|
||||
random_str(8)
|
||||
}
|
||||
|
||||
pub fn random_str(len: usize) -> String {
|
||||
(0..len).map(|_| ((random::<f32>() * 26.0) as u8 + 97) as char).collect()
|
||||
(0..8).map(|_| ((random::<f32>() * 26.0) as u8 + 97) as char).collect()
|
||||
}
|
||||
|
||||
impl RandomTempPath {
|
||||
@@ -40,8 +34,7 @@ impl RandomTempPath {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(random_filename());
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +43,7 @@ impl RandomTempPath {
|
||||
dir.push(random_filename());
|
||||
fs::create_dir_all(dir.as_path()).unwrap();
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,67 +54,16 @@ impl RandomTempPath {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.path.to_str().unwrap()
|
||||
}
|
||||
|
||||
pub fn new_in(&self, name: &str) -> String {
|
||||
let mut path = self.path.clone();
|
||||
path.push(name);
|
||||
path.to_str().unwrap().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for RandomTempPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
impl Deref for RandomTempPath {
|
||||
type Target = Path;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandomTempPath {
|
||||
fn drop(&mut self) {
|
||||
if let Err(_) = fs::remove_dir_all(&self) {
|
||||
if let Err(e) = fs::remove_file(&self) {
|
||||
if self.panic_on_drop_failure {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(self.as_path()) {
|
||||
panic!("failed to remove temp directory, probably something failed to destroyed ({})", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuardedTempResult<T> {
|
||||
pub result: Option<T>,
|
||||
pub _temp: RandomTempPath
|
||||
}
|
||||
|
||||
impl<T> GuardedTempResult<T> {
|
||||
pub fn reference(&self) -> &T {
|
||||
self.result.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn reference_mut(&mut self) -> &mut T {
|
||||
self.result.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> T {
|
||||
self.result.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for GuardedTempResult<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T { self.result.as_ref().unwrap() }
|
||||
}
|
||||
|
||||
impl<T> DerefMut for GuardedTempResult<T> {
|
||||
fn deref_mut(&mut self) -> &mut T { self.result.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creates_dir() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! Stop guard mod
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::*;
|
||||
|
||||
/// Stop guard that will set a stop flag on drop
|
||||
pub struct StopGuard {
|
||||
flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl StopGuard {
|
||||
/// Create a stop guard
|
||||
pub fn new() -> StopGuard {
|
||||
StopGuard {
|
||||
flag: Arc::new(AtomicBool::new(false))
|
||||
}
|
||||
}
|
||||
|
||||
/// Share stop guard between the threads
|
||||
pub fn share(&self) -> Arc<AtomicBool> {
|
||||
self.flag.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StopGuard {
|
||||
fn drop(&mut self) {
|
||||
self.flag.store(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::*;
|
||||
use std::cmp;
|
||||
|
||||
pub struct TestSocket {
|
||||
pub read_buffer: Vec<u8>,
|
||||
pub write_buffer: Vec<u8>,
|
||||
pub cursor: usize,
|
||||
pub buf_size: usize,
|
||||
}
|
||||
|
||||
impl Default for TestSocket {
|
||||
fn default() -> Self {
|
||||
TestSocket::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSocket {
|
||||
pub fn new() -> Self {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_buf(buf_size: usize) -> TestSocket {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: buf_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ready(data: Vec<u8>) -> TestSocket {
|
||||
TestSocket {
|
||||
read_buffer: data,
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for TestSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
let end_position = cmp::min(self.read_buffer.len(), self.cursor+buf.len());
|
||||
if self.cursor > end_position { return Ok(0) }
|
||||
let len = cmp::max(end_position - self.cursor, 0);
|
||||
match len {
|
||||
0 => Ok(0),
|
||||
_ => {
|
||||
for i in self.cursor..end_position {
|
||||
buf[i-self.cursor] = self.read_buffer[i];
|
||||
}
|
||||
self.cursor = end_position;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestSocket {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
if self.buf_size == 0 || buf.len() < self.buf_size {
|
||||
self.write_buffer.extend(buf.iter().cloned());
|
||||
Ok(buf.len())
|
||||
}
|
||||
else {
|
||||
self.write_buffer.extend(buf.iter().take(self.buf_size).cloned());
|
||||
Ok(self.buf_size)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
11
doc.sh
Executable file
11
doc.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# generate documentation only for partiy and ethcore libraries
|
||||
|
||||
cargo doc --no-deps --verbose \
|
||||
-p ethash \
|
||||
-p ethcore-util \
|
||||
-p ethcore \
|
||||
-p ethsync \
|
||||
-p ethcore-rpc \
|
||||
-p parity \
|
||||
-p ethminer
|
||||
@@ -1,33 +0,0 @@
|
||||
FROM centos:latest
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN yum -y update&& \
|
||||
yum install -y git make gcc-c++ gcc file binutils
|
||||
# install rustup
|
||||
RUN curl -sSf https://static.rust-lang.org/rustup.sh -o rustup.sh &&\
|
||||
ls&&\
|
||||
sh rustup.sh -s -- --disable-sudo
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
# set compiler
|
||||
ENV CXX g++
|
||||
ENV CC gcc
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity&&\
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
ls -a&&\
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
@@ -1,47 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
curl git make g++ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
|
||||
libc6-arm64-cross libc6-dev-arm64-cross wget file ca-certificates \
|
||||
binutils-aarch64-linux-gnu \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
ENV RUST_TARGETS="aarch64-unknown-linux-gnu"
|
||||
|
||||
# multirust add arm--linux-gnuabhf toolchain
|
||||
RUN rustup target add aarch64-unknown-linux-gnu
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.aarch64-unknown-linux-gnu]\n\
|
||||
linker = "aarch64-linux-gnu-gcc"\n'\
|
||||
>>.cargo/config && \
|
||||
cat .cargo/config && \
|
||||
cargo build --target aarch64-unknown-linux-gnu --release --verbose && \
|
||||
ls /build/parity/target/aarch64-unknown-linux-gnu/release/parity && \
|
||||
/usr/bin/aarch64-linux-gnu-strip /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
RUN file /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/aarch64-unknown-linux-gnu/release/parity"]
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
curl git make g++ gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf \
|
||||
libc6-dev-armhf-cross wget file ca-certificates \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
ENV RUST_TARGETS="arm-unknown-linux-gnueabihf"
|
||||
|
||||
# multirust add arm--linux-gnuabhf toolchain
|
||||
RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.armv7-unknown-linux-gnueabihf]\n\
|
||||
linker = "arm-linux-gnueabihf-gcc"\n'\
|
||||
>>.cargo/config && \
|
||||
cat .cargo/config && \
|
||||
cargo build --target armv7-unknown-linux-gnueabihf --release --verbose && \
|
||||
ls /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity && \
|
||||
/usr/bin/arm-linux-gnueabihf-strip /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
RUN file /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/armv7-unknown-linux-gnueabihf/release/parity"]
|
||||
@@ -27,11 +27,11 @@ RUN git clone https://github.com/debris/evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
# install multirust
|
||||
RUN curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh -s -- --yes
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
# export rust LIBRARY_PATH
|
||||
ENV LIBRARY_PATH /usr/local/lib
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
@@ -1,59 +1,42 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
binutils \
|
||||
file \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
|
||||
# cmake and llvm ppas. then update ppas
|
||||
# cmake, llvm and rocksdb ppas. then update ppas
|
||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
|
||||
# install evmjit
|
||||
RUN git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
# install multirust
|
||||
RUN curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh -s -- --yes
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
# export rust LIBRARY_PATH
|
||||
ENV LIBRARY_PATH /usr/local/lib
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
cargo build --release --features ethcore/jit --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
cd parity && \
|
||||
cargo build --release --features ethcore/jit
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
# install tools and dependencies
|
||||
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
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout stable && \
|
||||
git pull && \
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
@@ -1,40 +1,23 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
make
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
# install multirust
|
||||
RUN curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh -s -- --yes
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
# export rust LIBRARY_PATH
|
||||
ENV LIBRARY_PATH /usr/local/lib
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout beta && \
|
||||
git pull && \
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
cd parity && \
|
||||
cargo build --release
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ethash"
|
||||
version = "1.4.0"
|
||||
version = "1.0.0"
|
||||
authors = ["arkpar <arkadiy@ethcore.io"]
|
||||
|
||||
[lib]
|
||||
@@ -9,4 +9,3 @@ authors = ["arkpar <arkadiy@ethcore.io"]
|
||||
log = "0.3"
|
||||
sha3 = { path = "../util/sha3" }
|
||||
primal = "0.2.3"
|
||||
parking_lot = "0.3"
|
||||
|
||||
@@ -20,17 +20,14 @@
|
||||
// TODO: fix endianess for big endian
|
||||
|
||||
use primal::is_prime;
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use sha3;
|
||||
use std::slice;
|
||||
use std::path::PathBuf;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{Read, Write, self};
|
||||
use std::fs::{self, File};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub const ETHASH_EPOCH_LENGTH: u64 = 30000;
|
||||
pub const ETHASH_CACHE_ROUNDS: usize = 3;
|
||||
pub const ETHASH_MIX_BYTES: usize = 128;
|
||||
@@ -45,14 +42,14 @@ const NODE_WORDS: usize = 64 / 4;
|
||||
const NODE_BYTES: usize = 64;
|
||||
const MIX_WORDS: usize = ETHASH_MIX_BYTES / 4;
|
||||
const MIX_NODES: usize = MIX_WORDS / NODE_WORDS;
|
||||
const FNV_PRIME: u32 = 0x01000193;
|
||||
const FNV_PRIME: u32 = 0x01000193;
|
||||
|
||||
/// Computation result
|
||||
pub struct ProofOfWork {
|
||||
/// Difficulty boundary
|
||||
pub value: H256,
|
||||
/// Mix
|
||||
pub mix_hash: H256,
|
||||
pub mix_hash: H256
|
||||
}
|
||||
|
||||
struct Node {
|
||||
@@ -88,10 +85,9 @@ pub type H256 = [u8; 32];
|
||||
pub struct Light {
|
||||
block_number: u64,
|
||||
cache: Vec<Node>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
}
|
||||
|
||||
/// Light cache structure
|
||||
/// Light cache structur
|
||||
impl Light {
|
||||
/// Create a new light cache for a given block number
|
||||
pub fn new(block_number: u64) -> Light {
|
||||
@@ -105,17 +101,17 @@ impl Light {
|
||||
light_compute(self, header_hash, nonce)
|
||||
}
|
||||
|
||||
pub fn file_path(seed_hash: H256) -> PathBuf {
|
||||
pub fn file_path(block_number: u64) -> PathBuf {
|
||||
let mut home = ::std::env::home_dir().unwrap();
|
||||
home.push(".ethash");
|
||||
home.push("light");
|
||||
let seed_hash = get_seedhash(block_number);
|
||||
home.push(to_hex(&seed_hash));
|
||||
home
|
||||
}
|
||||
|
||||
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 path = Light::file_path(block_number);
|
||||
let mut file = try!(File::open(path));
|
||||
|
||||
let cache_size = get_cache_size(block_number);
|
||||
@@ -130,82 +126,21 @@ impl Light {
|
||||
Ok(Light {
|
||||
cache: nodes,
|
||||
block_number: block_number,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_file(&self) -> io::Result<PathBuf> {
|
||||
let seed_compute = self.seed_compute.lock();
|
||||
let path = Light::file_path(seed_compute.get_seedhash(self.block_number));
|
||||
|
||||
if self.block_number >= ETHASH_EPOCH_LENGTH * 2 {
|
||||
let deprecated = Light::file_path(
|
||||
seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2));
|
||||
|
||||
if deprecated.exists() {
|
||||
debug!(target: "ethash", "removing: {:?}", &deprecated);
|
||||
try!(fs::remove_file(deprecated));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_file(&self) -> io::Result<()> {
|
||||
let path = Light::file_path(self.block_number);
|
||||
try!(fs::create_dir_all(path.parent().unwrap()));
|
||||
let mut file = try!(File::create(&path));
|
||||
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) };
|
||||
try!(file.write(buf));
|
||||
Ok(path)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SeedHashCompute {
|
||||
prev_epoch: Cell<u64>,
|
||||
prev_seedhash: Cell<H256>,
|
||||
}
|
||||
|
||||
impl SeedHashCompute {
|
||||
#[inline]
|
||||
pub fn new() -> SeedHashCompute {
|
||||
SeedHashCompute {
|
||||
prev_epoch: Cell::new(0),
|
||||
prev_seedhash: Cell::new([0u8; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_cache(&self) {
|
||||
self.prev_epoch.set(0);
|
||||
self.prev_seedhash.set([0u8; 32]);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_seedhash(&self, block_number: u64) -> H256 {
|
||||
let epoch = block_number / ETHASH_EPOCH_LENGTH;
|
||||
if epoch < self.prev_epoch.get() {
|
||||
// can't build on previous hash if requesting an older block
|
||||
self.reset_cache();
|
||||
}
|
||||
if epoch > self.prev_epoch.get() {
|
||||
let seed_hash = SeedHashCompute::resume_compute_seedhash(self.prev_seedhash.get(), self.prev_epoch.get(), epoch);
|
||||
self.prev_seedhash.set(seed_hash);
|
||||
self.prev_epoch.set(epoch);
|
||||
}
|
||||
self.prev_seedhash.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resume_compute_seedhash(mut hash: H256, start_epoch: u64, end_epoch: u64) -> H256 {
|
||||
for _ in start_epoch..end_epoch {
|
||||
unsafe { sha3::sha3_256(hash[..].as_mut_ptr(), 32, hash[..].as_ptr(), 32) };
|
||||
}
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slow_get_seedhash(block_number: u64) -> H256 {
|
||||
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
return x.wrapping_mul(FNV_PRIME) ^ y;
|
||||
@@ -218,24 +153,34 @@ fn sha3_512(input: &[u8], output: &mut [u8]) {
|
||||
|
||||
#[inline]
|
||||
fn get_cache_size(block_number: u64) -> usize {
|
||||
let mut sz: u64 = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - NODE_BYTES as u64;
|
||||
while !is_prime(sz / NODE_BYTES as u64) {
|
||||
sz = sz - 2 * NODE_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
let mut sz: u64 = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - NODE_BYTES as u64;
|
||||
while !is_prime(sz / NODE_BYTES as u64) {
|
||||
sz = sz - 2 * NODE_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_data_size(block_number: u64) -> usize {
|
||||
let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - ETHASH_MIX_BYTES as u64;
|
||||
while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
|
||||
sz = sz - 2 * ETHASH_MIX_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - ETHASH_MIX_BYTES as u64;
|
||||
while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
|
||||
sz = sz - 2 * ETHASH_MIX_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Given the `block_number`, determine the seed hash for Ethash.
|
||||
pub fn get_seedhash(block_number: u64) -> H256 {
|
||||
let epochs = block_number / ETHASH_EPOCH_LENGTH;
|
||||
let mut ret: H256 = [0u8; 32];
|
||||
for _ in 0..epochs {
|
||||
unsafe { sha3::sha3_256(ret[..].as_mut_ptr(), 32, ret[..].as_ptr(), 32) };
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Difficulty quick check for POW preverification
|
||||
///
|
||||
@@ -266,12 +211,12 @@ pub fn light_compute(light: &Light, header_hash: &H256, nonce: u64) -> ProofOfWo
|
||||
hash_compute(light, full_size, header_hash, nonce)
|
||||
}
|
||||
|
||||
fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
if full_size % MIX_WORDS != 0 {
|
||||
panic!("Unaligned full size");
|
||||
}
|
||||
// pack hash and nonce together into first 40 bytes of s_mix
|
||||
let mut s_mix: [Node; MIX_NODES + 1] = [Node::default(), Node::default(), Node::default()];
|
||||
let mut s_mix: [Node; MIX_NODES + 1] = [ Node::default(), Node::default(), Node::default() ];
|
||||
unsafe { ptr::copy_nonoverlapping(header_hash.as_ptr(), s_mix.get_unchecked_mut(0).bytes.as_mut_ptr(), 32) };
|
||||
unsafe { ptr::copy_nonoverlapping(mem::transmute(&nonce), s_mix.get_unchecked_mut(0).bytes[32..].as_mut_ptr(), 8) };
|
||||
|
||||
@@ -285,12 +230,11 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
|
||||
let page_size = 4 * MIX_WORDS;
|
||||
let num_full_pages = (full_size / page_size) as u32;
|
||||
let cache: &[Node] = &light.cache; // deref once for better performance
|
||||
|
||||
for i in 0..(ETHASH_ACCESSES as u32) {
|
||||
let index = fnv_hash(f_mix.get_unchecked(0).as_words().get_unchecked(0) ^ i, *mix.get_unchecked(0).as_words().get_unchecked((i as usize) % MIX_WORDS)) % num_full_pages;
|
||||
for n in 0..MIX_NODES {
|
||||
let tmp_node = calculate_dag_item(index * MIX_NODES as u32 + n as u32, cache);
|
||||
let tmp_node = calculate_dag_item(index * MIX_NODES as u32 + n as u32, light);
|
||||
for w in 0..NODE_WORDS {
|
||||
*mix.get_unchecked_mut(n).as_words_mut().get_unchecked_mut(w) = fnv_hash(*mix.get_unchecked(n).as_words().get_unchecked(w), *tmp_node.as_words().get_unchecked(w));
|
||||
}
|
||||
@@ -313,7 +257,7 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
ptr::copy_nonoverlapping(mix.get_unchecked_mut(0).bytes.as_ptr(), buf[64..].as_mut_ptr(), 32);
|
||||
ptr::copy_nonoverlapping(mix.get_unchecked_mut(0).bytes.as_ptr(), mix_hash.as_mut_ptr(), 32);
|
||||
let mut value: H256 = [0u8; 32];
|
||||
sha3::sha3_256(value.as_mut_ptr(), value.len(), buf.as_ptr(), buf.len());
|
||||
sha3::sha3_256(value.as_mut_ptr(), value.len(), buf.as_ptr(), buf.len());
|
||||
ProofOfWork {
|
||||
mix_hash: mix_hash,
|
||||
value: value,
|
||||
@@ -321,17 +265,18 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
fn calculate_dag_item(node_index: u32, light: &Light) -> Node {
|
||||
unsafe {
|
||||
let num_parent_nodes = cache.len();
|
||||
let init = cache.get_unchecked(node_index as usize % num_parent_nodes);
|
||||
let num_parent_nodes = light.cache.len();
|
||||
let cache_nodes = &light.cache;
|
||||
let init = cache_nodes.get_unchecked(node_index as usize % num_parent_nodes);
|
||||
let mut ret = init.clone();
|
||||
*ret.as_words_mut().get_unchecked_mut(0) ^= node_index;
|
||||
sha3::sha3_512(ret.bytes.as_mut_ptr(), ret.bytes.len(), ret.bytes.as_ptr(), ret.bytes.len());
|
||||
|
||||
for i in 0..ETHASH_DATASET_PARENTS {
|
||||
let parent_index = fnv_hash(node_index ^ i, *ret.as_words().get_unchecked(i as usize % NODE_WORDS)) % num_parent_nodes as u32;
|
||||
let parent = cache.get_unchecked(parent_index as usize);
|
||||
let parent = cache_nodes.get_unchecked(parent_index as usize);
|
||||
for w in 0..NODE_WORDS {
|
||||
*ret.as_words_mut().get_unchecked_mut(w) = fnv_hash(*ret.as_words().get_unchecked(w), *parent.as_words().get_unchecked(w));
|
||||
}
|
||||
@@ -342,9 +287,7 @@ fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
}
|
||||
|
||||
fn light_new(block_number: u64) -> Light {
|
||||
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let seedhash = seed_compute.get_seedhash(block_number);
|
||||
let seedhash = get_seedhash(block_number);
|
||||
let cache_size = get_cache_size(block_number);
|
||||
|
||||
if cache_size % NODE_BYTES != 0 {
|
||||
@@ -365,7 +308,7 @@ fn light_new(block_number: u64) -> Light {
|
||||
let idx = *nodes.get_unchecked_mut(i).as_words().get_unchecked(0) as usize % num_nodes;
|
||||
let mut data = nodes.get_unchecked((num_nodes - 1 + i) % num_nodes).clone();
|
||||
for w in 0..NODE_WORDS {
|
||||
*data.as_words_mut().get_unchecked_mut(w) ^= *nodes.get_unchecked(idx).as_words().get_unchecked(w);
|
||||
*data.as_words_mut().get_unchecked_mut(w) ^= *nodes.get_unchecked(idx).as_words().get_unchecked(w) ;
|
||||
}
|
||||
sha3_512(&data.bytes, &mut nodes.get_unchecked_mut(i).bytes);
|
||||
}
|
||||
@@ -375,11 +318,10 @@ fn light_new(block_number: u64) -> Light {
|
||||
Light {
|
||||
cache: nodes,
|
||||
block_number: block_number,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
}
|
||||
}
|
||||
|
||||
static CHARS: &'static [u8] = b"0123456789abcdef";
|
||||
static CHARS: &'static[u8] = b"0123456789abcdef";
|
||||
fn to_hex(bytes: &[u8]) -> String {
|
||||
let mut v = Vec::with_capacity(bytes.len() * 2);
|
||||
for &byte in bytes.iter() {
|
||||
@@ -387,7 +329,9 @@ fn to_hex(bytes: &[u8]) -> String {
|
||||
v.push(CHARS[(byte & 0xf) as usize]);
|
||||
}
|
||||
|
||||
unsafe { String::from_utf8_unchecked(v) }
|
||||
unsafe {
|
||||
String::from_utf8_unchecked(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -417,8 +361,8 @@ fn test_get_data_size() {
|
||||
|
||||
#[test]
|
||||
fn test_difficulty_test() {
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d];
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d ];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
let boundary_good = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
assert_eq!(quick_get_difficulty(&hash, nonce, &mix_hash)[..], boundary_good[..]);
|
||||
@@ -428,8 +372,8 @@ fn test_difficulty_test() {
|
||||
|
||||
#[test]
|
||||
fn test_light_compute() {
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d];
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d ];
|
||||
let boundary = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
@@ -438,49 +382,3 @@ fn test_light_compute() {
|
||||
assert_eq!(result.mix_hash[..], mix_hash[..]);
|
||||
assert_eq!(result.value[..], boundary[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_once() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_zero() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
assert_eq!(seed_compute.get_seedhash(0), [0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_older() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an older value first shouldn't affect the result
|
||||
let _ = seed_compute.get_seedhash(50000);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_newer() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an newer value first shouldn't affect the result
|
||||
let _ = seed_compute.get_seedhash(972764);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let first = Light::new(0).to_file().unwrap();
|
||||
|
||||
let second = Light::new(ETHASH_EPOCH_LENGTH).to_file().unwrap();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = Light::new(ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = Light::new(ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
|
||||
@@ -18,18 +18,15 @@
|
||||
//! See https://github.com/ethereum/wiki/wiki/Ethash
|
||||
extern crate primal;
|
||||
extern crate sha3;
|
||||
extern crate parking_lot;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
mod compute;
|
||||
|
||||
use std::mem;
|
||||
use compute::Light;
|
||||
pub use compute::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty, slow_get_seedhash};
|
||||
pub use compute::{get_seedhash, quick_get_difficulty, H256, ProofOfWork, ETHASH_EPOCH_LENGTH};
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct LightCache {
|
||||
recent_epoch: Option<u64>,
|
||||
@@ -64,27 +61,22 @@ impl EthashManager {
|
||||
pub fn compute_light(&self, block_number: u64, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
let epoch = block_number / ETHASH_EPOCH_LENGTH;
|
||||
let light = {
|
||||
let mut lights = self.cache.lock();
|
||||
let mut lights = self.cache.lock().unwrap();
|
||||
let light = match lights.recent_epoch.clone() {
|
||||
Some(ref e) if *e == epoch => lights.recent.clone(),
|
||||
_ => match lights.prev_epoch.clone() {
|
||||
Some(e) if e == epoch => {
|
||||
// don't swap if recent is newer.
|
||||
if lights.recent_epoch > lights.prev_epoch {
|
||||
None
|
||||
} else {
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
};
|
||||
match light {
|
||||
None => {
|
||||
@@ -103,7 +95,7 @@ impl EthashManager {
|
||||
lights.prev = mem::replace(&mut lights.recent, Some(light.clone()));
|
||||
light
|
||||
}
|
||||
Some(light) => light,
|
||||
Some(light) => light
|
||||
}
|
||||
};
|
||||
light.compute(header_hash, nonce)
|
||||
@@ -116,12 +108,12 @@ fn test_lru() {
|
||||
let hash = [0u8; 32];
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
ethash.compute_light(50000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 1);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().unwrap().recent_epoch.unwrap(), 1);
|
||||
assert_eq!(ethash.cache.lock().unwrap().prev_epoch.unwrap(), 0);
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 1);
|
||||
assert_eq!(ethash.cache.lock().unwrap().recent_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().unwrap().prev_epoch.unwrap(), 1);
|
||||
ethash.compute_light(70000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 2);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().unwrap().recent_epoch.unwrap(), 2);
|
||||
assert_eq!(ethash.cache.lock().unwrap().prev_epoch.unwrap(), 0);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ description = "Ethcore library"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore"
|
||||
version = "1.4.0"
|
||||
version = "1.0.2"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
"ethcore-ipc-codegen" = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
@@ -16,46 +12,21 @@ env_logger = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
heapsize = "0.3"
|
||||
rust-crypto = "0.2.34"
|
||||
num_cpus = "0.2"
|
||||
crossbeam = "0.2.9"
|
||||
lazy_static = "0.2"
|
||||
bloomchain = "0.1"
|
||||
rayon = "0.4.2"
|
||||
semver = "0.2"
|
||||
bit-set = "0.4"
|
||||
time = "0.1"
|
||||
rand = "0.3"
|
||||
byteorder = "0.5"
|
||||
transient-hashmap = "0.1"
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
ethash = { path = "../ethash" }
|
||||
ethcore-util = { path = "../util" }
|
||||
ethcore-io = { path = "../util/io" }
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
ethash = { path = "../ethash" }
|
||||
num_cpus = "0.2"
|
||||
clippy = { version = "0.0.54", optional = true }
|
||||
crossbeam = "0.1.5"
|
||||
lazy_static = "0.1"
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethjson = { path = "../json" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
ethstore = { path = "../ethstore" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
lru-cache = "0.1.0"
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
|
||||
[dependencies.hyper]
|
||||
git = "https://github.com/ethcore/hyper"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
jit = ["evmjit"]
|
||||
evm-debug = ["slow-blocks"]
|
||||
evm-debug-tests = ["evm-debug"]
|
||||
slow-blocks = [] # Use SLOW_TX_DURATION="50" (compile time!) to track transactions over 50ms
|
||||
evm-debug = []
|
||||
json-tests = []
|
||||
test-heavy = []
|
||||
dev = ["clippy"]
|
||||
default = []
|
||||
benches = []
|
||||
ipc = []
|
||||
ethkey-cli = ["ethkey/cli"]
|
||||
ethstore-cli = ["ethstore/cli"]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethcore_ipc_codegen;
|
||||
|
||||
fn main() {
|
||||
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/client/traits.rs", cfg!(feature="ipc")).unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/snapshot/snapshot_service_trait.rs", cfg!(feature="ipc")).unwrap();
|
||||
ethcore_ipc_codegen::derive_ipc_cond("src/client/chain_notify.rs", cfg!(feature="ipc")).unwrap();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "Homestead (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x0",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x400000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
|
||||
"gasLimit": "0x1388"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "Homestead (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x0",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x0",
|
||||
"eip161abcTransition": "0x0",
|
||||
"eip161dTransition": "0x0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x400000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
|
||||
"gasLimit": "0x1388"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"name": "Expanse",
|
||||
"forkName": "expanse",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"difficultyIncrementDivisor": "60",
|
||||
"durationLimit": "0x3C",
|
||||
"blockReward": "0x6f05b59d3b200000",
|
||||
"registrar" : "0x6c221ca53705f3497ec90ca7b84c59ae7382fc21",
|
||||
"homesteadTransition": "0x30d40",
|
||||
"difficultyHardforkTransition": "0x59d9",
|
||||
"difficultyHardforkBoundDivisor": "0x0200",
|
||||
"bombDefuseTransition": "0x30d40",
|
||||
"eip150Transition": "0x7fffffffffffffff",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID": "0x1",
|
||||
"subprotocolName": "exp"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x214652414e4b4f21",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x40000000",
|
||||
"author": "0x93decab0cd745598860f782ac1e8f046cb99e898",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x4672616e6b6f497346726565646f6d",
|
||||
"gasLimit": "0x1388"
|
||||
},
|
||||
"nodes": [
|
||||
"enode://7f335a047654f3e70d6f91312a7cf89c39704011f1a584e2698250db3d63817e74b88e26b7854111e16b2c9d0c7173c05419aeee2d0321850227b126d8b1be3f@46.101.156.249:42786",
|
||||
"enode://df872f81e25f72356152b44cab662caf1f2e57c3a156ecd20e9ac9246272af68a2031b4239a0bc831f2c6ab34733a041464d46b3ea36dce88d6c11714446e06b@178.62.208.109:42786",
|
||||
"enode://96d3919b903e7f5ad59ac2f73c43be9172d9d27e2771355db03fd194732b795829a31fe2ea6de109d0804786c39a807e155f065b4b94c6fce167becd0ac02383@45.55.22.34:42786",
|
||||
"enode://5f6c625bf287e3c08aad568de42d868781e961cbda805c8397cfb7be97e229419bef9a5a25a75f97632787106bba8a7caf9060fab3887ad2cfbeb182ab0f433f@46.101.182.53:42786",
|
||||
"enode://d33a8d4c2c38a08971ed975b750f21d54c927c0bf7415931e214465a8d01651ecffe4401e1db913f398383381413c78105656d665d83f385244ab302d6138414@128.199.183.48:42786",
|
||||
"enode://df872f81e25f72356152b44cab662caf1f2e57c3a156ecd20e9ac9246272af68a2031b4239a0bc831f2c6ab34733a041464d46b3ea36dce88d6c11714446e06b@178.62.208.109:42786",
|
||||
"enode://f6f0d6b9b7d02ec9e8e4a16e38675f3621ea5e69860c739a65c1597ca28aefb3cec7a6d84e471ac927d42a1b64c1cbdefad75e7ce8872d57548ddcece20afdd1@159.203.64.95:42786"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||
"bb94f0ceb32257275b2a7a9c094c13e469b4563e": {
|
||||
"balance": "10000000000000000000000000"
|
||||
},
|
||||
"15656715068ab0dbdf0ab00748a8a19e40f28192": {
|
||||
"balance": "1000000000000000000000000"
|
||||
},
|
||||
"c075fa11f85bda3aaba67106226aaf086ac16f4e": {
|
||||
"balance": "100000000000000000000000"
|
||||
},
|
||||
"93decab0cd745598860f782ac1e8f046cb99e898": {
|
||||
"balance": "10000000000000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +1,24 @@
|
||||
{
|
||||
"name": "Frontier/Homestead",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
"0xd4fe7bc31cedb7bfb8a345f31e668033056b2728",
|
||||
"0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425",
|
||||
"0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f",
|
||||
"0xecd135fa4f61a655311e86238c92adcd779555d2",
|
||||
"0x1975bd06d486162d5dc297798dfc41edd5d160a7",
|
||||
"0xa3acf3a1e16b1d7c315e23510fdd7847b48234f6",
|
||||
"0x319f70bab6845585f412ec7724b744fec6095c85",
|
||||
"0x06706dd3f2c9abf0a21ddcc6941d9b86f0596936",
|
||||
"0x5c8536898fbb74fc7445814902fd08422eac56d0",
|
||||
"0x6966ab0d485353095148a2155858910e0965b6f9",
|
||||
"0x779543a0491a837ca36ce8c635d6154e3c4911a6",
|
||||
"0x2a5ed960395e2a49b1c758cef4aa15213cfd874c",
|
||||
"0x5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5",
|
||||
"0x9c50426be05db97f5d64fc54bf89eff947f0a321",
|
||||
"0x200450f06520bdd6c527622a273333384d870efb",
|
||||
"0xbe8539bfe837b67d1282b2b1d61c3f723966f049",
|
||||
"0x6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb",
|
||||
"0xf1385fb24aad0cd7432824085e42aff90886fef5",
|
||||
"0xd1ac8b1ef1b69ff51d1d401a476e7e612414f091",
|
||||
"0x8163e7fb499e90f8544ea62bbf80d21cd26d9efd",
|
||||
"0x51e0ddd9998364a2eb38588679f0d2c42653e4a6",
|
||||
"0x627a0a960c079c21c34f7612d5d230e01b4ad4c7",
|
||||
"0xf0b1aa0eb660754448a7937c022e30aa692fe0c5",
|
||||
"0x24c4d950dfd4dd1902bbed3508144a54542bba94",
|
||||
"0x9f27daea7aca0aa0446220b98d028715e3bc803d",
|
||||
"0xa5dc5acd6a7968a4554d89d65e59b7fd3bff0f90",
|
||||
"0xd9aef3a1e38a39c16b31d1ace71bca8ef58d315b",
|
||||
"0x63ed5a272de2f6d968408b4acb9024f4cc208ebf",
|
||||
"0x6f6704e5a10332af6672e50b3d9754dc460dfa4d",
|
||||
"0x77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6",
|
||||
"0x492ea3bb0f3315521c31f273e565b868fc090f17",
|
||||
"0x0ff30d6de14a8224aa97b78aea5388d1c51c1f00",
|
||||
"0x9ea779f907f0b315b364b0cfc39a0fde5b02a416",
|
||||
"0xceaeb481747ca6c540a000c1f3641f8cef161fa7",
|
||||
"0xcc34673c6c40e791051898567a1222daf90be287",
|
||||
"0x579a80d909f346fbfb1189493f521d7f48d52238",
|
||||
"0xe308bd1ac5fda103967359b2712dd89deffb7973",
|
||||
"0x4cb31628079fb14e4bc3cd5e30c2f7489b00960c",
|
||||
"0xac1ecab32727358dba8962a0f3b261731aad9723",
|
||||
"0x4fd6ace747f06ece9c49699c7cabc62d02211f75",
|
||||
"0x440c59b325d2997a134c2c7c60a8c61611212bad",
|
||||
"0x4486a3d68fac6967006d7a517b889fd3f98c102b",
|
||||
"0x9c15b54878ba618f494b38f0ae7443db6af648ba",
|
||||
"0x27b137a85656544b1ccb5a0f2e561a5703c6a68f",
|
||||
"0x21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241",
|
||||
"0x23b75c2f6791eef49c69684db4c6c1f93bf49a50",
|
||||
"0x1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b",
|
||||
"0xb9637156d330c0d605a791f1c31ba5890582fe1c",
|
||||
"0x6131c42fa982e56929107413a9d526fd99405560",
|
||||
"0x1591fc0f688c81fbeb17f5426a162a7024d430c2",
|
||||
"0x542a9515200d14b68e934e9830d91645a980dd7a",
|
||||
"0xc4bbd073882dd2add2424cf47d35213405b01324",
|
||||
"0x782495b7b3355efb2833d56ecb34dc22ad7dfcc4",
|
||||
"0x58b95c9a9d5d26825e70a82b6adb139d3fd829eb",
|
||||
"0x3ba4d81db016dc2890c81f3acec2454bff5aada5",
|
||||
"0xb52042c8ca3f8aa246fa79c3feaa3d959347c0ab",
|
||||
"0xe4ae1efdfc53b73893af49113d8694a057b9c0d1",
|
||||
"0x3c02a7bc0391e86d91b7d144e61c2c01a25a79c5",
|
||||
"0x0737a6b837f97f46ebade41b9bc3e1c509c85c53",
|
||||
"0x97f43a37f595ab5dd318fb46e7a155eae057317a",
|
||||
"0x52c5317c848ba20c7504cb2c8052abd1fde29d03",
|
||||
"0x4863226780fe7c0356454236d3b1c8792785748d",
|
||||
"0x5d2b2e6fcbe3b11d26b525e085ff818dae332479",
|
||||
"0x5f9f3392e9f62f63b8eac0beb55541fc8627f42c",
|
||||
"0x057b56736d32b86616a10f619859c6cd6f59092a",
|
||||
"0x9aa008f65de0b923a2a4f02012ad034a5e2e2192",
|
||||
"0x304a554a310c7e546dfe434669c62820b7d83490",
|
||||
"0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79",
|
||||
"0x4deb0033bb26bc534b197e61d19e0733e5679784",
|
||||
"0x07f5c1e1bc2c93e0402f23341973a0e043f7bf8a",
|
||||
"0x35a051a0010aba705c9008d7a7eff6fb88f6ea7b",
|
||||
"0x4fa802324e929786dbda3b8820dc7834e9134a2a",
|
||||
"0x9da397b9e80755301a3b32173283a91c0ef6c87e",
|
||||
"0x8d9edb3054ce5c5774a420ac37ebae0ac02343c6",
|
||||
"0x0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9",
|
||||
"0x5dc28b15dffed94048d73806ce4b7a4612a1d48f",
|
||||
"0xbcf899e6c7d9d5a215ab1e3444c86806fa854c76",
|
||||
"0x12e626b0eebfe86a56d633b9864e389b45dcb260",
|
||||
"0xa2f1ccba9395d7fcb155bba8bc92db9bafaeade7",
|
||||
"0xec8e57756626fdc07c63ad2eafbd28d08e7b0ca5",
|
||||
"0xd164b088bd9108b60d0ca3751da4bceb207b0782",
|
||||
"0x6231b6d0d5e77fe001c2a460bd9584fee60d409b",
|
||||
"0x1cba23d343a983e9b5cfd19496b9a9701ada385f",
|
||||
"0xa82f360a8d3455c5c41366975bde739c37bfeb8a",
|
||||
"0x9fcd2deaff372a39cc679d5c5e4de7bafb0b1339",
|
||||
"0x005f5cee7a43331d5a3d3eec71305925a62f34b6",
|
||||
"0x0e0da70933f4c7849fc0d203f5d1d43b9ae4532d",
|
||||
"0xd131637d5275fd1a68a3200f4ad25c71a2a9522e",
|
||||
"0xbc07118b9ac290e4622f5e77a0853539789effbe",
|
||||
"0x47e7aa56d6bdf3f36be34619660de61275420af8",
|
||||
"0xacd87e28b0c9d1254e868b81cba4cc20d9a32225",
|
||||
"0xadf80daec7ba8dcf15392f1ac611fff65d94f880",
|
||||
"0x5524c55fb03cf21f549444ccbecb664d0acad706",
|
||||
"0x40b803a9abce16f50f36a77ba41180eb90023925",
|
||||
"0xfe24cdd8648121a43a7c86d289be4dd2951ed49f",
|
||||
"0x17802f43a0137c506ba92291391a8a8f207f487d",
|
||||
"0x253488078a4edf4d6f42f113d1e62836a942cf1a",
|
||||
"0x86af3e9626fce1957c82e88cbf04ddf3a2ed7915",
|
||||
"0xb136707642a4ea12fb4bae820f03d2562ebff487",
|
||||
"0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940",
|
||||
"0xf14c14075d6c4ed84b86798af0956deef67365b5",
|
||||
"0xca544e5c4687d109611d0f8f928b53a25af72448",
|
||||
"0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c",
|
||||
"0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7",
|
||||
"0x6d87578288b6cb5549d5076a207456a1f6a63dc0",
|
||||
"0xb2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e",
|
||||
"0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6",
|
||||
"0x2b3455ec7fedf16e646268bf88846bd7a2319bb2",
|
||||
"0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a",
|
||||
"0xd343b217de44030afaa275f54d31a9317c7f441e",
|
||||
"0x84ef4b2357079cd7a7c69fd7a37cd0609a679106",
|
||||
"0xda2fef9e4a3230988ff17df2165440f37e8b1708",
|
||||
"0xf4c64518ea10f995918a454158c6b61407ea345c",
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
],
|
||||
"eip150Transition": "0x259518",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engineName": "Ethash",
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"tieBreakingGas": false,
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x1",
|
||||
"forkBlock": "0x1d4c00",
|
||||
"forkCanonHash": "0x4985f5ca3d2afbec36529aa96f74de3cc10a2a4a6c44f2157a57d2c6059a11bb"
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0000000000000042",
|
||||
"difficulty": "0x400000000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
@@ -162,27 +27,10 @@
|
||||
"stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"
|
||||
},
|
||||
"nodes": [
|
||||
"enode://efe4f2493f4aff2d641b1db8366b96ddacfe13e7a6e9c8f8f8cf49f9cdba0fdf3258d8c8f8d0c5db529f8123c8f1d95f36d54d590ca1bb366a5818b9a4ba521c@163.172.187.252:30303",
|
||||
"enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@163.172.157.114:30303",
|
||||
"enode://bcc7240543fe2cf86f5e9093d05753dd83343f8fda7bf0e833f65985c73afccf8f981301e13ef49c4804491eab043647374df1c4adf85766af88a624ecc3330e@136.243.154.244:30303",
|
||||
"enode://ed4227681ca8c70beb2277b9e870353a9693f12e7c548c35df6bca6a956934d6f659999c2decb31f75ce217822eefca149ace914f1cbe461ed5a2ebaf9501455@88.212.206.70:30303",
|
||||
"enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303",
|
||||
"enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303",
|
||||
"enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303",
|
||||
"enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303",
|
||||
"enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303",
|
||||
"enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303",
|
||||
"enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303",
|
||||
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
||||
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
|
||||
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",
|
||||
"enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303",
|
||||
|
||||
"enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303",
|
||||
"enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303",
|
||||
"enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303",
|
||||
"enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303",
|
||||
"enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303"
|
||||
"enode://248f12bc8b18d5289358085520ac78cd8076485211e6d96ab0bc93d6cd25442db0ce3a937dc404f64f207b0b9aed50e25e98ce32af5ac7cb321ff285b97de485@zero.parity.io:30303"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
@@ -1,157 +1,24 @@
|
||||
{
|
||||
"name": "Frontier (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
"0xd4fe7bc31cedb7bfb8a345f31e668033056b2728",
|
||||
"0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425",
|
||||
"0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f",
|
||||
"0xecd135fa4f61a655311e86238c92adcd779555d2",
|
||||
"0x1975bd06d486162d5dc297798dfc41edd5d160a7",
|
||||
"0xa3acf3a1e16b1d7c315e23510fdd7847b48234f6",
|
||||
"0x319f70bab6845585f412ec7724b744fec6095c85",
|
||||
"0x06706dd3f2c9abf0a21ddcc6941d9b86f0596936",
|
||||
"0x5c8536898fbb74fc7445814902fd08422eac56d0",
|
||||
"0x6966ab0d485353095148a2155858910e0965b6f9",
|
||||
"0x779543a0491a837ca36ce8c635d6154e3c4911a6",
|
||||
"0x2a5ed960395e2a49b1c758cef4aa15213cfd874c",
|
||||
"0x5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5",
|
||||
"0x9c50426be05db97f5d64fc54bf89eff947f0a321",
|
||||
"0x200450f06520bdd6c527622a273333384d870efb",
|
||||
"0xbe8539bfe837b67d1282b2b1d61c3f723966f049",
|
||||
"0x6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb",
|
||||
"0xf1385fb24aad0cd7432824085e42aff90886fef5",
|
||||
"0xd1ac8b1ef1b69ff51d1d401a476e7e612414f091",
|
||||
"0x8163e7fb499e90f8544ea62bbf80d21cd26d9efd",
|
||||
"0x51e0ddd9998364a2eb38588679f0d2c42653e4a6",
|
||||
"0x627a0a960c079c21c34f7612d5d230e01b4ad4c7",
|
||||
"0xf0b1aa0eb660754448a7937c022e30aa692fe0c5",
|
||||
"0x24c4d950dfd4dd1902bbed3508144a54542bba94",
|
||||
"0x9f27daea7aca0aa0446220b98d028715e3bc803d",
|
||||
"0xa5dc5acd6a7968a4554d89d65e59b7fd3bff0f90",
|
||||
"0xd9aef3a1e38a39c16b31d1ace71bca8ef58d315b",
|
||||
"0x63ed5a272de2f6d968408b4acb9024f4cc208ebf",
|
||||
"0x6f6704e5a10332af6672e50b3d9754dc460dfa4d",
|
||||
"0x77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6",
|
||||
"0x492ea3bb0f3315521c31f273e565b868fc090f17",
|
||||
"0x0ff30d6de14a8224aa97b78aea5388d1c51c1f00",
|
||||
"0x9ea779f907f0b315b364b0cfc39a0fde5b02a416",
|
||||
"0xceaeb481747ca6c540a000c1f3641f8cef161fa7",
|
||||
"0xcc34673c6c40e791051898567a1222daf90be287",
|
||||
"0x579a80d909f346fbfb1189493f521d7f48d52238",
|
||||
"0xe308bd1ac5fda103967359b2712dd89deffb7973",
|
||||
"0x4cb31628079fb14e4bc3cd5e30c2f7489b00960c",
|
||||
"0xac1ecab32727358dba8962a0f3b261731aad9723",
|
||||
"0x4fd6ace747f06ece9c49699c7cabc62d02211f75",
|
||||
"0x440c59b325d2997a134c2c7c60a8c61611212bad",
|
||||
"0x4486a3d68fac6967006d7a517b889fd3f98c102b",
|
||||
"0x9c15b54878ba618f494b38f0ae7443db6af648ba",
|
||||
"0x27b137a85656544b1ccb5a0f2e561a5703c6a68f",
|
||||
"0x21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241",
|
||||
"0x23b75c2f6791eef49c69684db4c6c1f93bf49a50",
|
||||
"0x1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b",
|
||||
"0xb9637156d330c0d605a791f1c31ba5890582fe1c",
|
||||
"0x6131c42fa982e56929107413a9d526fd99405560",
|
||||
"0x1591fc0f688c81fbeb17f5426a162a7024d430c2",
|
||||
"0x542a9515200d14b68e934e9830d91645a980dd7a",
|
||||
"0xc4bbd073882dd2add2424cf47d35213405b01324",
|
||||
"0x782495b7b3355efb2833d56ecb34dc22ad7dfcc4",
|
||||
"0x58b95c9a9d5d26825e70a82b6adb139d3fd829eb",
|
||||
"0x3ba4d81db016dc2890c81f3acec2454bff5aada5",
|
||||
"0xb52042c8ca3f8aa246fa79c3feaa3d959347c0ab",
|
||||
"0xe4ae1efdfc53b73893af49113d8694a057b9c0d1",
|
||||
"0x3c02a7bc0391e86d91b7d144e61c2c01a25a79c5",
|
||||
"0x0737a6b837f97f46ebade41b9bc3e1c509c85c53",
|
||||
"0x97f43a37f595ab5dd318fb46e7a155eae057317a",
|
||||
"0x52c5317c848ba20c7504cb2c8052abd1fde29d03",
|
||||
"0x4863226780fe7c0356454236d3b1c8792785748d",
|
||||
"0x5d2b2e6fcbe3b11d26b525e085ff818dae332479",
|
||||
"0x5f9f3392e9f62f63b8eac0beb55541fc8627f42c",
|
||||
"0x057b56736d32b86616a10f619859c6cd6f59092a",
|
||||
"0x9aa008f65de0b923a2a4f02012ad034a5e2e2192",
|
||||
"0x304a554a310c7e546dfe434669c62820b7d83490",
|
||||
"0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79",
|
||||
"0x4deb0033bb26bc534b197e61d19e0733e5679784",
|
||||
"0x07f5c1e1bc2c93e0402f23341973a0e043f7bf8a",
|
||||
"0x35a051a0010aba705c9008d7a7eff6fb88f6ea7b",
|
||||
"0x4fa802324e929786dbda3b8820dc7834e9134a2a",
|
||||
"0x9da397b9e80755301a3b32173283a91c0ef6c87e",
|
||||
"0x8d9edb3054ce5c5774a420ac37ebae0ac02343c6",
|
||||
"0x0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9",
|
||||
"0x5dc28b15dffed94048d73806ce4b7a4612a1d48f",
|
||||
"0xbcf899e6c7d9d5a215ab1e3444c86806fa854c76",
|
||||
"0x12e626b0eebfe86a56d633b9864e389b45dcb260",
|
||||
"0xa2f1ccba9395d7fcb155bba8bc92db9bafaeade7",
|
||||
"0xec8e57756626fdc07c63ad2eafbd28d08e7b0ca5",
|
||||
"0xd164b088bd9108b60d0ca3751da4bceb207b0782",
|
||||
"0x6231b6d0d5e77fe001c2a460bd9584fee60d409b",
|
||||
"0x1cba23d343a983e9b5cfd19496b9a9701ada385f",
|
||||
"0xa82f360a8d3455c5c41366975bde739c37bfeb8a",
|
||||
"0x9fcd2deaff372a39cc679d5c5e4de7bafb0b1339",
|
||||
"0x005f5cee7a43331d5a3d3eec71305925a62f34b6",
|
||||
"0x0e0da70933f4c7849fc0d203f5d1d43b9ae4532d",
|
||||
"0xd131637d5275fd1a68a3200f4ad25c71a2a9522e",
|
||||
"0xbc07118b9ac290e4622f5e77a0853539789effbe",
|
||||
"0x47e7aa56d6bdf3f36be34619660de61275420af8",
|
||||
"0xacd87e28b0c9d1254e868b81cba4cc20d9a32225",
|
||||
"0xadf80daec7ba8dcf15392f1ac611fff65d94f880",
|
||||
"0x5524c55fb03cf21f549444ccbecb664d0acad706",
|
||||
"0x40b803a9abce16f50f36a77ba41180eb90023925",
|
||||
"0xfe24cdd8648121a43a7c86d289be4dd2951ed49f",
|
||||
"0x17802f43a0137c506ba92291391a8a8f207f487d",
|
||||
"0x253488078a4edf4d6f42f113d1e62836a942cf1a",
|
||||
"0x86af3e9626fce1957c82e88cbf04ddf3a2ed7915",
|
||||
"0xb136707642a4ea12fb4bae820f03d2562ebff487",
|
||||
"0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940",
|
||||
"0xf14c14075d6c4ed84b86798af0956deef67365b5",
|
||||
"0xca544e5c4687d109611d0f8f928b53a25af72448",
|
||||
"0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c",
|
||||
"0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7",
|
||||
"0x6d87578288b6cb5549d5076a207456a1f6a63dc0",
|
||||
"0xb2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e",
|
||||
"0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6",
|
||||
"0x2b3455ec7fedf16e646268bf88846bd7a2319bb2",
|
||||
"0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a",
|
||||
"0xd343b217de44030afaa275f54d31a9317c7f441e",
|
||||
"0x84ef4b2357079cd7a7c69fd7a37cd0609a679106",
|
||||
"0xda2fef9e4a3230988ff17df2165440f37e8b1708",
|
||||
"0xf4c64518ea10f995918a454158c6b61407ea345c",
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
],
|
||||
"eip150Transition": "0x7fffffffffffffff",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engineName": "Ethash",
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"tieBreakingGas": false,
|
||||
"minGasLimit": "0x1388",
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0000000000000042",
|
||||
"difficulty": "0x400000000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
{
|
||||
"name": "Frontier (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x7fffffffffffffff",
|
||||
"eip150Transition": "0x7fffffffffffffff",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engineName": "Ethash",
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"tieBreakingGas": false,
|
||||
"minGasLimit": "0x1388",
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0000000000000042",
|
||||
"difficulty": "0x400000000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
{
|
||||
"name": "Homestead (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x7fffffffffffffff",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engineName": "Ethash",
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"frontierCompatibilityModeLimit": 0,
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"tieBreakingGas": false,
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0000000000000042",
|
||||
"difficulty": "0x400000000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -1,39 +1,24 @@
|
||||
{
|
||||
"name": "Morden",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
|
||||
"homesteadTransition": "0x789b0",
|
||||
"eip150Transition": "0x1b34d8",
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engineName": "Ethash",
|
||||
"params": {
|
||||
"accountStartNonce": "0x0100000",
|
||||
"frontierCompatibilityModeLimit": "0x789b0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"tieBreakingGas": false,
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x2",
|
||||
"forkBlock": "0x1b34d8",
|
||||
"forkCanonHash": "0xf376243aeff1f256d970714c3de9fd78fa4e63cf63e32a51fe1169e375d98145"
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar": "",
|
||||
"networkID" : "0x2"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x00006d6f7264656e",
|
||||
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578"
|
||||
}
|
||||
},
|
||||
"nonce": "0x00006d6f7264656e",
|
||||
"difficulty": "0x20000",
|
||||
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
@@ -41,8 +26,7 @@
|
||||
"gasLimit": "0x2fefd8"
|
||||
},
|
||||
"nodes": [
|
||||
"enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303",
|
||||
"enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303"
|
||||
"enode://b1217cbaa440e35ed471157123fe468e19e8b5ad5bedb4b1fdbcbdab6fb2f5ed3e95dd9c24a22a79fdb2352204cea207df27d92bfd21bfd41545e8b16f637499@104.44.138.37:30303"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user