Compare commits

..

72 Commits

Author SHA1 Message Date
Arkadiy Paronyan
6d14c5458b Use global state cache when mining (#2529) 2016-10-08 02:00:06 +02:00
arkpar
9239a7cd9e v1.3.5 2016-10-08 01:02:41 +02:00
Arkadiy Paronyan
d27ecb6527 Transaction queue limited by gas (#2528) 2016-10-08 00:59:04 +02:00
Robert Habermeier
5502340dea Merge pull request #2521 from ethcore/update-appveyor-rustc-beta
Update appveyor rustc [beta]
2016-10-07 17:39:41 +02:00
Denis S. Soldatov aka General-Beck
2313f9b4f1 Update gitlab-ci
add javascript test
2016-10-07 22:31:09 +07:00
Robert Habermeier
324a5873d7 turn off MIR for windows builds 2016-10-07 16:07:01 +02:00
Robert Habermeier
b45e7f1c41 update rustc for appveyor to 1.12.0 2016-10-07 16:06:39 +02:00
Tomasz Drwięga
64af073bd4 Increase size of transaction queue by default (#2519)
Conflicts:
	ethcore/src/miner/miner.rs
	parity/cli/config.full.toml
	parity/cli/mod.rs
	parity/configuration.rs
2016-10-07 14:11:33 +01:00
Arkadiy Paronyan
fe83046198 Backport to beta (#2518)
* Handle reorganizations in the state cache

* Renamed and documented a few methods

* Basic test

* Renamed pending to buffered

* Caching optimizations

* Fixed a test

* Fixed a test
2016-10-07 14:09:52 +01:00
Tomasz Drwięga
8031910892 [beta] Fixing RPC Filter conversion to EthFilter (#2501)
* Fixing RPC Filter conversion to EthFilter

Conflicts:
	rpc/src/v1/impls/eth.rs
	rpc/src/v1/types/filter.rs

* Removing limit

* Fixing rpc tests
2016-10-07 12:16:09 +01:00
Tomasz Drwięga
73a3dac38c [beta] Using pending block only if is not old (#2515)
* Using pending block only if not old

Conflicts:
	ethcore/src/miner/miner.rs
	rpc/src/v1/impls/eth.rs
	rpc/src/v1/impls/eth_filter.rs

* Fixing compilation issues
2016-10-07 11:46:17 +01:00
Arkadiy Paronyan
bbaf5ed4f5 Backports into beta (#2512)
* RocksDB version bump

* Preserve cache on reverting the snapshot (#2488)

* Preserve cache on reverting the snapshot

* Renamed merge_with into replace_with

* Renamed and documented snapshotting methods

* Track dirty accounts in the state (#2461)

* State to track dirty accounts

* Removed clone_for_snapshot

* Renaming stuff

* Documentation and other minor fixes

* Replaced MaybeAccount with Option

* Adjustable stack size for EVM (#2483)

* stack size for io workers & evm threshold

* rust way to remember stack size

* right value

* 24kb size

* some stack reduction

* Fixed overflow panic in handshake_panic (#2495)
2016-10-07 10:49:48 +01:00
Tomasz Drwięga
9cf777510f CLI to specify queue ordering strategy (#2494)
* Alter gas priorities to include gas_price also

* CLI options and tests

* Adding ordering by gas

* whitespace

[ci:skip]
2016-10-07 09:04:13 +02:00
Nikolay Volf
9b398421ce Run inplace upgrades after version update (#2411)
* Update migration.rs

In highly unlikely scenario when the bloom update fails it will at least try again on the next run

* proper exchange
2016-10-07 09:03:49 +02:00
Tomasz Drwięga
a618dcaaf0 Fixing penalization in future (#2493) 2016-10-06 15:52:09 +01:00
Nikolay Volf
50021c7611 Fix ethstore opening all key files in the directory at once (BETA) (#2472)
* get rid of collects & zips

* not an option here!
2016-10-05 15:41:48 +02:00
Denis S. Soldatov aka General-Beck
f62d2e6ea8 Update gitlab-ci
fix windows
2016-10-05 10:54:01 +07:00
Denis S. Soldatov aka General-Beck
7da8e7926d Update gitlab-ci
set RUSTFLAGS=-Zorbit=off windows
2016-10-05 08:38:33 +07:00
Denis S. Soldatov aka General-Beck
5363e75f3a Update gitlab-ci
add RUSTFLAGS=-Zorbit=false in windows build
2016-10-05 08:04:56 +07:00
Arkadiy Paronyan
31d667a29a Merge pull request #2465 from ethcore/beta-staging
Beta backports
2016-10-05 00:24:13 +02:00
arkpar
ca2bc92dda Fixing tests 2016-10-04 23:24:16 +02:00
Marek Kotewicz
a0b71a9f52 fixed #2263, geth keys with ciphertext shorter than 32 bytes (#2318)
* fixed #2263, geth keys with ciphertext shorter than 32 bytes

* replace unwrap with more helpful expect

* tests for decrypting short secrets
2016-10-04 22:08:31 +02:00
arkpar
300c0c120b v1.3.4 2016-10-04 22:00:59 +02:00
Nikolay Volf
b228dc7b54 IPC-library dependency fork & bump for beta (#2455)
* bump json-ipc-server

* bump again
2016-10-04 18:18:07 +02:00
Denis S. Soldatov aka General-Beck
df4d98001a update gitlab-ci
set RUST 1.11 for windows
2016-10-04 14:43:49 +08:00
Robert Habermeier
2e4672ea13 Merge pull request #2414 from ethcore/compilation-beta
fix broken beta compilation (backport to beta)
2016-10-02 21:07:35 +02:00
Arkadiy Paronyan
7f7fe33bde Jumptable cache (#2435)
* v1.3.3

* Jumptable cache
2016-10-02 19:52:53 +02:00
Arkadiy Paronyan
3d313d592a Disabling debug symbols due to rustc 1.12 memory usage regression 2016-10-02 09:45:27 +02:00
NikVolf
abc6970d08 fix ethkey more 2016-10-01 15:29:11 +03:00
NikVolf
fa636d402d ethkey eq 2016-10-01 13:04:08 +03:00
NikVolf
73d6666294 some checksums 2016-10-01 00:28:31 +03:00
Denis S. Soldatov aka General-Beck
f16ccacd22 Update gitlab-ci
add checksum
2016-09-30 21:03:05 +07:00
Nikolay Volf
ba3de5d3b9 rayon bump 2016-09-30 02:25:35 +03:00
Robert Habermeier
f16de5501a fix broken beta compilation 2016-09-29 23:03:02 +03:00
Arkadiy Paronyan
e298ab30e4 Quick fix for missing tree route blocks (#2400) 2016-09-29 14:47:13 +02:00
Tomasz Drwięga
63d3a4dd59 Beta Backports (#2396)
* Removing extras data from retracted blocks. (#2375)

* Removing extras data from retracted blocks.

* Adding a test case

Conflicts:
	ethcore/src/blockchain/blockchain.rs

* Fixing transaction queue

Conflicts:
	ethcore/src/miner/transaction_queue.rs

* Prioritizing re-imported transactions (#2372)

* Prioritizing re-imported transactions

* Fixing compilation on beta

Conflicts:
	Cargo.lock
	ethcore/src/client/client.rs
	ethcore/src/miner/transaction_queue.rs

* Post-merge fixes
2016-09-29 13:19:53 +02:00
Arkadiy Paronyan
0e55b6c6c9 Cache the fork block header after snapshot restoration (#2391) 2016-09-29 12:34:40 +02:00
Nikolay Volf
4d115987cb Accounts bloom (#2357)
* proper bloom

* incremental bloom updates

* crate update

* return of the column

* fix n^2 byteorder write

* add notes to funs

* working bloom commits

* Optimizations

* bloom diag

* migration basic

* migration ongoing

* migration finalizing

* mingration api workarounds

* fix test_client setups

* snapshot bloom update

* review fixes

* just forward keys in the migration

* migration extra tracing

* fix migration path

* remove close pray

* review issues
2016-09-29 11:27:41 +02:00
Tomasz Drwięga
2d623f14db Disable colors for signer token in beta (#2379) 2016-09-28 23:33:50 +02:00
Robert Habermeier
1d1a3b9d02 correct sync memory usage calculation (#2386) 2016-09-28 23:00:27 +02:00
Arkadiy Paronyan
1d69b0e124 Fixing jit feature compilation (#2376)
* Fixing jit feature compilation

* Fixing jit feature compilation (#2310)

Conflicts:
	Cargo.lock

* Delegatecall support
2016-09-28 17:25:32 +02:00
Arkadiy Paronyan
3c59475be6 Clear state cache on sealed block import (#2377) 2016-09-28 17:24:47 +02:00
Denis S. Soldatov aka General-Beck
facab31551 Update gitlab-ci
add RUST_BACKTRACE=1 in test stage
2016-09-28 01:52:17 +07:00
Arkadiy Paronyan
c9cfcd2728 Canonical state cache (#2308)
* State cache

* Reduced copying data between caches

Whitespace and optional symbols

* Set a limit on storage cache

* Style and docs
2016-09-27 16:44:32 +02:00
Arkadiy Paronyan
557dbb73b8 DIV optimization (#2353) 2016-09-27 14:04:08 +02:00
Gav Wood
6f772a28e2 Reorder transaction_by_hash to favour canon search (#2331)
* Reorder transaction_by_hash to favour canon search

* Fix test.
2016-09-27 12:16:42 +02:00
Tomasz Drwięga
16f3119547 Lenient bytes deserialization (#2340)
* Lenient bytes deserialization

* Error when deserializing invalid hex

* Error when deserializing invalid hex

Conflicts:
	rpc/src/v1/types/bytes.rs
2016-09-27 11:24:27 +02:00
Tomasz Drwięga
c3741640f7 Penalize transactions with gas above gas limit (#2271)
* Handle RLP to string UTF-8 decoding errors (#2217)

* Penalize transactions with gas above gas limit

* Avoid penalizing legit transactions

* saturating not overflowing

* Remove crufty code

* Introduce gas price ordering.

* Gas before gas-price, as long as the minimum price is met.

* saturating add
2016-09-25 12:14:39 +02:00
Denis S. Soldatov aka General-Beck
decca7f698 Update gitlab-ci
deploy fix
2016-09-24 05:01:48 +07:00
Denis S. Soldatov aka General-Beck
fc53726c7f Update gitlab-ci
fix armv7 path
2016-09-24 03:00:22 +07:00
Denis S. Soldatov aka General-Beck
e2b81bc08d Update gitlab-ci
[ci skip]
rename x86_64-unknown-centos-gnu
2016-09-24 00:11:23 +07:00
Denis S. Soldatov aka General-Beck
4d2d1eac1f Update gitlab-ci
fix path in arm
2016-09-23 23:45:57 +07:00
Denis S. Soldatov aka General-Beck
5572e3dd1d Update gitlab-ci
fix  windows deploy to S3
add ubuntu 14.04 build
2016-09-23 23:40:25 +07:00
Tomasz Drwięga
0db1c0d336 Peek transaction queue via RPC (#2270)
* Handle RLP to string UTF-8 decoding errors (#2217)

* pending transactions
2016-09-23 18:13:03 +02:00
Denis S. Soldatov aka General-Beck
905f04dc9d Update Ubuntu arm Dockerfile
[ci skip] remove CC and CXX env
backport to beta https://github.com/ethcore/parity/pull/2064
2016-09-23 21:35:04 +07:00
Denis S. Soldatov aka General-Beck
f560cf360c Update Dockerfile ubuntu-aarch64
[ci skip] remove CC and CXX env
backport to beta https://github.com/ethcore/parity/pull/2064
2016-09-23 21:34:16 +07:00
Tomasz Drwięga
f5d48cbf2a Handle RLP to string UTF-8 decoding errors (#2217) (#2226) 2016-09-22 14:48:54 +02:00
Denis S. Soldatov aka General-Beck
d59e2ecbc6 Update gitlab-ci
fix x86_64-pc-windows-msvc
2016-09-21 22:36:54 +07:00
Denis S. Soldatov aka General-Beck
a97ca5aaf4 Update gitlab-ci
remove -export
2016-09-21 22:25:51 +07:00
Tomasz Drwięga
df61b1b328 Fixing compilation without ui feature (#2207) 2016-09-21 11:51:49 +01:00
Tomasz Drwięga
52a69d19e6 Avoid cloning clean stuff (#2173) 2016-09-20 23:10:26 +08:00
Arkadiy Paronyan
33084aaa07 v1.3.2 (#2200) 2016-09-20 14:21:17 +08:00
Arkadiy Paronyan
2a82fa0a47 Backports to beta (#2068)
* Fix several RPCs (#1926)

* Fix up pending receipts details.

* Add support for additional params and registry over RPC.

* Fix tests.

* Add test, additional fix.

Fixes #1932.

* Fix up tests.

* Fix test.

* Fix test.

* DB WAL size limit (#1935)

* Limit WAL size

* Check pruning by db modification date (#1924)

* Cache address hash (#1943)

* ECIES without MAC (#1948)

* Use random IV for ECIES AES

* ECIES encrypt/decrypt for a single message

* Derive IV from shared secret

* Apply settings to column families

* fixed #1933 (#1979)

* Fixed neighbours collection (#1996)

* dapps-hosts configuration

* Disabled counter check

* always process trie death row on commit, add more tracing

* fixed transaction addresses mapping, fixes #1971

* simplified iterator

* v1.3.1

* v1.3.1
2016-09-11 14:04:17 +02:00
Denis S. Soldatov aka General-Beck
1ccc90108c Update gitlab-ci 2016-09-10 00:59:40 +07:00
Marek Kotewicz
e60c5b59a6 Fixing serde overflow error (#1977) (#2030) 2016-09-01 12:23:05 +02:00
Denis S. Soldatov aka General-Beck
b199fbb6c6 Update gitlab-ci
clone from master
2016-08-27 02:59:47 +07:00
Denis S. Soldatov aka General-Beck
d9d506db57 Update gitlab-ci
remove CC&CXX env from beta branch
2016-08-19 12:18:37 +07:00
Arkadiy Paronyan
425e1ce04c Check pruning by db modification date (#1924) 2016-08-12 17:48:11 +02:00
Tomasz Drwięga
8741d0d155 Bumping Parity UI (#1920) 2016-08-12 12:58:31 +02:00
Arkadiy Paronyan
dfb2ddfdc2 Backports to beta (#1919)
* RPC errors & logs (#1845)

* Refactoring errors in RPC

* Updating jsonrpc-core

* Fixing code_at

* Avoid mentioning obvious segments in proof

[ci:skip]

* fixed cache_manager lock order

* Purging .derefs, fixing clippy warnings. (#1890)

* Fixing clippy warnings

* Purging derefs

* Simplifying engine derefs

* Simplifying more engine derefs

* Adding more details to miner log

* fixed #1889, .DS_Store is no longer treated as key file (#1892)

* fixed #1889, .DS_Store is no longer treated as key file

* ethstore filters directories, hidden files and common system files

* fixed compiling

* fix regression with geth dir

* fix regression with geth dir

* Fix ipc compilation and add ipc feature to test targets (#1902)

* fix compilation and add it to the ci run

* no separator?

* use quotes and spaces

* RocksDB version bump

* Don't return deleted nodes that are not yet flushed (#1908)

* polling & connection timeouts (#1910)

* Peers RPC + UI displaying active/connected/max peers (#1915)

* Peers API

* Bumping Parity-UI

* Fixing tests

* Save nodes removed from backing_overlay until commit (#1917)
2016-08-12 12:16:20 +02:00
Arkadiy Paronyan
52ac5a00f5 Backports for beta (#1888)
* Sync to peers with confirmed fork block only (#1863)

* Fixing gas conversion

* Validating u256->usize conversion

* Update cache usage on commiting block info (#1871)

* Use UntrustedRlp for block verification (#1872)

* take snapshot at specified block and slightly better informants (#1873)

* prettier informant for snapshot creation

* allow taking snapshot at a given block

* minor tweaks

* elaborate on cli

* Send new block hashes to all peers (#1875)

* Reduce max open files (#1876)

* ws-rs update

* Fixing test

* Fixing miner deadlock (#1885)
2016-08-09 14:05:02 +02:00
arkpar
6336bdecf3 Version set to beta 2016-08-06 08:08:27 +02:00
2411 changed files with 24779 additions and 216564 deletions

4
.gitignore vendored
View File

@@ -30,7 +30,3 @@
# Build artifacts
out/
.vscode
/parity.*

View File

@@ -1,118 +1,69 @@
stages:
- test
- js-build
- push-release
- build
- test
variables:
GIT_DEPTH: "3"
SIMPLECOV: "true"
SIMPLECOV: "true"
RUST_BACKTRACE: "1"
RUSTFLAGS: ""
CARGOFLAGS: ""
cache:
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
untracked: true
linux-stable:
stage: build
image: ethcore/rust:stable
only:
- master
- beta
- tags
- stable
- triggers
script:
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- cargo build --release --verbose
- strip target/release/parity
- strip target/release/evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/evm deb/usr/bin/evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- md5sum target/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/checksum --body checksum
tags:
- rust
- rust-stable
artifacts:
paths:
- target/release/parity
- target/release/parity/evmbin
- target/release/parity/ethstore
- target/release/parity/ethkey
name: "stable-x86_64-unknown-linux-gnu_parity"
linux-stable-debian:
linux-stable-14.04:
stage: build
image: ethcore/rust-debian:latest
image: ethcore/rust-14.04:latest
only:
- master
- beta
- tags
- stable
- triggers
script:
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- cargo build --release --verbose
- strip target/release/parity
- strip target/release/evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/evm deb/usr/bin/evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- md5sum target/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/checksum --body checksum
tags:
- rust
- rust-debian
- rust-14.04
artifacts:
paths:
- target/release/parity
name: "stable-x86_64-unknown-debian-gnu_parity"
name: "stable-x86_64-unknown-ubuntu_14_04-gnu_parity"
linux-beta:
stage: build
image: ethcore/rust:beta
only:
- master
- beta
- tags
- stable
- triggers
script:
- cargo build -j $(nproc) --release $CARGOFLAGS
- cargo build --release --verbose
- strip target/release/parity
tags:
- rust
@@ -126,12 +77,12 @@ linux-nightly:
stage: build
image: ethcore/rust:nightly
only:
- master
- beta
- tags
- stable
- triggers
script:
- cargo build -j $(nproc) --release $CARGOFLAGS
- cargo build --release --verbose
- strip target/release/parity
tags:
- rust
@@ -145,26 +96,20 @@ linux-centos:
stage: build
image: ethcore/rust-centos:latest
only:
- master
- beta
- tags
- stable
- triggers
script:
- export CXX="g++"
- export CC="gcc"
- export PLATFORM=x86_64-unknown-centos-gnu
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build --release --verbose
- strip target/release/parity
- md5sum target/release/parity > parity.md5
- export SHA3=$(target/release/parity tools hash target/release/parity)
- aws configure set aws_access_key_id $s3_key
- md5sum target/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/checksum --body checksum
tags:
- rust
- rust-centos
@@ -172,84 +117,27 @@ linux-centos:
paths:
- target/release/parity
name: "x86_64-unknown-centos-gnu_parity"
linux-i686:
stage: build
image: ethcore/rust-i686:latest
only:
- beta
- tags
- stable
- triggers
script:
- export HOST_CC=gcc
- export HOST_CXX=g++
- export COMMIT=$(git rev-parse HEAD)
- export PLATFORM=i686-unknown-linux-gnu
- cargo build -j $(nproc) --target i686-unknown-linux-gnu --features final --release $CARGOFLAGS
- strip target/$PLATFORM/release/parity
- md5sum target/$PLATFORM/release/parity > parity.md5
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
- sh scripts/deb-build.sh i386
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-i686
artifacts:
paths:
- target/i686-unknown-linux-gnu/release/parity
name: "i686-unknown-linux-gnu"
allow_failure: true
linux-armv7:
stage: build
image: ethcore/rust-armv7:latest
only:
- master
- beta
- tags
- stable
- triggers
script:
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=armv7-unknown-linux-gnueabihf
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/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
- cargo build --target armv7-unknown-linux-gnueabihf --release --verbose
- arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity
- md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/checksum --body checksum
tags:
- rust
- rust-arm
@@ -262,40 +150,23 @@ linux-arm:
stage: build
image: ethcore/rust-arm:latest
only:
- master
- beta
- tags
- stable
- triggers
script:
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=arm-unknown-linux-gnueabihf
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/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
- cargo build --target arm-unknown-linux-gnueabihf --release --verbose
- arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity
- md5sum target/arm-unknown-linux-gnueabihf/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/checksum --body checksum
tags:
- rust
- rust-arm
@@ -308,33 +179,23 @@ linux-armv6:
stage: build
image: ethcore/rust-armv6:latest
only:
- master
- beta
# - tags
# - stable
# - triggers
- tags
- stable
script:
- export CC=arm-linux-gnueabi-gcc
- export CXX=arm-linux-gnueabi-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=arm-unknown-linux-gnueabi
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- arm-linux-gnueabi-strip target/$PLATFORM/release/parity
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key
- cargo build --target arm-unknown-linux-gnueabi --release --verbose
- arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity
- md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/checksum --body checksum
tags:
- rust
- rust-arm
@@ -347,39 +208,23 @@ linux-aarch64:
stage: build
image: ethcore/rust-aarch64:latest
only:
- master
- beta
- tags
- stable
- triggers
script:
- export CC=aarch64-linux-gnu-gcc
- export CXX=aarch64-linux-gnu-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=aarch64-unknown-linux-gnu
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh arm64
- cp target/$PLATFORM/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
- cargo build --target aarch64-unknown-linux-gnu --release --verbose
- aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity
- md5sum target/aarch64-unknown-linux-gnu/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/checksum --body checksum
tags:
- rust
- rust-arm
@@ -391,36 +236,17 @@ linux-aarch64:
darwin:
stage: build
only:
- master
- beta
- tags
- stable
- triggers
script: |
export COMMIT=$(git rev-parse HEAD)
export PLATFORM=x86_64-apple-darwin
cargo build -j 8 --features final --release #$CARGOFLAGS
cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS
rm -rf parity.md5
md5sum target/release/parity > parity.md5
export SHA3=$(target/release/parity tools hash target/release/parity)
cd mac
xcodebuild -configuration Release
cd ..
packagesbuild -v mac/Parity.pkgproj
productsign --sign 'Developer ID Installer: PARITY TECHNOLOGIES LIMITED (P2PX3JU8FT)' target/release/Parity\ Ethereum.pkg target/release/Parity\ Ethereum-signed.pkg
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
md5sum "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" >> "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
aws configure set aws_access_key_id $s3_key
aws configure set aws_secret_access_key $s3_secret
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
script:
- cargo build --release --verbose
- md5sum target/release/parity >> checksum
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/checksum --body checksum
tags:
- osx
artifacts:
@@ -428,183 +254,41 @@ darwin:
- target/release/parity
name: "x86_64-apple-darwin_parity"
windows:
cache:
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
untracked: true
stage: build
only:
- master
- beta
- tags
- stable
- triggers
script:
- set PLATFORM=x86_64-pc-windows-msvc
- set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt
- set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64
- set RUST_BACKTRACE=1
- set RUSTFLAGS=%RUSTFLAGS%
- set RUSTFLAGS=-Zorbit=off
- rustup default stable-x86_64-pc-windows-msvc
- cargo build --features final --release #%CARGOFLAGS%
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
- target\release\parity.exe tools hash target\release\parity.exe > parity.sha3
- set /P SHA3=<parity.sha3
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
- cd nsis
- makensis.exe installer.nsi
- copy installer.exe InstallParity.exe
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
- md5sums InstallParity.exe > InstallParity.exe.md5
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
- md5sums win-installer.zip > win-installer.zip.md5
- cd ..\target\release\
- md5sums parity.exe > parity.exe.md5
- zip parity.zip parity.exe parity.md5
- md5sums parity.zip > parity.zip.md5
- cd ..\..
- cargo build --release --verbose
- cmd md5sum target\release\parity >> checksum
- aws configure set aws_access_key_id %s3_key%
- aws configure set aws_secret_access_key %s3_secret%
- echo %CI_BUILD_REF_NAME%
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %S3_BUCKET%
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity --body target\release\parity.exe
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity --body target\release\parity.pdb
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/checksum --body checksum
tags:
- rust-windows
artifacts:
paths:
- target/release/parity.exe
- nsis/InstallParity.exe
- target/release/parity.pdb
name: "x86_64-pc-windows-msvc_parity"
docker-build:
stage: build
only:
- tags
- triggers
before_script:
- docker info
script:
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
- sh scripts/docker-build.sh $DOCKER_TAG
tags:
- docker
test-darwin:
test-linux:
stage: test
only:
- triggers
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- osx
allow_failure: true
test-windows:
stage: test
only:
- triggers
before_script:
- git submodule update --init --recursive
script:
- set RUST_BACKTRACE=1
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p 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 RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS&&./scripts/cov.sh "$KCOV_CMD"; fi
tags:
- rust
- rust-stable
js-test:
stage: test
image: ethcore/rust:stable
before_script:
- git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
script:
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
tags:
- rust
- rust-stable
test-rust-beta:
stage: test
only:
- triggers
image: ethcore/rust:beta
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- rust
- rust-beta
allow_failure: true
test-rust-nightly:
stage: test
only:
- triggers
image: ethcore/rust:nightly
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- rust
- rust-nightly
allow_failure: true
js-release:
stage: js-build
only:
- master
- beta
- stable
- tags
image: ethcore/rust:stable
before_script:
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
- echo $JS_FILES_MODIFIED
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
script:
- echo $JS_FILES_MODIFIED
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
- ./test.sh --verbose
tags:
- rust-test
- javascript
push-release:
stage: push-release
only:
- tags
image: ethcore/rust:stable
script:
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
tags:
- curl
dependencies:
- linux-stable

77
.travis.yml Normal file
View File

@@ -0,0 +1,77 @@
sudo: required
dist: trusty
language: rust
branches:
only:
- master
- /^beta-.*$/
- /^stable-.*$/
- /^beta$/
- /^stable$/
git:
depth: 3
matrix:
include:
- rust: stable
env: RUN_TESTS="true"
- rust: beta
env: RUN_COVERAGE="true"
- rust: stable
env: RUN_DOCS="true"
env:
global:
- CXX="g++-4.8"
- CC="gcc-4.8"
- RUST_BACKTRACE="1"
- RUN_TESTS="false"
- RUN_COVERAGE="false"
- RUN_DOCS="false"
# GH_TOKEN for documentation
- secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw=
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov"
cache:
apt: true
directories:
- $TRAVIS_BUILD_DIR/target
- $TRAVIS_BUILD_DIR/kcov-master
- $HOME/.cargo
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
- gcc-4.8
- g++-4.8
install:
- ([ "$RUN_COVERAGE" = "false" ]) || (test -x $KCOV_CMD) || (
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
tar xzf master.tar.gz &&
mkdir -p kcov-master/build &&
cd kcov-master/build &&
cmake .. &&
make && make install DESTDIR=../tmp &&
cd
)
script:
- if [ "$RUN_TESTS" = "true" ]; then ./test.sh --verbose; fi
- if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
[ "$RUN_DOCS" = "true" ] &&
./scripts/doc.sh &&
pip install --user ghp-import &&
/home/travis/.local/bin/ghp-import -n target/doc &&
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages

1707
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,16 @@
[package]
description = "Parity Ethereum client"
description = "Ethcore client."
name = "parity"
version = "1.6.2"
version = "1.3.5"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
rustc_version = "0.1"
syntex = "*"
ethcore-ipc-codegen = { path = "ipc/codegen" }
ethcore-ipc-tests = { path = "ipc/tests" }
[dependencies]
log = "0.3"
@@ -14,46 +21,27 @@ time = "0.1"
num_cpus = "0.2"
number_prefix = "0.2"
rpassword = "0.2.1"
semver = "0.5"
semver = "0.2"
ansi_term = "0.7"
lazy_static = "0.2"
regex = "0.1"
isatty = "0.1"
toml = "0.2"
serde = "0.9"
serde_json = "0.9"
app_dirs = "1.1.1"
fdlimit = "0.1"
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
ethsync = { path = "sync" }
fdlimit = { path = "util/fdlimit" }
ethcore = { path = "ethcore" }
ethcore-util = { path = "util" }
ethsync = { path = "sync" }
ethcore-io = { path = "util/io" }
ethcore-devtools = { path = "devtools" }
ethcore-rpc = { path = "rpc" }
ethcore-signer = { path = "signer" }
ethcore-ipc = { path = "ipc/rpc" }
ethcore-ipc-nano = { path = "ipc/nano" }
ethcore-ipc = { path = "ipc/rpc" }
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
ethcore-light = { path = "ethcore/light" }
ethcore-logger = { path = "logger" }
ethcore-stratum = { path = "stratum" }
evmbin = { path = "evmbin" }
rlp = { path = "util/rlp" }
rpc-cli = { path = "rpc_cli" }
parity-rpc-client = { path = "rpc_client" }
parity-hash-fetch = { path = "hash-fetch" }
parity-ipfs-api = { path = "ipfs" }
parity-updater = { path = "updater" }
parity-reactor = { path = "util/reactor" }
parity-local-store = { path = "local-store" }
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git", branch = "beta" }
ethcore-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.103", optional = true}
ethcore-secretstore = { path = "secret_store", optional = true }
[dev-dependencies]
ethcore-ipc-tests = { path = "ipc/tests" }
clippy = { version = "0.0.80", optional = true}
[target.'cfg(windows)'.dependencies]
winapi = "0.2"
@@ -61,31 +49,19 @@ winapi = "0.2"
[target.'cfg(not(windows))'.dependencies]
daemonize = "0.2"
[dependencies.hyper]
version = "0.8"
default-features = false
[features]
default = ["ui-precompiled"]
ui = [
"dapps",
"ethcore-dapps/ui",
"ethcore-signer/ui",
]
ui-precompiled = [
"dapps",
"ethcore-signer/ui-precompiled",
"ethcore-dapps/ui-precompiled",
]
default = ["ui", "use-precompiled-js"]
ui = ["dapps", "ethcore-signer/ui"]
use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"]
dapps = ["ethcore-dapps"]
ipc = ["ethcore/ipc", "ethsync/ipc"]
ipc = ["ethcore/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"]
ethkey-cli = ["ethcore/ethkey-cli"]
ethstore-cli = ["ethcore/ethstore-cli"]
evm-debug = ["ethcore/evm-debug"]
evm-debug-tests = ["ethcore/evm-debug-tests"]
slow-blocks = ["ethcore/slow-blocks"]
final = ["ethcore-util/final"]
secretstore = ["ethcore-secretstore"]
[[bin]]
path = "parity/main.rs"
@@ -94,4 +70,4 @@ name = "parity"
[profile.release]
debug = false
lto = false
panic = "abort"

View File

@@ -1,51 +1,41 @@
# [Parity](https://ethcore.io/parity.html)
### Fast, light, and robust Ethereum implementation
[![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/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 [![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url]
[Internal Documentation][doc-url]
Be sure to check out [our wiki][wiki-url] for more information.
[travis-image]: https://travis-ci.org/ethcore/parity.svg?branch=master
[travis-url]: https://travis-ci.org/ethcore/parity
[coveralls-image]: https://coveralls.io/repos/github/ethcore/parity/badge.svg?branch=master
[coveralls-url]: https://coveralls.io/github/ethcore/parity?branch=master
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[gitter-url]: https://gitter.im/ethcore/parity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg
[license-url]: https://www.gnu.org/licenses/gpl-3.0.en.html
[doc-url]: https://ethcore.github.io/parity/ethcore/index.html
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
[doc-url]: http://ethcore.github.io/parity/ethcore/index.html
[wiki-url]: https://github.com/ethcore/parity/wiki
**Parity requires Rust version 1.15.0 to build**
----
## About Parity
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and
cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) 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
By default, Parity will run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
of RPC APIs.
Parity also runs a server for running decentralized apps, or "Dapps", on `http://127.0.0.1:8080`.
This includes a few useful Dapps, including Ethereum Wallet, Maker OTC, and a node status page.
In a near-future release, it will be easy to install Dapps and use them through this web interface.
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.5. You can download it at https://ethcore.io/parity.html or follow the instructions
Parity's current release is 1.2. You can download it at https://ethcore.io/parity.html or follow the instructions
below to build from source.
----
@@ -56,18 +46,11 @@ Parity is fully compatible with Stable Rust.
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
- Linux:
```bash
$ curl https://sh.rustup.rs -sSf | sh
```
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl`, `libudev-dev` and `pkg-config` packages to be installed.
- OSX:
- Linux and OSX:
```bash
$ curl https://sh.rustup.rs -sSf | sh
```
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
- Windows
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
@@ -100,34 +83,10 @@ $ cargo build --release
```
This will produce an executable in the `./target/release` subdirectory.
Note: if cargo fails to parse manifest try:
```bash
$ ~/.cargo/bin/cargo build --release
```
----
## Simple one-line installer for Mac and Ubuntu
```bash
bash <(curl https://get.parity.io -Lk)
```
## Start Parity
### Manually
To start Parity manually, just run
To get started, just run
```bash
$ ./target/release/parity
```
and Parity will begin syncing the Ethereum blockchain.
### Using systemd service file
To start Parity as a regular user using systemd init:
1. Copy `parity/scripts/parity.service` to your
systemd user directory (usually `~/.config/systemd/user`).
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
`ARGS="ARG1 ARG2 ARG3"`.
Example: `ARGS="ui --identity MyMachine"`.
and parity will begin syncing the Ethereum blockchain.

50
appveyor.yml Normal file
View File

@@ -0,0 +1,50 @@
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
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 }
- 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

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,9 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate parity_dapps_glue;
extern crate rustc_version;
use rustc_version::{version_meta, Channel};
fn main() {
parity_dapps_glue::js::build(env!("CARGO_MANIFEST_DIR"), "build");
parity_dapps_glue::generate();
if let Channel::Nightly = version_meta().channel {
println!("cargo:rustc-cfg=nightly");
}
}

View File

@@ -1,45 +1,46 @@
[package]
description = "Parity Dapps crate"
name = "ethcore-dapps"
version = "1.6.0"
version = "1.3.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
authors = ["Ethcore <admin@ethcore.io"]
build = "build.rs"
[lib]
[dependencies]
rand = "0.3"
log = "0.3"
env_logger = "0.3"
futures = "0.1"
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-core = "2.1"
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" }
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
unicase = "1.3"
url = "1.0"
rustc-serialize = "0.3"
serde = "0.9"
serde_json = "0.9"
serde_derive = "0.9"
linked-hash-map = "0.3"
parity-dapps-glue = "1.4"
base32 = "0.3"
mime = "0.2"
mime_guess = "1.6.1"
time = "0.1.35"
zip = { version = "0.1", default-features = false }
ethcore-devtools = { path = "../devtools" }
serde = "0.7.0"
serde_json = "0.7.0"
serde_macros = { version = "0.7.0", optional = true }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
fetch = { path = "../util/fetch" }
parity-ui = { path = "./ui" }
parity-hash-fetch = { path = "../hash-fetch" }
parity-reactor = { path = "../util/reactor" }
parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
# List of apps
parity-dapps-status = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true }
mime_guess = { version = "1.6.1" }
clippy = { version = "0.0.80", optional = true}
clippy = { version = "0.0.103", optional = true}
[build-dependencies]
serde_codegen = { version = "0.7.0", optional = true }
syntex = "*"
[features]
default = ["serde_codegen", "extra-dapps"]
extra-dapps = ["parity-dapps-wallet"]
nightly = ["serde_macros"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
ui = ["parity-ui/no-precompiled-js"]
ui-precompiled = ["parity-ui/use-precompiled-js"]
use-precompiled-js = [
"parity-dapps-status/use-precompiled-js",
"parity-dapps-home/use-precompiled-js",
"parity-dapps-wallet/use-precompiled-js"
]

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,8 +14,32 @@
// 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;
#[cfg(not(feature = "serde_macros"))]
mod inner {
extern crate syntex;
extern crate serde_codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("./src/api/types.rs.in");
let dst = Path::new(&out_dir).join("types.rs");
let mut registry = syntex::Registry::new();
serde_codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
}
#[cfg(feature = "serde_macros")]
mod inner {
pub fn main() {}
}
fn main() {
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
inner::main();
}

View File

@@ -1,31 +0,0 @@
[package]
description = "Base Package for all Parity built-in dapps"
name = "parity-dapps-glue"
version = "1.6.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.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 = []

View File

@@ -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".
```

View File

@@ -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() {}
}

View File

@@ -1,189 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate aster;
extern crate glob;
extern crate mime_guess;
use self::mime_guess::guess_mime_type;
use std::path::{self, Path, PathBuf};
use std::ops::Deref;
use syntax::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());
}

View File

@@ -1,89 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(feature="use-precompiled-js", allow(dead_code))]
#![cfg_attr(feature="use-precompiled-js", allow(unused_imports))]
use std::fmt;
use std::process::Command;
#[cfg(not(windows))]
mod platform {
use std::process::Command;
pub static NPM_CMD: &'static str = "npm";
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
cmd
}
}
#[cfg(windows)]
mod platform {
use std::process::{Command, Stdio};
pub static NPM_CMD: &'static str = "npm.cmd";
// NOTE [ToDr] For some reason on windows
// We cannot have any file descriptors open when running a child process
// during build phase.
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
}
}
fn die<T : fmt::Debug>(s: &'static str, e: T) -> ! {
panic!("Error: {}: {:?}", s, e);
}
#[cfg(feature = "use-precompiled-js")]
pub fn test(_path: &str) {
}
#[cfg(feature = "use-precompiled-js")]
pub fn build(_path: &str, _dest: &str) {
}
#[cfg(not(feature = "use-precompiled-js"))]
pub fn build(path: &str, dest: &str) {
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
.arg("install")
.arg("--no-progress")
.current_dir(path)
.status()
.unwrap_or_else(|e| die("Installing node.js dependencies with npm", e));
assert!(child.success(), "There was an error installing dependencies.");
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
.arg("run")
.arg("build")
.env("NODE_ENV", "production")
.env("BUILD_DEST", dest)
.current_dir(path)
.status()
.unwrap_or_else(|e| die("Building JS code", e));
assert!(child.success(), "There was an error build JS code.");
}
#[cfg(not(feature = "use-precompiled-js"))]
pub fn test(path: &str) {
let child = Command::new(platform::NPM_CMD)
.arg("run")
.arg("test")
.current_dir(path)
.status()
.unwrap_or_else(|e| die("Running test command", e));
assert!(child.success(), "There was an error while running JS tests.");
}

View File

@@ -1,39 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "with-syntex"), feature(rustc_private, plugin))]
#![cfg_attr(not(feature = "with-syntex"), plugin(quasi_macros))]
#[cfg(feature = "with-syntex")]
extern crate syntex;
#[cfg(feature = "with-syntex")]
#[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");

View File

@@ -1,46 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate quasi;
mod codegen;
mod build;
pub mod js;
pub use build::inner::generate;
use std::default::Default;
#[derive(Clone)]
pub struct File {
pub path: &'static str,
pub content: &'static [u8],
// TODO: use strongly-typed MIME.
pub content_type: &'static str,
}
#[derive(Clone, Debug)]
pub struct Info {
pub name: &'static str,
pub version: &'static str,
pub author: &'static str,
pub description: &'static str,
pub icon_url: &'static str,
}
pub trait WebApp : Default + Send + Sync {
fn file(&self, path: &str) -> Option<&File>;
fn info(&self) -> Info;
}

Binary file not shown.

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,37 +15,23 @@
// 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 hyper::{server, net, Decoder, Encoder, Next};
use api::types::{App, ApiError};
use api::response;
use apps::fetcher::Fetcher;
use api::response::{as_json, as_json_error, ping_response};
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>>,
local_domain: String,
endpoints: Arc<Endpoints>,
fetcher: Arc<Fetcher>,
}
impl RestApi {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
pub fn new(local_domain: String, endpoints: Arc<Endpoints>) -> Box<Endpoint> {
Box::new(RestApi {
cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
"null" => AccessControlAllowOrigin::Null,
other => AccessControlAllowOrigin::Value(other.into()),
}).collect()),
local_domain: local_domain,
endpoints: endpoints,
fetcher: fetcher,
})
}
@@ -57,98 +43,39 @@ impl RestApi {
}
impl Endpoint for RestApi {
fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
Box::new(RestApiRouter::new(self.clone(), path, control))
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
Box::new(RestApiRouter {
api: self.clone(),
handler: as_json_error(&ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
}),
})
}
}
struct RestApiRouter {
api: RestApi,
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();
}
// TODO [ToDr] Consider using `path.app_params` instead
let url = extract_url(&request);
if url.is_none() {
// Just return 404 if we can't parse URL
return Next::write();
}
let url = url.expect("Check for None early-exists above; qed");
let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed");
let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed");
let url = url.expect("Check for None is above; qed");
let endpoint = url.path.get(1).map(|v| v.as_str());
let hash = url.path.get(2).map(|v| v.as_str());
// at this point path.app_id contains 'api', adjust it to the hash properly, otherwise
// we will try and retrieve 'api' as the hash when doing the /api/content route
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
let handler = endpoint.and_then(|v| match v {
"apps" => Some(response::as_json(&self.api.list_apps())),
"ping" => Some(response::ping()),
"content" => self.resolve_content(hash, path, control),
_ => None
"apps" => Some(as_json(&self.api.list_apps())),
"ping" => Some(ping_response(&self.api.local_domain)),
_ => None,
});
// Overwrite default
@@ -164,7 +91,6 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
*res.headers_mut() = self.response_headers();
self.handler.on_response(res)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,6 +16,9 @@
//! REST API
#![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))]
#![cfg_attr(feature="nightly", plugin(serde_macros, clippy))]
mod api;
mod response;
mod types;

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -19,22 +19,18 @@ 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> {
Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
}
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> {
Box::new(ContentHandler::not_found(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
}
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())
pub fn ping_response(local_domain: &str) -> Box<Handler> {
Box::new(EchoHandler::cors(vec![
format!("http://{}", local_domain),
// Allow CORS calls also for localhost
format!("http://{}", local_domain.replace("127.0.0.1", "localhost")),
]))
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,51 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use endpoint::EndpointInfo;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
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)]
#[serde(deny_unknown_fields)]
pub struct ApiError {
pub code: String,
pub title: String,
pub detail: String,
}
#[cfg(feature = "serde_macros")]
include!("types.rs.in");
#[cfg(not(feature = "serde_macros"))]
include!(concat!(env!("OUT_DIR"), "/types.rs"));

50
dapps/src/api/types.rs.in Normal file
View File

@@ -0,0 +1,50 @@
// 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, 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(),
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ApiError {
pub code: String,
pub title: String,
pub detail: String,
}

View File

@@ -1,128 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Fetchable Dapps support.
use std::fs;
use linked_hash_map::LinkedHashMap;
use page::LocalPageEndpoint;
use handlers::FetchControl;
pub enum ContentStatus {
Fetching(FetchControl),
Ready(LocalPageEndpoint),
}
#[derive(Default)]
pub struct ContentCache {
cache: LinkedHashMap<String, ContentStatus>,
}
impl ContentCache {
pub fn insert(&mut self, content_id: String, status: ContentStatus) -> Option<ContentStatus> {
self.cache.insert(content_id, status)
}
pub fn remove(&mut self, content_id: &str) -> Option<ContentStatus> {
self.cache.remove(content_id)
}
pub fn get(&mut self, content_id: &str) -> Option<&mut ContentStatus> {
self.cache.get_refresh(content_id)
}
pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> {
let len = self.cache.len();
if len <= expected_size {
return Vec::new();
}
let mut removed = Vec::with_capacity(len - expected_size);
while self.cache.len() > expected_size {
let entry = self.cache.pop_front().expect("expected_size bounded at 0, len is greater; qed");
match entry.1 {
ContentStatus::Fetching(ref fetch) => {
trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
// Mark as aborted
fetch.abort()
},
ContentStatus::Ready(ref endpoint) => {
trace!(target: "dapps", "Removing {} because of limit.", entry.0);
// Remove path (dir or file)
let res = fs::remove_dir_all(&endpoint.path()).or_else(|_| fs::remove_file(&endpoint.path()));
if let Err(e) = res {
warn!(target: "dapps", "Unable to remove dapp/content from cache: {:?}", e);
}
}
}
removed.push(entry);
}
removed
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.cache.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn only_keys(data: Vec<(String, ContentStatus)>) -> Vec<String> {
data.into_iter().map(|x| x.0).collect()
}
#[test]
fn should_remove_least_recently_used() {
// given
let mut cache = ContentCache::default();
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
// when
let res = cache.clear_garbage(2);
// then
assert_eq!(cache.len(), 2);
assert_eq!(only_keys(res), vec!["a"]);
}
#[test]
fn should_update_lru_if_accessed() {
// given
let mut cache = ContentCache::default();
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
// when
cache.get("a");
let res = cache.clear_garbage(2);
// then
assert_eq!(cache.len(), 2);
assert_eq!(only_keys(res), vec!["b"]);
}
}

View File

@@ -1,255 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use zip;
use std::{fs, fmt};
use std::io::{self, Read, Write};
use std::path::PathBuf;
use fetch::{self, Mime};
use util::H256;
use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentValidator, ValidatorResponse};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
fn write_response_and_check_hash(
id: &str,
mut content_path: PathBuf,
filename: &str,
response: fetch::Response
) -> Result<(fs::File, PathBuf), ValidationError> {
// try to parse id
let id = id.parse().map_err(|_| ValidationError::InvalidContentId)?;
// check if content exists
if content_path.exists() {
warn!(target: "dapps", "Overwriting existing content at 0x{:?}", id);
fs::remove_dir_all(&content_path)?
}
// create directory
fs::create_dir_all(&content_path)?;
// append filename
content_path.push(filename);
// Now write the response
let mut file = io::BufWriter::new(fs::File::create(&content_path)?);
let mut reader = io::BufReader::new(response);
io::copy(&mut reader, &mut file)?;
file.flush()?;
// Validate hash
// TODO [ToDr] calculate sha3 in-flight while reading the response
let mut file = io::BufReader::new(fs::File::open(&content_path)?);
let hash = sha3(&mut file)?;
if id == hash {
Ok((file.into_inner(), content_path))
} else {
Err(ValidationError::HashMismatch {
expected: id,
got: hash,
})
}
}
pub struct Content {
id: String,
mime: Mime,
content_path: PathBuf,
on_done: OnDone,
}
impl Content {
pub fn new(id: String, mime: Mime, content_path: PathBuf, on_done: OnDone) -> Self {
Content {
id: id,
mime: mime,
content_path: content_path,
on_done: on_done,
}
}
}
impl ContentValidator for Content {
type Error = ValidationError;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let validate = |content_path: PathBuf| {
// Create dir
let (_, content_path) = write_response_and_check_hash(self.id.as_str(), content_path.clone(), self.id.as_str(), response)?;
Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))
};
// Prepare path for a file
let content_path = self.content_path.join(&self.id);
// Make sure to always call on_done (even in case of errors)!
let result = validate(content_path.clone());
// remove the file if there was an error
if result.is_err() {
// Ignore errors since the file might not exist
let _ = fs::remove_dir_all(&content_path);
}
(self.on_done)(result.as_ref().ok().cloned());
result.map(ValidatorResponse::Local)
}
}
pub struct Dapp {
id: String,
dapps_path: PathBuf,
on_done: OnDone,
embeddable_on: Option<(String, u16)>,
}
impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Option<(String, u16)>) -> Self {
Dapp {
id: id,
dapps_path: dapps_path,
on_done: on_done,
embeddable_on: embeddable_on,
}
}
fn find_manifest(zip: &mut zip::ZipArchive<fs::File>) -> Result<(Manifest, PathBuf), ValidationError> {
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
if !file.name().ends_with(MANIFEST_FILENAME) {
continue;
}
// try to read manifest
let mut manifest = String::new();
let manifest = file
.read_to_string(&mut manifest).ok()
.and_then(|_| deserialize_manifest(manifest).ok());
if let Some(manifest) = manifest {
let mut manifest_location = PathBuf::from(file.name());
manifest_location.pop(); // get rid of filename
return Ok((manifest, manifest_location));
}
}
Err(ValidationError::ManifestNotFound)
}
}
impl ContentValidator for Dapp {
type Error = ValidationError;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let validate = |dapp_path: PathBuf| {
let (file, zip_path) = write_response_and_check_hash(self.id.as_str(), dapp_path.clone(), &format!("{}.zip", self.id), response)?;
trace!(target: "dapps", "Opening dapp bundle at {:?}", zip_path);
// Unpack archive
let mut zip = zip::ZipArchive::new(file)?;
// First find manifest file
let (mut manifest, manifest_dir) = Self::find_manifest(&mut zip)?;
// Overwrite id to match hash
manifest.id = self.id.clone();
// Unpack zip
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
let is_dir = file.name().chars().rev().next() == Some('/');
let file_path = PathBuf::from(file.name());
let location_in_manifest_base = file_path.strip_prefix(&manifest_dir);
// Create files that are inside manifest directory
if let Ok(location_in_manifest_base) = location_in_manifest_base {
let p = dapp_path.join(location_in_manifest_base);
// Check if it's a directory
if is_dir {
fs::create_dir_all(p)?;
} else {
let mut target = fs::File::create(p)?;
io::copy(&mut file, &mut target)?;
}
}
}
// Remove zip
fs::remove_file(&zip_path)?;
// Write manifest
let manifest_str = serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)?;
let manifest_path = dapp_path.join(MANIFEST_FILENAME);
let mut manifest_file = fs::File::create(manifest_path)?;
manifest_file.write_all(manifest_str.as_bytes())?;
// Create endpoint
let endpoint = LocalPageEndpoint::new(dapp_path, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
Ok(endpoint)
};
// Prepare directory for dapp
let target = self.dapps_path.join(&self.id);
// Validate the dapp
let result = validate(target.clone());
// remove the file if there was an error
if result.is_err() {
// Ignore errors since the file might not exist
let _ = fs::remove_dir_all(&target);
}
(self.on_done)(result.as_ref().ok().cloned());
result.map(ValidatorResponse::Local)
}
}
#[derive(Debug)]
pub enum ValidationError {
Io(io::Error),
Zip(zip::result::ZipError),
InvalidContentId,
ManifestNotFound,
ManifestSerialization(String),
HashMismatch { expected: H256, got: H256, },
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io),
ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip),
ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."),
ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."),
ValidationError::ManifestSerialization(ref err) => {
write!(f, "There was an error during Dapp Manifest serialization: {:?}", err)
},
ValidationError::HashMismatch { ref expected, ref got } => {
write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got)
},
}
}
}
impl From<io::Error> for ValidationError {
fn from(err: io::Error) -> Self {
ValidationError::Io(err)
}
}
impl From<zip::result::ZipError> for ValidationError {
fn from(err: zip::result::ZipError) -> Self {
ValidationError::Zip(err)
}
}

View File

@@ -1,265 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Fetchable Dapps support.
//! Manages downloaded (cached) Dapps and downloads them when necessary.
//! Uses `URLHint` to resolve addresses into Dapps bundle file location.
mod installers;
use std::{fs, env};
use std::path::PathBuf;
use std::sync::Arc;
use rustc_serialize::hex::FromHex;
use fetch::{Client as FetchClient, Fetch};
use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
use parity_reactor::Remote;
use hyper;
use hyper::status::StatusCode;
use {SyncStatus, random_filename};
use util::Mutex;
use page::LocalPageEndpoint;
use handlers::{ContentHandler, ContentFetcherHandler};
use endpoint::{Endpoint, EndpointPath, Handler};
use apps::cache::{ContentCache, ContentStatus};
/// Limit of cached dapps/content
const MAX_CACHED_DAPPS: usize = 20;
pub trait Fetcher: Send + Sync + 'static {
fn contains(&self, content_id: &str) -> bool;
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler>;
}
pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + Send + Sync + 'static = URLHintContract> {
dapps_path: PathBuf,
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
embeddable_on: Option<(String, u16)>,
remote: Remote,
fetch: F,
}
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
fn drop(&mut self) {
// Clear cache path
let _ = fs::remove_dir_all(&self.dapps_path);
}
}
impl<R: URLHint + Send + Sync + 'static, F: Fetch> ContentFetcher<F, R> {
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>, remote: Remote, fetch: F) -> Self {
let mut dapps_path = env::temp_dir();
dapps_path.push(random_filename());
ContentFetcher {
dapps_path: dapps_path,
resolver: resolver,
sync: sync_status,
cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_on: embeddable_on,
remote: remote,
fetch: fetch,
}
}
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
address,
))
}
#[cfg(test)]
fn set_status(&self, content_id: &str, status: ContentStatus) {
self.cache.lock().insert(content_id.to_owned(), status);
}
}
impl<R: URLHint + Send + Sync + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
fn contains(&self, content_id: &str) -> bool {
{
let mut cache = self.cache.lock();
// Check if we already have the app
if cache.get(content_id).is_some() {
return true;
}
}
// fallback to resolver
if let Ok(content_id) = content_id.from_hex() {
// else try to resolve the app_id
let has_content = self.resolver.resolve(content_id).is_some();
// if there is content or we are syncing return true
has_content || self.sync.is_major_importing()
} else {
false
}
}
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let mut cache = self.cache.lock();
let content_id = path.app_id.clone();
let (new_status, handler) = {
let status = cache.get(&content_id);
match status {
// Just serve the content
Some(&mut ContentStatus::Ready(ref endpoint)) => {
(None, endpoint.to_async_handler(path, control))
},
// Content is already being fetched
Some(&mut ContentStatus::Fetching(ref fetch_control)) if !fetch_control.is_deadline_reached() => {
trace!(target: "dapps", "Content fetching in progress. Waiting...");
(None, fetch_control.to_async_handler(path, control))
},
// We need to start fetching the content
_ => {
trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id);
let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let content = self.resolver.resolve(content_hex);
let cache = self.cache.clone();
let id = content_id.clone();
let on_done = move |result: Option<LocalPageEndpoint>| {
let mut cache = cache.lock();
match result {
Some(endpoint) => cache.insert(id.clone(), ContentStatus::Ready(endpoint)),
// In case of error
None => cache.remove(&id),
};
};
match content {
// Don't serve dapps if we are still syncing (but serve content)
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
Some(URLHintResult::Dapp(dapp)) => {
let handler = ContentFetcherHandler::new(
dapp.url(),
path,
control,
installers::Dapp::new(
content_id.clone(),
self.dapps_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
),
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
},
Some(URLHintResult::Content(content)) => {
let handler = ContentFetcherHandler::new(
content.url,
path,
control,
installers::Content::new(
content_id.clone(),
content.mime,
self.dapps_path.clone(),
Box::new(on_done),
),
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
},
None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
None => {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None,
self.embeddable_on.clone(),
)) as Box<Handler>)
},
}
},
}
};
if let Some(status) = new_status {
cache.clear_garbage(MAX_CACHED_DAPPS);
cache.insert(content_id, status);
}
handler
}
}
#[cfg(test)]
mod tests {
use std::env;
use std::sync::Arc;
use util::Bytes;
use fetch::{Fetch, Client};
use hash_fetch::urlhint::{URLHint, URLHintResult};
use parity_reactor::Remote;
use apps::cache::ContentStatus;
use endpoint::EndpointInfo;
use page::LocalPageEndpoint;
use super::{ContentFetcher, Fetcher};
struct FakeResolver;
impl URLHint for FakeResolver {
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
None
}
}
#[test]
fn should_true_if_contains_the_app() {
// given
let path = env::temp_dir();
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None, Remote::new_sync(), Client::new().unwrap());
let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(),
description: "".into(),
version: "".into(),
author: "".into(),
icon_url: "".into(),
}, Default::default(), None);
// when
fetcher.set_status("test", ContentStatus::Ready(handler));
fetcher.set_status("test2", ContentStatus::Fetching(Default::default()));
// then
assert_eq!(fetcher.contains("test"), true);
assert_eq!(fetcher.contains("test2"), true);
assert_eq!(fetcher.contains("test3"), false);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,13 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde_json;
use std::io;
use std::io::Read;
use std::fs;
use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache};
use std::path::PathBuf;
use page::LocalPageEndpoint;
use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use api::App;
struct LocalDapp {
id: String,
@@ -28,86 +29,17 @@ struct LocalDapp {
info: EndpointInfo,
}
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())
))
})
})
}
fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
}
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}
fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path);
fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path.as_str());
if let Err(e) = files {
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path.display(), e);
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
return vec![];
}
let files = files.expect("Check is done earlier");
files.map(|dir| {
let entry = dir?;
let file_type = entry.file_type()?;
let entry = try!(dir);
let file_type = try!(entry.file_type());
// skip files
if file_type.is_file() {
@@ -128,6 +60,57 @@ fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
}
m.ok()
})
.map(|(name, path)| local_dapp(name, path))
.map(|(name, path)| {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
})
.collect()
}
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push("manifest.json");
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e)));
// Try to deserialize manifest
serde_json::from_str::<App>(&s).map_err(|e| format!("{:?}", e))
})
.map(|app| EndpointInfo {
name: app.name,
description: app.description,
version: app.version,
author: app.author,
icon_url: app.icon_url,
})
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
pub fn local_endpoints(dapps_path: String) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info))
);
}
pages
}

View File

@@ -1,29 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde_json;
pub use 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))
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,71 +14,56 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
use proxypac::ProxyPac;
use web::Web;
use fetch::Fetch;
use parity_dapps::WebApp;
use parity_reactor::Remote;
use {WebProxyTokens};
mod cache;
mod fs;
pub mod fetcher;
pub mod manifest;
extern crate parity_ui;
extern crate parity_dapps_status;
extern crate parity_dapps_home;
pub const HOME_PAGE: &'static str = "parity";
pub const DAPPS_DOMAIN: &'static str = ".web3.site";
pub const RPC_PATH: &'static str = "rpc";
pub const API_PATH: &'static str = "api";
pub const UTILS_PATH: &'static str = "parity-utils";
pub const WEB_PATH: &'static str = "web";
pub const URL_REFERER: &'static str = "__referer=";
pub const DAPPS_DOMAIN : &'static str = ".parity";
pub const RPC_PATH : &'static str = "rpc";
pub const API_PATH : &'static str = "api";
pub const UTILS_PATH : &'static str = "parity-utils";
pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
pub fn main_page() -> &'static str {
"/home/"
}
pub fn all_endpoints<F: Fetch>(
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned()))
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(signer_address.clone()));
pages.insert(WEB_PATH.into(), Web::boxed(signer_address.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
pub fn all_endpoints(dapps_path: String) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path);
// Home page needs to be safe embed
// because we use Cross-Origin LocalStorage.
// TODO [ToDr] Account naming should be moved to parity.
pages.insert("home".into(), Box::new(
PageEndpoint::new_safe_to_embed(parity_dapps_home::App::default())
));
pages.insert("proxy".into(), ProxyPac::boxed());
insert::<parity_dapps_status::App>(&mut pages, "parity");
insert::<parity_dapps_status::App>(&mut pages, "status");
// Optional dapps
wallet_page(&mut pages);
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()),
}));
#[cfg(feature = "parity-dapps-wallet")]
fn wallet_page(pages: &mut Endpoints) {
extern crate parity_dapps_wallet;
insert::<parity_dapps_wallet::App>(pages, "wallet");
}
#[cfg(not(feature = "parity-dapps-wallet"))]
fn wallet_page(_pages: &mut Endpoints) {}
enum Embeddable {
Yes(Option<(String, u16)>),
#[allow(dead_code)]
No,
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,16 +16,14 @@
//! URL Endpoint traits
use hyper::{self, server, net};
use hyper::{server, net};
use std::collections::BTreeMap;
#[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath {
pub app_id: String,
pub app_params: Vec<String>,
pub host: String,
pub port: u16,
pub using_dapps_domains: bool,
}
#[derive(Debug, PartialEq, Clone)]
@@ -37,17 +35,11 @@ pub struct EndpointInfo {
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)
}
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<net::HttpStream> + Send>;
}
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
pub type Handler = server::Handler<net::HttpStream> + Send;

View File

@@ -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>

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,58 +16,52 @@
//! Simple Content Handler
use std::io::Write;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::mime::Mime;
use hyper::status::StatusCode;
use util::version;
use handlers::add_security_headers;
#[derive(Clone)]
pub struct ContentHandler {
code: StatusCode,
content: String,
mimetype: Mime,
mimetype: String,
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 ok(content: String, mimetype: String) -> Self {
ContentHandler {
code: StatusCode::Ok,
content: content,
mimetype: mimetype,
write_pos: 0
}
}
pub fn not_found(content: String, mimetype: Mime) -> Self {
Self::new(StatusCode::NotFound, content, mimetype)
pub fn forbidden(content: String, mimetype: String) -> Self {
ContentHandler {
code: StatusCode::Forbidden,
content: content,
mimetype: mimetype,
write_pos: 0
}
}
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 not_found(content: String, mimetype: String) -> Self {
ContentHandler {
code: StatusCode::NotFound,
content: content,
mimetype: mimetype,
write_pos: 0
}
}
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 {
pub fn new(code: StatusCode, content: String, mimetype: String) -> Self {
ContentHandler {
code: code,
content: content,
mimetype: mimetype,
write_pos: 0,
safe_to_embed_on: embeddable_on,
}
}
}
@@ -83,8 +77,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap()));
Next::write()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -17,25 +17,82 @@
//! Echo Handler
use std::io::Read;
use hyper::{server, Decoder, Encoder, Next};
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::method::Method;
use hyper::net::HttpStream;
use unicase::UniCase;
use super::ContentHandler;
#[derive(Default)]
#[derive(Debug, PartialEq)]
/// Type of Cross-Origin request
enum Cors {
/// Not a Cross-Origin request - no headers needed
No,
/// Cross-Origin request with valid Origin
Allowed(String),
/// Cross-Origin request with invalid Origin
Forbidden,
}
pub struct EchoHandler {
safe_origins: Vec<String>,
content: String,
cors: Cors,
handler: Option<ContentHandler>,
}
impl EchoHandler {
pub fn cors(safe_origins: Vec<String>) -> Self {
EchoHandler {
safe_origins: safe_origins,
content: String::new(),
cors: Cors::Forbidden,
handler: None,
}
}
fn cors_header(&self, origin: Option<String>) -> Cors {
fn origin_is_allowed(origin: &str, safe_origins: &[String]) -> bool {
for safe in safe_origins {
if origin.starts_with(safe) {
return true;
}
}
false
}
match origin {
Some(ref origin) if origin_is_allowed(origin, &self.safe_origins) => {
Cors::Allowed(origin.clone())
},
None => Cors::No,
_ => Cors::Forbidden,
}
}
}
impl server::Handler<HttpStream> for EchoHandler {
fn on_request(&mut self, _: server::Request<HttpStream>) -> Next {
Next::read()
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
let origin = request.headers().get_raw("origin")
.and_then(|list| list.get(0))
.and_then(|origin| String::from_utf8(origin.clone()).ok());
self.cors = self.cors_header(origin);
// Don't even read the payload if origin is forbidden!
if let Cors::Forbidden = self.cors {
self.handler = Some(ContentHandler::ok(String::new(), "text/plain".into()));
Next::write()
} else {
Next::read()
}
}
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
match decoder.read_to_string(&mut self.content) {
Ok(0) => {
self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
self.handler = Some(ContentHandler::ok(self.content.clone(), "application/json".into()));
Next::write()
},
Ok(_) => Next::read(),
@@ -47,14 +104,45 @@ impl server::Handler<HttpStream> for EchoHandler {
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.on_response(res)
if let Cors::Allowed(ref domain) = self.cors {
let mut headers = res.headers_mut();
headers.set(header::Allow(vec![Method::Options, Method::Post, Method::Get]));
headers.set(header::AccessControlAllowHeaders(vec![
UniCase("origin".to_owned()),
UniCase("content-type".to_owned()),
UniCase("accept".to_owned()),
]));
headers.set(header::AccessControlAllowOrigin::Value(domain.clone()));
}
self.handler.as_mut().unwrap().on_response(res)
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.on_response_writable(encoder)
self.handler.as_mut().unwrap().on_response_writable(encoder)
}
}
#[test]
fn should_return_correct_cors_value() {
// given
let safe_origins = vec!["chrome-extension://".to_owned(), "http://localhost:8080".to_owned()];
let cut = EchoHandler {
safe_origins: safe_origins,
content: String::new(),
cors: Cors::No,
handler: None,
};
// when
let res1 = cut.cors_header(Some("http://ethcore.io".into()));
let res2 = cut.cors_header(Some("http://localhost:8080".into()));
let res3 = cut.cors_header(Some("chrome-extension://deadbeefcafe".into()));
let res4 = cut.cors_header(None);
// then
assert_eq!(res1, Cors::Forbidden);
assert_eq!(res2, Cors::Allowed("http://localhost:8080".into()));
assert_eq!(res3, Cors::Allowed("chrome-extension://deadbeefcafe".into()));
assert_eq!(res4, Cors::No);
}

View File

@@ -1,394 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Hyper Server Handler that fetches a file during a request (proxy).
use std::fmt;
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Instant, Duration};
use fetch::{self, Fetch};
use futures::Future;
use parity_reactor::Remote;
use util::Mutex;
use hyper::{server, Decoder, Encoder, Next, Method, Control};
use hyper::net::HttpStream;
use hyper::uri::RequestUri;
use hyper::status::StatusCode;
use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting};
const FETCH_TIMEOUT: u64 = 300;
pub enum ValidatorResponse {
Local(LocalPageEndpoint),
Streaming(StreamingHandler<fetch::Response>),
}
pub trait ContentValidator: Send + 'static {
type Error: fmt::Debug + fmt::Display;
fn validate_and_install(&self, fetch::Response) -> Result<ValidatorResponse, Self::Error>;
}
enum FetchState {
Waiting,
NotStarted(String),
Error(ContentHandler),
InProgress(mpsc::Receiver<FetchState>),
Streaming(StreamingHandler<fetch::Response>),
Done(LocalPageEndpoint, Box<PageHandlerWaiting>),
}
enum WaitResult {
Error(ContentHandler),
Done(LocalPageEndpoint),
NonAwaitable,
}
#[derive(Clone)]
pub struct FetchControl {
abort: Arc<AtomicBool>,
listeners: Arc<Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>>,
deadline: Instant,
}
impl Default for FetchControl {
fn default() -> Self {
FetchControl {
abort: Arc::new(AtomicBool::new(false)),
listeners: Arc::new(Mutex::new(Vec::new())),
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
}
}
}
impl FetchControl {
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
let mut listeners = self.listeners.lock();
for (control, sender) in listeners.drain(..) {
trace!(target: "dapps", "Resuming request waiting for content...");
if let Err(e) = sender.send(status()) {
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
} else {
let _ = control.ready(Next::read());
}
}
}
fn set_status(&self, status: &FetchState) {
match *status {
FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())),
FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())),
FetchState::Streaming(_) => self.notify(|| WaitResult::NonAwaitable),
FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {},
}
}
pub fn is_deadline_reached(&self) -> bool {
self.deadline < Instant::now()
}
pub fn abort(&self) {
self.abort.store(true, Ordering::SeqCst);
}
pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> {
let (tx, rx) = mpsc::channel();
self.listeners.lock().push((control, tx));
Box::new(WaitingHandler {
receiver: rx,
state: FetchState::Waiting,
uri: RequestUri::default(),
path: path,
})
}
}
pub struct WaitingHandler {
receiver: mpsc::Receiver<WaitResult>,
state: FetchState,
uri: RequestUri,
path: EndpointPath,
}
impl server::Handler<HttpStream> for WaitingHandler {
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
self.uri = request.uri().clone();
Next::wait()
}
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
let result = self.receiver.try_recv().ok();
self.state = match result {
Some(WaitResult::Error(handler)) => FetchState::Error(handler),
Some(WaitResult::Done(endpoint)) => {
let mut page_handler = endpoint.to_page_handler(self.path.clone());
page_handler.set_uri(&self.uri);
FetchState::Done(endpoint, page_handler)
},
_ => {
warn!("A result for waiting request was not received.");
FetchState::Waiting
},
};
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder),
FetchState::Streaming(ref mut handler) => handler.on_request_readable(decoder),
FetchState::Error(ref mut handler) => handler.on_request_readable(decoder),
_ => Next::write(),
}
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_response(res),
FetchState::Streaming(ref mut handler) => handler.on_response(res),
FetchState::Error(ref mut handler) => handler.on_response(res),
_ => Next::end(),
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
_ => Next::end(),
}
}
}
#[derive(Clone)]
struct Errors {
embeddable_on: Option<(String, u16)>,
}
impl Errors {
fn download_error<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Download Error",
"There was an error when fetching the content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn invalid_content<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Invalid Dapp",
"Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn timeout_error(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
None,
self.embeddable_on.clone(),
)
}
fn method_not_allowed(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
self.embeddable_on.clone(),
)
}
}
pub struct ContentFetcherHandler<H: ContentValidator, F: Fetch> {
fetch_control: FetchControl,
control: Control,
remote: Remote,
status: FetchState,
fetch: F,
installer: Option<H>,
path: EndpointPath,
errors: Errors,
}
impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
pub fn new(
url: String,
path: EndpointPath,
control: Control,
installer: H,
embeddable_on: Option<(String, u16)>,
remote: Remote,
fetch: F,
) -> Self {
ContentFetcherHandler {
fetch_control: FetchControl::default(),
control: control,
remote: remote,
fetch: fetch,
status: FetchState::NotStarted(url),
installer: Some(installer),
path: path,
errors: Errors {
embeddable_on: embeddable_on,
},
}
}
pub fn fetch_control(&self) -> FetchControl {
self.fetch_control.clone()
}
fn fetch_content(&self, uri: RequestUri, url: &str, installer: H) -> mpsc::Receiver<FetchState> {
let (tx, rx) = mpsc::channel();
let abort = self.fetch_control.abort.clone();
let path = self.path.clone();
let tx2 = tx.clone();
let control = self.control.clone();
let errors = self.errors.clone();
let future = self.fetch.fetch_with_abort(url, abort.into()).then(move |result| {
trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result);
let new_state = match result {
Ok(response) => match installer.validate_and_install(response) {
Ok(ValidatorResponse::Local(endpoint)) => {
trace!(target: "dapps", "Validation OK. Returning response.");
let mut handler = endpoint.to_page_handler(path);
handler.set_uri(&uri);
FetchState::Done(endpoint, handler)
},
Ok(ValidatorResponse::Streaming(handler)) => {
trace!(target: "dapps", "Validation OK. Streaming response.");
FetchState::Streaming(handler)
},
Err(e) => {
trace!(target: "dapps", "Error while validating content: {:?}", e);
FetchState::Error(errors.invalid_content(e))
},
},
Err(e) => {
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
FetchState::Error(errors.download_error(e))
},
};
// Content may be resolved when the connection is already dropped.
let _ = tx2.send(new_state);
// Ignoring control errors
let _ = control.ready(Next::read());
Ok(()) as Result<(), ()>
});
// make sure to run within fetch thread pool.
let future = self.fetch.process(future);
// spawn to event loop
let control = self.control.clone();
let errors = self.errors.clone();
self.remote.spawn_with_timeout(|| future, Duration::from_secs(FETCH_TIMEOUT), move || {
// Notify about the timeout
let _ = tx.send(FetchState::Error(errors.timeout_error()));
// Ignoring control errors
let _ = control.ready(Next::read());
});
rx
}
}
impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetcherHandler<H, F> {
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
let status = if let FetchState::NotStarted(ref url) = self.status {
let uri = request.uri().clone();
let installer = self.installer.take().expect("Installer always set initialy; installer used only in on_request; on_request invoked only once; qed");
Some(match *request.method() {
// Start fetching content
Method::Get => {
trace!(target: "dapps", "Fetching content from: {:?}", url);
let receiver = self.fetch_content(uri, url, installer);
FetchState::InProgress(receiver)
},
// or return error
_ => FetchState::Error(self.errors.method_not_allowed()),
})
} else { None };
if let Some(status) = status {
self.fetch_control.set_status(&status);
self.status = status;
}
Next::read()
}
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
let (status, next) = match self.status {
// Request may time out
FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
(Some(FetchState::Error(self.errors.timeout_error())), Next::write())
},
FetchState::InProgress(ref receiver) => {
// Check if there is an answer
let rec = receiver.try_recv();
match rec {
// just return the new state
Ok(state) => (Some(state), Next::write()),
// wait some more
_ => (None, Next::wait())
}
},
FetchState::Error(ref mut handler) => (None, handler.on_request_readable(decoder)),
_ => (None, Next::write()),
};
if let Some(status) = status {
self.fetch_control.set_status(&status);
self.status = status;
}
next
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.status {
FetchState::Done(_, ref mut handler) => handler.on_response(res),
FetchState::Streaming(ref mut handler) => handler.on_response(res),
FetchState::Error(ref mut handler) => handler.on_response(res),
_ => Next::end(),
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.status {
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
_ => Next::end(),
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -17,64 +17,31 @@
//! Hyper handlers implementations.
mod auth;
mod content;
mod echo;
mod fetch;
mod content;
mod redirect;
mod streaming;
pub use self::auth::AuthRequiredHandler;
pub use self::content::ContentHandler;
pub use self::echo::EchoHandler;
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
pub use self::content::ContentHandler;
pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler;
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> {
convert_uri_to_url(req.uri(), req.headers().get::<header::Host>())
}
/// Extracts URL given URI and Host header.
pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) -> Option<Url> {
match *uri {
match *req.uri() {
uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url),
_ => None,
}
},
uri::RequestUri::AbsolutePath { ref path, ref query } => {
let query = match *query {
Some(ref query) => format!("?{}", query),
None => "".into(),
};
uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match host {
let url_string = match req.headers().get::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}{}", host.hostname, host.port.unwrap_or(80), path, query)
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -20,20 +20,15 @@ use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
#[derive(Clone)]
pub struct Redirection {
to_url: String
to_url: &'static str
}
impl Redirection {
pub fn new(url: &str) -> Self {
Redirection {
to_url: url.to_owned()
}
}
pub fn boxed(url: &str) -> Box<Self> {
Box::new(Self::new(url))
pub fn new(url: &'static str) -> Box<Self> {
Box::new(Redirection {
to_url: url
})
}
}
@@ -47,8 +42,7 @@ impl server::Handler<HttpStream> for Redirection {
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
// Don't use `MovedPermanently` here to prevent browser from caching the redirections.
res.set_status(StatusCode::Found);
res.set_status(StatusCode::MovedPermanently);
res.headers_mut().set(header::Location(self.to_url.to_owned()));
Next::write()
}

View File

@@ -1,101 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Content Stream Response
use std::io::{self, Read};
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::mime::Mime;
use hyper::status::StatusCode;
use handlers::add_security_headers;
const BUFFER_SIZE: usize = 1024;
pub struct StreamingHandler<R: io::Read> {
buffer: [u8; BUFFER_SIZE],
buffer_leftover: usize,
status: StatusCode,
content: io::BufReader<R>,
mimetype: Mime,
safe_to_embed_on: Option<(String, u16)>,
}
impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
StreamingHandler {
buffer: [0; BUFFER_SIZE],
buffer_leftover: 0,
status: status,
content: io::BufReader::new(content),
mimetype: mimetype,
safe_to_embed_on: embeddable_on,
}
}
pub fn set_initial_content(&mut self, content: &str) {
assert_eq!(self.buffer_leftover, 0);
let bytes = content.as_bytes();
self.buffer_leftover = bytes.len();
self.buffer[0..self.buffer_leftover].copy_from_slice(bytes);
}
}
impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.status);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
fn handle_error(e: io::Error) -> Next {
match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end(),
}
}
let write_pos = self.buffer_leftover;
match self.content.read(&mut self.buffer[write_pos..]) {
Err(e) => handle_error(e),
Ok(read) => match encoder.write(&self.buffer[..write_pos + read]) {
Err(e) => handle_error(e),
Ok(0) => Next::end(),
Ok(wrote) => {
self.buffer_leftover = write_pos + read - wrote;
if self.buffer_leftover > 0 {
for i in self.buffer_leftover..write_pos + read {
self.buffer.swap(i, i - self.buffer_leftover);
}
}
Next::write()
},
},
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,44 +15,47 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore Webapplications for Parity
//! ```
//! extern crate jsonrpc_core;
//! extern crate ethcore_dapps;
//!
//! use std::sync::Arc;
//! use jsonrpc_core::IoHandler;
//! use ethcore_dapps::*;
//!
//! struct SayHello;
//! impl MethodCommand for SayHello {
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
//! Ok(Value::String("hello".to_string()))
//! }
//! }
//!
//! fn main() {
//! let io = IoHandler::new();
//! io.add_method("say_hello", SayHello);
//! let _server = Server::start_unsecure_http(
//! &"127.0.0.1:3030".parse().unwrap(),
//! Arc::new(io)
//! );
//! }
//! ```
//!
#![warn(missing_docs)]
#![cfg_attr(feature="nightly", feature(plugin))]
#![cfg_attr(feature="nightly", plugin(clippy))]
extern crate base32;
extern crate 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 jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate mime_guess;
extern crate rustc_serialize;
extern crate ethcore_rpc;
extern crate ethcore_util as util;
extern crate parity_hash_fetch as hash_fetch;
extern crate linked_hash_map;
extern crate fetch;
extern crate parity_dapps_glue as parity_dapps;
extern crate futures;
extern crate parity_reactor;
#[macro_use]
extern crate log;
#[macro_use]
extern crate mime;
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate ethcore_devtools as devtools;
#[cfg(test)]
extern crate env_logger;
extern crate url as url_lib;
extern crate hyper;
extern crate unicase;
extern crate serde;
extern crate serde_json;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate parity_dapps;
extern crate ethcore_rpc;
extern crate ethcore_util;
extern crate mime_guess;
mod endpoint;
mod apps;
@@ -63,181 +66,61 @@ mod rpc;
mod api;
mod proxypac;
mod url;
mod web;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
use ethcore_rpc::{Metadata};
use fetch::{Fetch, Client as FetchClient};
use hash_fetch::urlhint::ContractClient;
use jsonrpc_core::Middleware;
use jsonrpc_core::reactor::RpcHandler;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
use parity_reactor::Remote;
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() }
}
/// Validates Web Proxy tokens
pub trait WebProxyTokens: Send + Sync {
/// Should return true if token is a valid web proxy access token.
fn is_web_proxy_token_valid(&self, token: &str) -> bool;
}
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) }
}
static DAPPS_DOMAIN : &'static str = ".parity";
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
remote: Remote,
fetch: Option<T>,
pub struct ServerBuilder {
dapps_path: String,
handler: Arc<IoHandler>,
}
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<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
pub fn new(dapps_path: String) -> Self {
ServerBuilder {
dapps_path: dapps_path.as_ref().to_owned(),
extra_dapps: vec![],
registrar: registrar,
sync_status: Arc::new(|| false),
web_proxy_tokens: Arc::new(|_| false),
signer_address: None,
allowed_hosts: Some(vec![]),
extra_cors: None,
remote: remote,
fetch: None,
dapps_path: dapps_path,
handler: Arc::new(IoHandler::new())
}
}
}
impl<T: Fetch> ServerBuilder<T> {
/// Set a fetch client to use.
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
extra_dapps: vec![],
registrar: self.registrar,
sync_status: self.sync_status,
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
extra_cors: self.extra_cors,
remote: self.remote,
fetch: Some(fetch),
}
}
/// Change default sync status.
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
self.sync_status = status;
self
}
/// Change default web proxy tokens validator.
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
self.web_proxy_tokens = tokens;
self
}
/// Change default signer port.
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
self.signer_address = signer_address;
self
}
/// Change allowed hosts.
/// `None` - All hosts are allowed
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
pub fn allowed_hosts(mut self, allowed_hosts: Option<Vec<String>>) -> Self {
self.allowed_hosts = allowed_hosts;
self
}
/// Extra cors headers.
/// `None` - no additional CORS URLs
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
self.extra_cors = cors;
self
}
/// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
let fetch = self.fetch_client()?;
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
hosts,
NoAuth,
handler,
self.dapps_path,
self.extra_dapps,
self.signer_address,
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
fetch,
self.handler.clone(),
self.dapps_path.clone(),
)
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, username: &str, password: &str, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
let fetch = self.fetch_client()?;
pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
hosts,
HttpBasicAuth::single_user(username, password),
handler,
self.dapps_path,
self.extra_dapps,
self.signer_address,
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
fetch,
self.handler.clone(),
self.dapps_path.clone(),
)
}
fn fetch_client(&self) -> Result<T, ServerError> {
match self.fetch.clone() {
Some(fetch) => Ok(fetch),
None => T::new().map_err(|_| ServerError::FetchInitialization),
}
}
}
/// Webapps HTTP server.
@@ -262,77 +145,28 @@ impl Server {
Some(allowed)
}
/// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
let basic_cors = match signer_address {
Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("http://{}", address(&signer_address)),
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)),
],
None => vec![],
};
match extra_cors {
None => basic_cors,
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
}
}
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
fn start_http<A: Authorization + 'static>(
addr: &SocketAddr,
hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
authorization: A,
handler: RpcHandler<Metadata, T>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
handler: Arc<IoHandler>,
dapps_path: String,
) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status,
signer_address.clone(),
remote.clone(),
fetch.clone(),
));
let endpoints = Arc::new(apps::all_endpoints(
dapps_path,
extra_dapps,
signer_address.clone(),
web_proxy_tokens,
remote.clone(),
fetch.clone(),
));
let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
let endpoints = Arc::new(apps::all_endpoints(dapps_path));
let special = Arc::new({
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, cors_domains.clone(), panic_handler.clone()));
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
special.insert(router::SpecialEndpoint::Api, api::RestApi::new(format!("{}", addr), endpoints.clone()));
special.insert(router::SpecialEndpoint::Utils, apps::utils());
special.insert(
router::SpecialEndpoint::Api,
api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone())
);
special
});
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
hyper::Server::http(addr)?
.handle(move |ctrl| router::Router::new(
ctrl,
signer_address.clone(),
content_fetcher.clone(),
try!(hyper::Server::http(addr))
.handle(move |_| router::Router::new(
apps::main_page(),
endpoints.clone(),
special.clone(),
authorization.clone(),
@@ -356,16 +190,6 @@ impl Server {
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
}
#[cfg(test)]
/// Returns address that this server is bound to.
pub fn addr(&self) -> &SocketAddr {
self.server.as_ref()
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
.addrs()
.first()
.expect("You cannot start the server without binding to at least one address; qed")
}
}
impl Drop for Server {
@@ -381,8 +205,6 @@ pub enum ServerError {
IoError(std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
/// Fetch service initialization error
FetchInitialization,
}
impl From<hyper::error::Error> for ServerError {
@@ -394,19 +216,8 @@ impl From<hyper::error::Error> for ServerError {
}
}
/// Random filename
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 {
mod tests {
use super::Server;
#[test]
@@ -424,26 +235,4 @@ mod util_tests {
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, None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
// then
assert_eq!(none, Vec::<String>::new());
assert_eq!(some, vec![
"http://parity.web3.site".to_owned(),
"http://parity.web3.site:18180".into(),
"http://127.0.0.1:18180".into(),
"https://parity.web3.site".into(),
"https://parity.web3.site:18180".into(),
"https://127.0.0.1:18180".into()
]);
assert_eq!(extra, vec!["all".to_owned()]);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use page::{handler, PageCache};
use page::handler;
use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info};
@@ -25,7 +25,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Option<(String, u16)>,
safe_to_embed: bool,
info: EndpointInfo,
}
@@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_on: None,
safe_to_embed: false,
info: EndpointInfo::from(info),
}
}
@@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint {
app: Arc::new(app),
prefix: Some(prefix),
safe_to_embed_on: None,
safe_to_embed: false,
info: EndpointInfo::from(info),
}
}
@@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking).
/// Use wisely!
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
pub fn new_safe_to_embed(app: T) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_on: address,
safe_to_embed: true,
info: EndpointInfo::from(info),
}
}
@@ -79,9 +79,8 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
app: BuiltinDapp::new(self.app.clone()),
prefix: self.prefix.clone(),
path: path,
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
cache: PageCache::Disabled,
safe_to_embed_on: self.safe_to_embed_on.clone(),
file: None,
safe_to_embed: self.safe_to_embed,
})
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,8 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use time::{self, Duration};
use std::io::Write;
use hyper::header;
use hyper::server;
use hyper::uri::RequestUri;
@@ -23,7 +22,6 @@ 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.
@@ -50,76 +48,20 @@ pub trait Dapp: Send + 'static {
fn file(&self, path: &str) -> Option<Self::DappFile>;
}
/// Currently served by `PageHandler` file
pub enum ServedFile<T: Dapp> {
/// File from dapp
File(T::DappFile),
/// Error (404)
Error(ContentHandler),
}
impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: 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 generic type for `PageHandler` allowing to set the URL.
/// Used by dapps fetching to set the URL after the content was downloaded.
pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send {
fn set_uri(&mut self, uri: &RequestUri);
}
/// A handler for a single webapp.
/// Resolves correct paths and serves as a plumbing code between
/// hyper server and dapp.
pub struct PageHandler<T: Dapp> {
/// A Dapp.
pub app: T,
/// File currently being served
pub file: ServedFile<T>,
/// File currently being served (or `None` if file does not exist).
pub file: Option<T::DappFile>,
/// Optional prefix to strip from path.
pub prefix: Option<String>,
/// Requested path.
pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Option<(String, u16)>,
/// Cache settings for this page.
pub cache: PageCache,
}
impl<T: Dapp> PageHandlerWaiting for PageHandler<T> {
fn set_uri(&mut self, uri: &RequestUri) {
trace!(target: "dapps", "Setting URI: {:?}", uri);
self.file = match *uri {
RequestUri::AbsolutePath { ref path, .. } => {
self.app.file(&self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
self.app.file(&self.extract_path(url.path()))
},
_ => None,
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
}
pub safe_to_embed: bool,
}
impl<T: Dapp> PageHandler<T> {
@@ -145,7 +87,15 @@ impl<T: Dapp> PageHandler<T> {
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
self.set_uri(req.uri());
self.file = match *req.uri() {
RequestUri::AbsolutePath(ref path) => {
self.app.file(&self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
self.app.file(&self.extract_path(url.path()))
},
_ => None,
};
Next::write()
}
@@ -154,40 +104,24 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.file {
ServedFile::File(ref f) => {
res.set_status(StatusCode::Ok);
if let PageCache::Enabled = self.cache {
let mut headers = res.headers_mut();
let validity = Duration::days(365);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
]));
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
}
match f.content_type().parse() {
Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
}
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
Next::write()
},
ServedFile::Error(ref mut handler) => {
handler.on_response(res)
if let Some(ref f) = self.file {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
if !self.safe_to_embed {
res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
}
Next::write()
} else {
res.set_status(StatusCode::NotFound);
Next::write()
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.file {
ServedFile::Error(ref mut handler) => handler.on_response_writable(encoder),
ServedFile::File(ref f) if f.is_drained() => Next::end(),
ServedFile::File(ref mut f) => match encoder.write(f.next_chunk()) {
None => Next::end(),
Some(ref f) if f.is_drained() => Next::end(),
Some(ref mut f) => match encoder.write(f.next_chunk()) {
Ok(bytes) => {
f.bytes_written(bytes);
Next::write()
@@ -252,14 +186,11 @@ fn should_extract_path_with_appid() {
prefix: None,
path: EndpointPath {
app_id: "app".to_owned(),
app_params: vec![],
host: "".to_owned(),
port: 8080,
using_dapps_domains: true,
port: 8080
},
file: ServedFile::new(None),
cache: Default::default(),
safe_to_embed_on: None,
file: None,
safe_to_embed: true,
};
// when

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -17,100 +17,37 @@
use mime_guess;
use std::io::{Seek, Read, SeekFrom};
use std::fs;
use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting};
use std::path::PathBuf;
use page::handler;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime;
#[derive(Debug, Clone)]
pub struct LocalPageEndpoint {
path: PathBuf,
mime: Option<Mime>,
info: Option<EndpointInfo>,
cache: PageCache,
embeddable_on: Option<(String, u16)>,
info: EndpointInfo,
}
impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
pub fn new(path: PathBuf, info: EndpointInfo) -> Self {
LocalPageEndpoint {
path: path,
mime: None,
info: Some(info),
cache: cache,
embeddable_on: embeddable_on,
}
}
pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self {
LocalPageEndpoint {
path: path,
mime: Some(mime),
info: None,
cache: cache,
embeddable_on: None,
}
}
pub fn path(&self) -> PathBuf {
self.path.clone()
}
fn page_handler_with_mime(&self, path: EndpointPath, mime: &Mime) -> handler::PageHandler<LocalSingleFile> {
handler::PageHandler {
app: LocalSingleFile { path: self.path.clone(), mime: format!("{}", mime) },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> {
handler::PageHandler {
app: LocalDapp { path: self.path.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
} else {
Box::new(self.page_handler(path))
info: info,
}
}
}
impl Endpoint for LocalPageEndpoint {
fn info(&self) -> Option<&EndpointInfo> {
self.info.as_ref()
Some(&self.info)
}
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
} else {
Box::new(self.page_handler(path))
}
}
}
struct LocalSingleFile {
path: PathBuf,
mime: String,
}
impl handler::Dapp for LocalSingleFile {
type DappFile = LocalFile;
fn file(&self, _path: &str) -> Option<Self::DappFile> {
LocalFile::from_path(&self.path, Some(&self.mime))
Box::new(handler::PageHandler {
app: LocalDapp::new(self.path.clone()),
prefix: None,
path: path,
file: None,
safe_to_embed: false,
})
}
}
@@ -118,6 +55,14 @@ struct LocalDapp {
path: PathBuf,
}
impl LocalDapp {
fn new(path: PathBuf) -> Self {
LocalDapp {
path: path
}
}
}
impl handler::Dapp for LocalDapp {
type DappFile = LocalFile;
@@ -126,7 +71,18 @@ impl handler::Dapp for LocalDapp {
for part in file_path.split('/') {
path.push(part);
}
LocalFile::from_path(&path, None)
// Check if file exists
fs::File::open(path.clone()).ok().map(|file| {
let content_type = mime_guess::guess_mime_type(path);
let len = file.metadata().ok().map_or(0, |meta| meta.len());
LocalFile {
content_type: content_type.to_string(),
buffer: [0; 4096],
file: file,
pos: 0,
len: len,
}
})
}
}
@@ -138,24 +94,6 @@ struct LocalFile {
pos: u64,
}
impl LocalFile {
fn from_path<P: AsRef<Path>>(path: P, mime: Option<&str>) -> Option<Self> {
// Check if file exists
fs::File::open(&path).ok().map(|file| {
let content_type = mime.map(|mime| mime.to_owned())
.unwrap_or_else(|| mime_guess::guess_mime_type(path).to_string());
let len = file.metadata().ok().map_or(0, |meta| meta.len());
LocalFile {
content_type: content_type,
buffer: [0; 4096],
file: file,
pos: 0,
len: len,
}
})
}
}
impl handler::DappFile for LocalFile {
fn content_type(&self) -> &str {
&self.content_type

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -21,5 +21,4 @@ mod handler;
pub use self::local::LocalPageEndpoint;
pub use self::builtin::PageEndpoint;
pub use self::handler::{PageCache, PageHandlerWaiting};

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -18,47 +18,31 @@
use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler;
use apps::{HOME_PAGE, DAPPS_DOMAIN};
use address;
use apps::DAPPS_DOMAIN;
pub struct ProxyPac {
signer_address: Option<(String, u16)>,
}
pub struct ProxyPac;
impl ProxyPac {
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
Box::new(ProxyPac {
signer_address: signer_address
})
pub fn boxed() -> Box<Endpoint> {
Box::new(ProxyPac)
}
}
impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_address
.as_ref()
.map(address)
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!(
r#"
function FindProxyForURL(url, host) {{
if (shExpMatch(host, "{0}{1}"))
if (shExpMatch(host, "*{0}"))
{{
return "PROXY {4}";
}}
if (shExpMatch(host, "*{1}"))
{{
return "PROXY {2}:{3}";
return "PROXY {1}:{2}";
}}
return "DIRECT";
}}
"#,
HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer);
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
DAPPS_DOMAIN, path.host, path.port);
Box::new(ContentHandler::ok(content, "application/javascript".to_owned()))
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -55,12 +55,10 @@ impl Authorization for HttpBasicAuth {
match auth {
Access::Denied => {
Authorized::No(Box::new(ContentHandler::error(
Authorized::No(Box::new(ContentHandler::new(
status::StatusCode::Unauthorized,
"Unauthorized",
"You need to provide valid credentials to access this page.",
None,
None,
"<h1>Unauthorized</h1>".into(),
"text/html".into(),
)))
},
Access::AuthRequired => {

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,8 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use apps::DAPPS_DOMAIN;
use hyper::{server, header, StatusCode};
use DAPPS_DOMAIN;
use hyper::server;
use hyper::net::HttpStream;
use jsonrpc_http_server::{is_host_header_valid};
@@ -28,20 +28,15 @@ pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String],
.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,
}
is_host_header_valid(request, &endpoints)
}
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,
Box::new(ContentHandler::forbidden(
r#"
<h1>Request with disallowed <code>Host</code> header has been blocked.</h1>
<p>Check the URL in your browser address bar.</p>
"#.into(),
"text/html".into()
))
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -20,17 +20,15 @@
pub mod auth;
mod host_validation;
use address;
use std::cmp;
use DAPPS_DOMAIN;
use std::sync::Arc;
use std::collections::HashMap;
use url::{Url, Host};
use hyper::{self, server, header, Next, Encoder, Decoder, Control, StatusCode};
use hyper::{self, server, Next, Encoder, Decoder};
use hyper::net::HttpStream;
use apps::{self, DAPPS_DOMAIN};
use apps::fetcher::Fetcher;
use apps;
use endpoint::{Endpoint, Endpoints, EndpointPath};
use handlers::{self, Redirection, ContentHandler};
use handlers::{Redirection, extract_url};
use self::auth::{Authorization, Authorized};
/// Special endpoints are accessible on every domain (every dapp)
@@ -43,10 +41,8 @@ pub enum SpecialEndpoint {
}
pub struct Router<A: Authorization + 'static> {
control: Option<Control>,
signer_address: Option<(String, u16)>,
main_page: &'static str,
endpoints: Arc<Endpoints>,
fetch: Arc<Fetcher>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>,
allowed_hosts: Option<Vec<String>>,
@@ -56,108 +52,41 @@ pub struct Router<A: Authorization + 'static> {
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
// Choose proper handler depending on path / domain
let url = handlers::extract_url(&req);
let endpoint = extract_endpoint(&url);
let referer = extract_referer_endpoint(&req);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
let is_get_request = *req.method() == hyper::Method::Get;
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
// Validate Host header
if let Some(ref hosts) = self.allowed_hosts {
trace!(target: "dapps", "Validating host headers against: {:?}", hosts);
let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect());
if !is_valid {
debug!(target: "dapps", "Rejecting invalid host header.");
if !host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect()) {
self.handler = host_validation::host_invalid_response();
return self.handler.on_request(req);
}
}
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);
}
// Choose proper handler depending on path / domain
let url = extract_url(&req);
let endpoint = extract_endpoint(&url);
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
self.handler = match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
if referer.app_id == apps::WEB_PATH
&& self.endpoints.contains_key(apps::WEB_PATH)
&& !is_web_endpoint(path)
=>
{
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
let base = referer_url.path[..len].join("/");
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
Redirection::boxed(&format!("/{}/{}", base, requested))
},
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)
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
},
// Then delegate to dapp
(Some(ref path), _, _) if self.endpoints.contains_key(&path.app_id) => {
trace!(target: "dapps", "Resolving to local/builtin dapp.");
self.endpoints.get(&path.app_id)
.expect("endpoints known to contain key; qed")
.to_async_handler(path.clone(), control)
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
},
// Try to resolve and fetch the dapp
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
trace!(target: "dapps", "Resolving to fetchable content.");
self.fetch.to_async_handler(path.clone(), control)
},
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached
// (in the past we used 301 instead of 302)
// It should be safe to remove it in (near) future.
//
// 404 for non-existent content
(Some(ref path), _, _) if is_get_request && path.app_id != "home" => {
trace!(target: "dapps", "Resolving to 404.");
Box::new(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Requested content was not found.",
None,
self.signer_address.clone(),
))
},
// Redirect any other GET request to signer.
_ if is_get_request => {
if let Some(ref signer_address) = self.signer_address {
trace!(target: "dapps", "Redirecting to signer interface.");
Redirection::boxed(&format!("http://{}", address(signer_address)))
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
self.signer_address.clone(),
))
}
// Redirection to main page
_ if *req.method() == hyper::method::Method::Get => {
Redirection::new(self.main_page)
},
// 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)
self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
}
};
@@ -183,23 +112,17 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
impl<A: Authorization> Router<A> {
pub fn new(
control: Control,
signer_address: Option<(String, u16)>,
content_fetcher: Arc<Fetcher>,
main_page: &'static str,
endpoints: Arc<Endpoints>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>,
allowed_hosts: Option<Vec<String>>,
) -> Self {
let handler = special.get(&SpecialEndpoint::Utils)
.expect("Utils endpoint always stored; qed")
.to_handler(EndpointPath::default());
let handler = special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default());
Router {
control: Some(control),
signer_address: signer_address,
main_page: main_page,
endpoints: endpoints,
fetch: content_fetcher,
special: special,
authorization: authorization,
allowed_hosts: allowed_hosts,
@@ -208,41 +131,6 @@ impl<A: Authorization> Router<A> {
}
}
fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
match *path {
Some(ref path) if path.app_id == apps::WEB_PATH => true,
_ => false,
}
}
fn extract_referer_endpoint(req: &server::Request<HttpStream>) -> Option<(EndpointPath, Url)> {
let referer = req.headers().get::<header::Referer>();
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
url.and_then(|url| {
let option = Some(url);
extract_url_referer_endpoint(&option).or_else(|| {
extract_endpoint(&option).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
})
})
}
fn extract_url_referer_endpoint(url: &Option<Url>) -> Option<(EndpointPath, Url)> {
let query = url.as_ref().and_then(|url| url.query.as_ref());
match (url, query) {
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
let referer_url = format!("http://{}:{}/{}", url.host, url.port, &query[apps::URL_REFERER.len()..]);
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
let referer_url = Url::parse(&referer_url).ok();
extract_endpoint(&referer_url).0.map(|endpoint| {
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
})
},
_ => None,
}
}
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
fn special_endpoint(url: &Url) -> SpecialEndpoint {
if url.path.len() <= 1 {
@@ -260,30 +148,21 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
match *url {
Some(ref url) => match url.host {
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
let id = &domain[0..(domain.len() - DAPPS_DOMAIN.len())];
let (id, params) = if let Some(split) = id.rfind('.') {
let (params, id) = id.split_at(split);
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
} else {
(id.to_owned(), url.path.clone())
};
let len = domain.len() - DAPPS_DOMAIN.len();
let id = domain[0..len].to_owned();
(Some(EndpointPath {
app_id: id,
app_params: params,
host: domain.clone(),
port: url.port,
using_dapps_domains: true,
}), special_endpoint(url))
},
_ if url.path.len() > 1 => {
let id = url.path[0].to_owned();
let id = url.path[0].clone();
(Some(EndpointPath {
app_id: id,
app_params: url.path[1..].to_vec(),
app_id: id.clone(),
host: format!("{}", url.host),
port: url.port,
using_dapps_domains: false,
}), special_endpoint(url))
},
_ => (None, special_endpoint(url)),
@@ -301,10 +180,8 @@ fn should_extract_endpoint() {
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["index.html".to_owned()],
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::None)
);
@@ -313,57 +190,47 @@ fn should_extract_endpoint() {
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
(Some(EndpointPath {
app_id: "rpc".to_owned(),
app_params: vec!["".to_owned()],
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok()),
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
host: "my.status.web3.site".to_owned(),
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://status.web3.site/test.html").ok()),
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["test.html".to_owned()],
host: "status.web3.site".to_owned(),
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.web3.site/rpc/").ok()),
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
host: "my.status.web3.site".to_owned(),
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.web3.site/api/").ok()),
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "api".into(), "".into()],
host: "my.status.web3.site".to_owned(),
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Api)
);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,69 +15,30 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::{Arc, Mutex};
use hyper;
use ethcore_rpc::{Metadata, Origin};
use jsonrpc_core::Middleware;
use jsonrpc_core::reactor::RpcHandler;
use jsonrpc_http_server::{Rpc, ServerHandler, PanicHandler, AccessControlAllowOrigin, HttpMetaExtractor};
use jsonrpc_core::IoHandler;
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
use endpoint::{Endpoint, EndpointPath, Handler};
pub fn rpc<T: Middleware<Metadata>>(
handler: RpcHandler<Metadata, T>,
cors_domains: Vec<String>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
) -> Box<Endpoint> {
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
Box::new(RpcEndpoint {
handler: handler,
meta_extractor: Arc::new(MetadataExtractor),
panic_handler: panic_handler,
cors_domain: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
cors_domain: Some(vec![AccessControlAllowOrigin::Null]),
// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
allowed_hosts: None,
})
}
struct RpcEndpoint<T: Middleware<Metadata>> {
handler: RpcHandler<Metadata, T>,
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
struct RpcEndpoint {
handler: Arc<IoHandler>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
allowed_hosts: Option<Vec<String>>,
}
impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
impl Endpoint for RpcEndpoint {
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
Box::new(ServerHandler::new(
Rpc::new(self.handler.clone(), self.meta_extractor.clone()),
self.cors_domain.clone(),
self.allowed_hosts.clone(),
panic_handler,
control,
))
}
}
struct MetadataExtractor;
impl HttpMetaExtractor<Metadata> for MetadataExtractor {
fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Metadata {
let dapp_id = request.headers().get::<hyper::header::Origin>()
.map(|origin| format!("{}://{}", origin.scheme, origin.host))
.or_else(|| {
// fallback to custom header, but only if origin is null
request.headers().get_raw("origin")
.and_then(|raw| raw.one())
.and_then(|raw| if raw == "null".as_bytes() {
request.headers().get_raw("x-parity-origin")
.and_then(|raw| raw.one())
.map(|raw| String::from_utf8_lossy(raw).into_owned())
} else {
None
})
});
Metadata {
origin: Origin::Dapps(dapp_id.map(Into::into).unwrap_or_default()),
}
Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone(), self.allowed_hosts.clone(), panic_handler))
}
}

View File

@@ -1,236 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, 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://parity.web3.site\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://parity.web3.site"),
"CORS header for parity.web3.site missing: {:?}",
response.headers
);
}
#[test]
fn should_return_signer_port_cors_headers_for_home_parity_with_https() {
// given
let server = serve();
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: https://parity.web3.site\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: https://parity.web3.site"),
"CORS header for parity.web3.site missing: {:?}",
response.headers
);
}
#[test]
fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
// given
let server = serve();
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://parity.web3.site: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://parity.web3.site:18180"),
"CORS header for parity.web3.site missing: {:?}",
response.headers
);
}
#[test]
fn should_return_extra_cors_headers() {
// given
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://somedomain.io\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
}

View File

@@ -1,80 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_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);
}

View File

@@ -1,518 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use devtools::http_client;
use rustc_serialize::hex::FromHex;
use tests::helpers::{
serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch,
serve_with_registrar_and_fetch, serve_with_registrar_and_fetch_and_threads,
request, assert_security_headers_for_embed,
};
#[test]
fn should_resolve_dapp() {
// given
let (server, registrar) = serve_with_registrar();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_return_503_when_syncing_but_should_make_the_calls() {
// given
let (server, registrar) = serve_with_registrar_and_sync();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 503 Service Unavailable");
assert_eq!(registrar.calls.lock().len(), 4);
assert_security_headers_for_embed(&response.headers);
}
const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000";
const GAVCOIN_ICON: &'static str = "00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e000000000000000000000000000000000000000000000000000000000000007768747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f657468636f72652f646170702d6173736574732f623838653938336162616131613661363334356238643934343863313562313137646462353430652f746f6b656e732f676176636f696e2d36347836342e706e67000000000000000000";
#[test]
fn should_return_502_on_hash_mismatch() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_return_error_for_invalid_dapp_zip() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_return_fetched_dapp_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a".parse().unwrap(),
Ok(gavcoin.clone())
);
fetch.set_response(include_bytes!("../../res/gavcoin.zip"));
// when
let response1 = http_client::request(server.addr(),
"\
GET /index.html HTTP/1.1\r\n\
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
let response2 = http_client::request(server.addr(),
"\
GET /manifest.json HTTP/1.1\r\n\
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response1.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response1.headers);
assert_eq!(
response1.body,
r#"18
<h1>Hello Gavcoin!</h1>
"#
);
response2.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response2.headers);
assert_eq!(
response2.body,
r#"BE
{
"id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a",
"name": "Gavcoin",
"description": "Gavcoin",
"version": "1.0.0",
"author": "",
"iconUrl": "icon.png"
}
0
"#
);
}
#[test]
fn should_return_fetched_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
response.assert_security_headers_present(None);
}
#[test]
fn should_cache_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
let request_str = "\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
";
let response = http_client::request(server.addr(), request_str);
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
// when
let response = http_client::request(server.addr(), request_str);
// then
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
}
#[test]
fn should_not_request_content_twice() {
use std::thread;
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch_and_threads(true);
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
let request_str = "\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
";
let fire_request = || {
let addr = server.addr().to_owned();
let req = request_str.to_owned();
thread::spawn(move || {
http_client::request(&addr, &req)
})
};
let control = fetch.manual();
// when
// Fire two requests at the same time
let r1 = fire_request();
let r2 = fire_request();
// wait for single request in fetch, the second one should go into waiting state.
control.wait_for_requests(1);
control.respond();
let response1 = r1.join().unwrap();
let response2 = r2.join().unwrap();
// then
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response1.assert_status("HTTP/1.1 200 OK");
response2.assert_status("HTTP/1.1 200 OK");
}
#[test]
fn should_encode_and_decode_base32() {
use base32;
let encoded = base32::encode(base32::Alphabet::Crockford, "token+https://parity.io".as_bytes());
assert_eq!("EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY", &encoded);
let data = base32::decode(base32::Alphabet::Crockford, "EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY").unwrap();
assert_eq!("token+https://parity.io", &String::from_utf8(data).unwrap());
}
#[test]
fn should_stream_web_content() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/");
fetch.assert_no_more_requests();
}
#[test]
fn should_support_base32_encoded_web_urls() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET /styles.css?test=123 HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_correctly_handle_long_label_when_splitted() {
// given
let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL");
// when
let response = request(server,
"\
GET /styles.css?test=123 HTTP/1.1\r\n\
Host: f1qprwk775k6am35a5wmpk3e9gnpgx3me1sk.mbsfcdqpwx3jd5h7ax39dxq2wvb5dhqpww3fe9t2wrvfdm.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://contribution.melonport.com/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_support_base32_encoded_web_urls_as_path() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET /web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css?test=123 HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_return_error_on_invalid_token() {
// given
let (server, fetch) = serve_with_fetch("test");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_return_error_on_invalid_protocol() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET /web/token/ftp/parity.io/ HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_disallow_non_get_requests() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
POST / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Content-Type: application/json\r\n\
Connection: close\r\n\
\r\n\
123\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 405 Method Not Allowed");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_fix_absolute_requests_based_on_referer() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET /styles.css HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
Referer: http://localhost:8080/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
fetch.assert_no_more_requests();
}
#[test]
fn should_fix_absolute_requests_based_on_referer_in_url() {
// given
let (server, fetch) = serve_with_fetch("token");
// when
let response = request(server,
"\
GET /styles.css HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
Referer: http://localhost:8080/?__referer=web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
fetch.assert_no_more_requests();
}

View File

@@ -1,122 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::{io, thread, time};
use std::sync::{atomic, mpsc, Arc};
use util::Mutex;
use futures::{self, Future};
use fetch::{self, Fetch};
pub struct FetchControl {
sender: mpsc::Sender<()>,
fetch: FakeFetch,
}
impl FetchControl {
pub fn respond(self) {
self.sender.send(())
.expect("Fetch cannot be finished without sending a response at least once.");
}
pub fn wait_for_requests(&self, len: usize) {
const MAX_TIMEOUT_MS: u64 = 5000;
const ATTEMPTS: u64 = 10;
let mut attempts_left = ATTEMPTS;
loop {
let current = self.fetch.requested.lock().len();
if current == len {
break;
} else if attempts_left == 0 {
panic!(
"Timeout reached when waiting for pending requests. Expected: {}, current: {}",
len, current
);
} else {
attempts_left -= 1;
// Should we handle spurious timeouts better?
thread::park_timeout(time::Duration::from_millis(MAX_TIMEOUT_MS / ATTEMPTS));
}
}
}
}
#[derive(Clone, Default)]
pub struct FakeFetch {
manual: Arc<Mutex<Option<mpsc::Receiver<()>>>>,
response: Arc<Mutex<Option<&'static [u8]>>>,
asserted: Arc<atomic::AtomicUsize>,
requested: Arc<Mutex<Vec<String>>>,
}
impl FakeFetch {
pub fn set_response(&self, data: &'static [u8]) {
*self.response.lock() = Some(data);
}
pub fn manual(&self) -> FetchControl {
assert!(self.manual.lock().is_none(), "Only one manual control may be active.");
let (tx, rx) = mpsc::channel();
*self.manual.lock() = Some(rx);
FetchControl {
sender: tx,
fetch: self.clone(),
}
}
pub fn assert_requested(&self, url: &str) {
let requests = self.requested.lock();
let idx = self.asserted.fetch_add(1, atomic::Ordering::SeqCst);
assert_eq!(requests.get(idx), Some(&url.to_owned()), "Expected fetch from specific URL.");
}
pub fn assert_no_more_requests(&self) {
let requests = self.requested.lock();
let len = self.asserted.load(atomic::Ordering::SeqCst);
assert_eq!(requests.len(), len, "Didn't expect any more requests, got: {:?}", &requests[len..]);
}
}
impl Fetch for FakeFetch {
type Result = futures::BoxFuture<fetch::Response, fetch::Error>;
fn new() -> Result<Self, fetch::Error> where Self: Sized {
Ok(FakeFetch::default())
}
fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result {
self.requested.lock().push(url.into());
let manual = self.manual.clone();
let response = self.response.clone();
let (tx, rx) = futures::oneshot();
thread::spawn(move || {
if let Some(rx) = manual.lock().take() {
// wait for manual resume
let _ = rx.recv();
}
let data = response.lock().take().unwrap_or(b"Some content");
let cursor = io::Cursor::new(data);
tx.complete(fetch::Response::from_reader(cursor));
});
rx.map_err(|_| fetch::Error::Aborted).boxed()
}
}

View File

@@ -1,168 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::str;
use std::ops::Deref;
use std::sync::Arc;
use env_logger::LogBuilder;
use ethcore_rpc::Metadata;
use jsonrpc_core::MetaIoHandler;
use jsonrpc_core::reactor::RpcEventLoop;
use ServerBuilder;
use Server;
use fetch::Fetch;
use devtools::http_client;
use parity_reactor::Remote;
mod registrar;
mod fetch;
use self::registrar::FakeRegistrar;
use self::fetch::FakeFetch;
const SIGNER_PORT: u16 = 18180;
fn init_logger() {
// Initialize logger
if let Ok(log) = env::var("RUST_LOG") {
let mut builder = LogBuilder::new();
builder.parse(&log);
let _ = builder.init(); // ignore errors since ./test.sh will call this multiple times.
}
}
pub struct ServerLoop {
pub server: Server,
pub event_loop: RpcEventLoop,
}
impl Deref for ServerLoop {
type Target = Server;
fn deref(&self) -> &Self::Target {
&self.server
}
}
pub fn init_server<F, B>(process: F, io: MetaIoHandler<Metadata>, remote: Remote) -> (ServerLoop, Arc<FakeRegistrar>) where
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
B: Fetch,
{
init_logger();
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
// TODO [ToDr] When https://github.com/ethcore/jsonrpc/issues/26 is resolved
// this additional EventLoop wouldn't be needed, we should be able to re-use remote.
let event_loop = RpcEventLoop::spawn();
let handler = event_loop.handler(Arc::new(io));
let server = process(ServerBuilder::new(
&dapps_path, registrar.clone(), remote,
))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), handler).unwrap();
(
ServerLoop { server: server, event_loop: event_loop },
registrar,
)
}
pub fn serve_with_auth(user: &str, pass: &str) -> ServerLoop {
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 event_loop = RpcEventLoop::spawn();
let handler = event_loop.handler(Arc::new(MetaIoHandler::default()));
let server = ServerBuilder::new(&dapps_path, registrar, Remote::new(event_loop.remote()))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.allowed_hosts(None)
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), user, pass, handler).unwrap();
ServerLoop {
server: server,
event_loop: event_loop,
}
}
pub fn serve_with_rpc(io: MetaIoHandler<Metadata>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None), io, Remote::new_sync()).0
}
pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
}
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
}
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
}
pub fn serve_with_registrar_and_sync() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| {
builder
.sync_status(Arc::new(|| true))
.allowed_hosts(None)
}, Default::default(), Remote::new_sync())
}
pub fn serve_with_registrar_and_fetch() -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
serve_with_registrar_and_fetch_and_threads(false)
}
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (ServerLoop, FakeFetch, Arc<FakeRegistrar>) {
let fetch = FakeFetch::default();
let f = fetch.clone();
let (server, reg) = init_server(move |builder| {
builder.allowed_hosts(None).fetch(f.clone())
}, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
(server, fetch, reg)
}
pub fn serve_with_fetch(web_token: &'static str) -> (ServerLoop, FakeFetch) {
let fetch = FakeFetch::default();
let f = fetch.clone();
let (server, _) = init_server(move |builder| {
builder
.allowed_hosts(None)
.fetch(f.clone())
.web_proxy_tokens(Arc::new(move |token| &token == web_token))
}, Default::default(), Remote::new_sync());
(server, fetch)
}
pub fn serve() -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()).0
}
pub fn request(server: ServerLoop, 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))
}

View File

@@ -1,72 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::str;
use std::sync::Arc;
use std::collections::HashMap;
use rustc_serialize::hex::FromHex;
use hash_fetch::urlhint::ContractClient;
use util::{Bytes, Address, Mutex, H256, ToPretty};
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
const URLHINT_RESOLVE: &'static str = "267b6922";
const DEFAULT_HASH: &'static str = "1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d";
pub struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<HashMap<(String, String), Result<Bytes, String>>>,
}
impl FakeRegistrar {
pub fn new() -> Self {
FakeRegistrar {
calls: Arc::new(Mutex::new(Vec::new())),
responses: Mutex::new({
let mut map = HashMap::new();
map.insert(
(REGISTRAR.into(), "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".into()),
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
);
map.insert(
(URLHINT.into(), format!("{}{}", URLHINT_RESOLVE, DEFAULT_HASH)),
Ok(vec![])
);
map
}),
}
}
pub fn set_result(&self, hash: H256, result: Result<Bytes, String>) {
self.responses.lock().insert(
(URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)),
result
);
}
}
impl ContractClient for FakeRegistrar {
fn registrar(&self) -> Result<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
let call = (address.to_hex(), data.to_hex());
self.calls.lock().push(call.clone());
self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call))
}
}

View File

@@ -1,207 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, request, assert_security_headers, assert_security_headers_for_embed};
#[test]
fn should_redirect_to_home() {
// given
let server = serve();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
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.web3.site\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, "DD\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"parity.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
assert_security_headers(&response.headers);
}
#[test]
fn should_serve_utils() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /parity-utils/inject.js HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body.contains("function(){"), true);
assert_security_headers(&response.headers);
}

View File

@@ -1,119 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use futures::{future, Future};
use ethcore_rpc::{Metadata, Origin};
use jsonrpc_core::{MetaIoHandler, Value};
use tests::helpers::{serve_with_rpc, request};
#[test]
fn should_serve_rpc() {
// given
let mut io = MetaIoHandler::default();
io.add_method("rpc_test", |_| {
Ok(Value::String("Hello World!".into()))
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}
#[test]
fn should_extract_metadata() {
// given
let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into()));
assert_eq!(meta.dapp_id(), "https://parity.io/".into());
future::ok(Value::String("Hello World!".into())).boxed()
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Origin: https://parity.io/\r\n\
X-Parity-Origin: https://this.should.be.ignored\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}
#[test]
fn should_extract_metadata_from_custom_header() {
// given
let mut io = MetaIoHandler::default();
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
assert_eq!(meta.origin, Origin::Dapps("https://parity.io/".into()));
assert_eq!(meta.dapp_id(), "https://parity.io/".into());
future::ok(Value::String("Hello World!".into())).boxed()
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Origin: null\r\n\
X-Parity-Origin: https://parity.io/\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}

View File

@@ -1,127 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_hosts, request};
#[test]
fn should_reject_invalid_host() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
assert!(response.body.contains("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.web3.site\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[test]
// NOTE [todr] This is required for error pages to be styled properly.
fn should_allow_parity_utils_even_on_invalid_domain() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
GET /parity-utils/styles.css HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[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
);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -37,9 +37,6 @@ pub struct Url {
/// Empty entries of `""` correspond to trailing slashes.
pub path: Vec<String>,
/// The URL query.
pub query: Option<String>,
/// The URL username field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
@@ -85,17 +82,15 @@ impl Url {
_ => None,
};
let port = raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))?;
let host = raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())?.to_owned();
let path = raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())?
let port = try!(raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme())));
let host = try!(raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())).to_owned();
let path = try!(raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned()))
.map(|part| part.to_owned()).collect();
let query = raw_url.query().map(|x| x.to_owned());
Ok(Url {
port: port,
host: host,
path: path,
query: query,
raw: raw_url,
username: username,
password: password,

View File

@@ -1,236 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Serving web-based content (proxying)
use std::sync::Arc;
use fetch::{self, Fetch};
use parity_reactor::Remote;
use base32;
use hyper::{self, server, net, Next, Encoder, Decoder};
use hyper::status::StatusCode;
use apps;
use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::{
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
StreamingHandler, extract_url,
};
use url::Url;
use WebProxyTokens;
pub type Embeddable = Option<(String, u16)>;
pub struct Web<F> {
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
}
impl<F: Fetch> Web<F> {
pub fn boxed(embeddable_on: Embeddable, web_proxy_tokens: Arc<WebProxyTokens>, remote: Remote, fetch: F) -> Box<Endpoint> {
Box::new(Web {
embeddable_on: embeddable_on,
web_proxy_tokens: web_proxy_tokens,
remote: remote,
fetch: fetch,
})
}
}
impl<F: Fetch> Endpoint for Web<F> {
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
Box::new(WebHandler {
control: control,
state: State::Initial,
path: path,
remote: self.remote.clone(),
fetch: self.fetch.clone(),
web_proxy_tokens: self.web_proxy_tokens.clone(),
embeddable_on: self.embeddable_on.clone(),
})
}
}
struct WebInstaller {
embeddable_on: Embeddable,
referer: String,
}
impl ContentValidator for WebInstaller {
type Error = String;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, String> {
let status = StatusCode::from_u16(response.status().to_u16());
let is_html = response.is_html();
let mime = response.content_type().unwrap_or(mime!(Text/Html));
let mut handler = StreamingHandler::new(
response,
status,
mime,
self.embeddable_on.clone(),
);
if is_html {
handler.set_initial_content(&format!(
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
apps::UTILS_PATH,
apps::URL_REFERER,
apps::WEB_PATH,
&self.referer,
));
}
Ok(ValidatorResponse::Streaming(handler))
}
}
enum State<F: Fetch> {
Initial,
Error(ContentHandler),
Fetching(ContentFetcherHandler<WebInstaller, F>),
}
struct WebHandler<F: Fetch> {
control: hyper::Control,
state: State<F>,
path: EndpointPath,
remote: Remote,
fetch: F,
web_proxy_tokens: Arc<WebProxyTokens>,
embeddable_on: Embeddable,
}
impl<F: Fetch> WebHandler<F> {
fn extract_target_url(&self, url: Option<Url>) -> Result<String, State<F>> {
let token_and_url = self.path.app_params.get(0)
.map(|encoded| encoded.replace('.', ""))
.and_then(|encoded| base32::decode(base32::Alphabet::Crockford, &encoded.to_uppercase()))
.and_then(|data| String::from_utf8(data).ok())
.ok_or_else(|| State::Error(ContentHandler::error(
StatusCode::BadRequest,
"Invalid parameter",
"Couldn't parse given parameter:",
self.path.app_params.get(0).map(String::as_str),
self.embeddable_on.clone()
)))?;
let mut token_it = token_and_url.split('+');
let token = token_it.next();
let target_url = token_it.next();
// Check if token supplied in URL is correct.
match token {
Some(token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
)));
}
}
// Validate protocol
let mut target_url = match target_url {
Some(url) if url.starts_with("http://") || url.starts_with("https://") => url.to_owned(),
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
)));
}
};
if !target_url.ends_with("/") {
target_url = format!("{}/", target_url);
}
// TODO [ToDr] Should just use `path.app_params`
let (path, query) = match (&url, self.path.using_dapps_domains) {
(&Some(ref url), true) => (&url.path[..], &url.query),
(&Some(ref url), false) => (&url.path[2..], &url.query),
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
)));
}
};
let query = match *query {
Some(ref query) => format!("?{}", query),
None => "".into(),
};
Ok(format!("{}{}{}", target_url, path.join("/"), query))
}
}
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
let url = extract_url(&request);
// First extract the URL (reject invalid URLs)
let target_url = match self.extract_target_url(url) {
Ok(url) => url,
Err(error) => {
self.state = error;
return Next::write();
}
};
let mut handler = ContentFetcherHandler::new(
target_url,
self.path.clone(),
self.control.clone(),
WebInstaller {
embeddable_on: self.embeddable_on.clone(),
referer: self.path.app_params.get(0)
.expect("`target_url` is valid; app_params is not empty;qed")
.to_owned(),
},
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
let res = handler.on_request(request);
self.state = State::Fetching(handler);
res
}
fn on_request_readable(&mut self, decoder: &mut Decoder<net::HttpStream>) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_request_readable(decoder),
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
}
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_response(res),
State::Fetching(ref mut handler) => handler.on_response(res),
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_response_writable(encoder),
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
}
}
}

View File

@@ -1,18 +0,0 @@
[package]
description = "Ethcore Parity UI"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "parity-ui"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
[build-dependencies]
rustc_version = "0.1"
[dependencies]
parity-ui-dev = { path = "../../js", optional = true }
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
[features]
no-precompiled-js = ["parity-ui-dev"]
use-precompiled-js = ["parity-ui-precompiled"]

View File

@@ -1,21 +1,22 @@
[package]
description = "Ethcore Database"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "ethcore-db"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.3.0"
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
syntex = "*"
ethcore-ipc-codegen = { path = "../ipc/codegen" }
[dependencies]
clippy = { version = "0.0.103", optional = true}
clippy = { version = "0.0.80", optional = true}
ethcore-devtools = { path = "../devtools" }
ethcore-ipc = { path = "../ipc/rpc" }
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
semver = "0.5"
semver = "0.2"
ethcore-ipc-nano = { path = "../ipc/nano" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
crossbeam = "0.2"

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate syntex;
extern crate ethcore_ipc_codegen as codegen;
use std::env;
@@ -26,13 +27,17 @@ pub fn main() {
{
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
codegen::expand(&src, &dst);
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// 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);
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -73,10 +73,10 @@ impl WriteCache {
match *cache_entry {
WriteCacheEntry::Write(ref val) => {
batch.put(&key, val)?;
try!(batch.put(&key, val));
},
WriteCacheEntry::Remove => {
batch.delete(&key)?;
try!(batch.delete(&key));
},
}
key.clone()
@@ -87,14 +87,14 @@ impl WriteCache {
removed_so_far = removed_so_far + 1;
}
if removed_so_far > 0 {
db.write(batch)?;
try!(db.write(batch));
}
Ok(())
}
/// flushes until cache is empty
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
while !self.is_empty() { self.flush(db, FLUSH_BATCH_SIZE)?; }
while !self.is_empty() { try!(self.flush(db, FLUSH_BATCH_SIZE)); }
Ok(())
}
@@ -104,7 +104,7 @@ impl WriteCache {
fn try_shrink(&mut self, db: &DB) -> Result<(), Error> {
if self.entries.len() > self.preferred_len {
self.flush(db, FLUSH_BATCH_SIZE)?;
try!(self.flush(db, FLUSH_BATCH_SIZE));
}
Ok(())
}
@@ -135,7 +135,7 @@ impl Database {
if db_lock.is_none() { return Ok(()); }
let db = db_lock.as_ref().unwrap();
cache_lock.try_shrink(&db)?;
try!(cache_lock.try_shrink(&db));
Ok(())
}
@@ -145,7 +145,7 @@ impl Database {
if db_lock.is_none() { return Ok(()); }
let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step");
cache_lock.flush_all(&db)?;
try!(cache_lock.flush_all(&db));
Ok(())
}
@@ -157,7 +157,7 @@ impl Drop for Database {
}
}
#[ipc]
#[derive(Ipc)]
impl DatabaseService for Database {
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error> {
let mut db = self.db.write();
@@ -174,7 +174,7 @@ impl DatabaseService for Database {
opts.set_block_based_table_factory(&block_opts);
opts.set_prefix_extractor_fixed_size(size);
}
*db = Some(DB::open(&opts, &path)?);
*db = Some(try!(DB::open(&opts, &path)));
Ok(())
}
@@ -185,7 +185,7 @@ impl DatabaseService for Database {
}
fn close(&self) -> Result<(), Error> {
self.flush_all()?;
try!(self.flush_all());
let mut db = self.db.write();
if db.is_none() { return Err(Error::IsClosed); }
@@ -231,9 +231,9 @@ impl DatabaseService for Database {
}
}
let db_lock = self.db.read();
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
match db.get(key)? {
match try!(db.get(key)) {
Some(db_vec) => {
Ok(Some(db_vec.to_vec()))
},
@@ -243,7 +243,7 @@ impl DatabaseService for Database {
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let db_lock = self.db.read();
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward));
match iter.next() {
@@ -255,14 +255,14 @@ impl DatabaseService for Database {
fn is_empty(&self) -> Result<bool, Error> {
let db_lock = self.db.read();
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
Ok(db.iterator(IteratorMode::Start).next().is_none())
}
fn iter(&self) -> Result<IteratorHandle, Error> {
let db_lock = self.db.read();
let db = db_lock.as_ref().ok_or(Error::IsClosed)?;
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
let mut iterators = self.iterators.write();
let next_iterator = iterators.keys().last().unwrap_or(&0) + 1;
@@ -270,7 +270,8 @@ impl DatabaseService for Database {
Ok(next_iterator)
}
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue> {
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue>
{
let mut iterators = self.iterators.write();
let mut iterator = match iterators.get_mut(&handle) {
Some(some_iterator) => some_iterator,
@@ -459,7 +460,7 @@ mod client_tests {
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();
let client = nanoipc::init_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
client.close().unwrap();
@@ -476,7 +477,7 @@ mod client_tests {
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();
let client = nanoipc::init_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
@@ -497,7 +498,7 @@ mod client_tests {
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();
let client = nanoipc::init_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
assert!(client.get("xxx".as_bytes()).unwrap().is_none());
@@ -515,7 +516,7 @@ mod client_tests {
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();
let client = nanoipc::init_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
let transaction = DBTransaction::new();
@@ -540,7 +541,7 @@ mod client_tests {
let stop = StopGuard::new();
run_worker(&scope, stop.share(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
let client = nanoipc::init_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
let mut batch = Vec::new();

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -52,27 +52,27 @@ impl std::convert::From<nanoipc::SocketError> for ServiceError {
pub fn blocks_service_url(db_path: &str) -> Result<String, std::io::Error> {
let mut path = PathBuf::from(db_path);
::std::fs::create_dir_all(db_path)?;
try!(::std::fs::create_dir_all(db_path));
path.push("blocks.ipc");
Ok(format!("ipc://{}", path.to_str().unwrap()))
}
pub fn extras_service_url(db_path: &str) -> Result<String, ::std::io::Error> {
let mut path = PathBuf::from(db_path);
::std::fs::create_dir_all(db_path)?;
try!(::std::fs::create_dir_all(db_path));
path.push("extras.ipc");
Ok(format!("ipc://{}", path.to_str().unwrap()))
}
pub fn blocks_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
let url = blocks_service_url(db_path)?;
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
let url = try!(blocks_service_url(db_path));
let client = try!(nanoipc::init_client::<DatabaseClient<_>>(&url));
Ok(client)
}
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
let url = extras_service_url(db_path)?;
let client = nanoipc::generic_client::<DatabaseClient<_>>(&url)?;
let url = try!(extras_service_url(db_path));
let client = try!(nanoipc::init_client::<DatabaseClient<_>>(&url));
Ok(client)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,6 +16,9 @@
//! Ethcore database trait
use std::mem;
use ipc::binary::BinaryConvertError;
use std::collections::VecDeque;
use std::cell::RefCell;
pub type IteratorHandle = u32;

View File

@@ -1,10 +1,10 @@
[package]
description = "Ethcore development/test/build tools"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "ethcore-devtools"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.3.0"
authors = ["Ethcore <admin@ethcore.io>"]
[dependencies]
rand = "0.3"

View File

@@ -1,124 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::thread;
use std::time::Duration;
use std::io::{Read, Write};
use std::str::{self, Lines};
use std::net::{TcpStream, SocketAddr};
pub struct Response {
pub status: String,
pub headers: Vec<String>,
pub headers_raw: String,
pub body: String,
}
impl Response {
pub fn assert_header(&self, header: &str, value: &str) {
let header = format!("{}: {}", header, value);
assert!(self.headers.iter().find(|h| *h == &header).is_some(), "Couldn't find header {} in {:?}", header, &self.headers)
}
pub fn assert_status(&self, status: &str) {
assert_eq!(self.status, status.to_owned(), "Got unexpected code. Body: {:?}", self.body);
}
pub fn assert_security_headers_present(&self, port: Option<u16>) {
assert_security_headers_present(&self.headers, port)
}
}
pub fn read_block(lines: &mut Lines, all: bool) -> String {
let mut block = String::new();
loop {
let line = lines.next();
match line {
None => break,
Some("") if !all => break,
Some(v) => {
block.push_str(v);
block.push_str("\n");
},
}
}
block
}
fn connect(address: &SocketAddr) -> TcpStream {
let mut retries = 0;
let mut last_error = None;
while retries < 10 {
retries += 1;
let res = TcpStream::connect(address);
match res {
Ok(stream) => {
return stream;
},
Err(e) => {
last_error = Some(e);
thread::sleep(Duration::from_millis(retries * 10));
}
}
}
panic!("Unable to connect to the server. Last error: {:?}", last_error);
}
pub fn request(address: &SocketAddr, request: &str) -> Response {
let mut req = connect(address);
req.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
req.write_all(request.as_bytes()).unwrap();
let mut response = String::new();
let _ = req.read_to_string(&mut response);
let mut lines = response.lines();
let status = lines.next().expect("Expected a response").to_owned();
let headers_raw = read_block(&mut lines, false);
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
let body = read_block(&mut lines, true);
Response {
status: status,
headers: headers,
headers_raw: headers_raw,
body: body,
}
}
/// Check if all required security headers are present
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
if let 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
);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -22,7 +22,6 @@ extern crate rand;
mod random_path;
mod test_socket;
mod stop_guard;
pub mod http_client;
pub use random_path::*;
pub use test_socket::*;

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -23,8 +23,7 @@ use std::ops::{Deref, DerefMut};
use rand::random;
pub struct RandomTempPath {
path: PathBuf,
pub panic_on_drop_failure: bool,
path: PathBuf
}
pub fn random_filename() -> String {
@@ -40,8 +39,7 @@ impl RandomTempPath {
let mut dir = env::temp_dir();
dir.push(random_filename());
RandomTempPath {
path: dir.clone(),
panic_on_drop_failure: true,
path: dir.clone()
}
}
@@ -50,8 +48,7 @@ impl RandomTempPath {
dir.push(random_filename());
fs::create_dir_all(dir.as_path()).unwrap();
RandomTempPath {
path: dir.clone(),
panic_on_drop_failure: true,
path: dir.clone()
}
}
@@ -70,26 +67,10 @@ impl RandomTempPath {
}
}
impl AsRef<Path> for RandomTempPath {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl Deref for RandomTempPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.as_path()
}
}
impl Drop for RandomTempPath {
fn drop(&mut self) {
if let Err(_) = fs::remove_dir_all(&self) {
if let Err(e) = fs::remove_file(&self) {
if self.panic_on_drop_failure {
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
}
}
if let Err(e) = fs::remove_dir_all(self.as_path()) {
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -5,7 +5,7 @@ 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&&\
ls&&\
sh rustup.sh -s -- --disable-sudo
# show backtraces
ENV RUST_BACKTRACE 1
@@ -26,8 +26,4 @@ RUN git clone https://github.com/ethcore/parity && \
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"]

View File

@@ -1,83 +0,0 @@
FROM ubuntu:14.04
MAINTAINER Parity Technologies <devops@parity.io>
WORKDIR /build
#ENV for build TAG
ARG BUILD_TAG
ENV BUILD_TAG ${BUILD_TAG:-master}
RUN echo $BUILD_TAG
# install tools and dependencies
RUN apt-get update && \
apt-get install -y --force-yes --no-install-recommends \
# make
build-essential \
# add-apt-repository
software-properties-common \
make \
curl \
wget \
git \
g++ \
gcc \
libc6 \
libc6-dev \
binutils \
file \
openssl \
libssl-dev \
libudev-dev \
pkg-config \
dpkg-dev \
# evmjit dependencies
zlib1g-dev \
libedit-dev \
libudev-dev &&\
# cmake and llvm ppa's. then update ppa's
add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
apt-get update && \
apt-get install -y --force-yes cmake llvm-3.7-dev && \
# install evmjit
git clone https://github.com/debris/evmjit && \
cd evmjit && \
mkdir build && cd build && \
cmake .. && make && make install && cd && \
# install rustup
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
# rustup directory
PATH=/root/.cargo/bin:$PATH && \
# show backtraces
RUST_BACKTRACE=1 && \
# build parity
cd /build&&git clone https://github.com/ethcore/parity && \
cd parity && \
git pull&& \
git checkout $BUILD_TAG && \
cargo build --verbose --release --features final && \
#ls /build/parity/target/release/parity && \
strip /build/parity/target/release/parity && \
file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\
#cleanup Docker image
rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\
apt-get purge -y \
# make
build-essential \
# add-apt-repository
software-properties-common \
make \
curl \
wget \
git \
g++ \
gcc \
binutils \
file \
pkg-config \
dpkg-dev \
# evmjit dependencies
zlib1g-dev \
libedit-dev \
cmake llvm-3.7-dev&&\
rm -rf /var/lib/apt/lists/*
# setup ENTRYPOINT
EXPOSE 8080 8545 8180
ENTRYPOINT ["/parity/parity"]

View File

@@ -40,8 +40,4 @@ RUN git clone https://github.com/ethcore/parity && \
cargo build --target aarch64-unknown-linux-gnu --release --verbose && \
ls /build/parity/target/aarch64-unknown-linux-gnu/release/parity && \
/usr/bin/aarch64-linux-gnu-strip /build/parity/target/aarch64-unknown-linux-gnu/release/parity
RUN file /build/parity/target/aarch64-unknown-linux-gnu/release/parity
EXPOSE 8080 8545 8180
ENTRYPOINT ["/build/parity/target/aarch64-unknown-linux-gnu/release/parity"]

View File

@@ -23,10 +23,9 @@ RUN rustup target add armv7-unknown-linux-gnueabihf
# show backtraces
ENV RUST_BACKTRACE 1
# show tools
RUN rustc -vV && \
cargo -V
cargo -V
# build parity
RUN git clone https://github.com/ethcore/parity && \
@@ -41,8 +40,4 @@ RUN git clone https://github.com/ethcore/parity && \
cargo build --target armv7-unknown-linux-gnueabihf --release --verbose && \
ls /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity && \
/usr/bin/arm-linux-gnueabihf-strip /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
RUN file /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
EXPOSE 8080 8545 8180
ENTRYPOINT ["/build/parity/target/armv7-unknown-linux-gnueabihf/release/parity"]

View File

@@ -52,8 +52,4 @@ RUN git clone https://github.com/ethcore/parity && \
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"]

View File

@@ -1,40 +0,0 @@
FROM ubuntu:14.04
WORKDIR /build
# install tools and dependencies
RUN apt-get update && \
apt-get install -y \
build-essential \
g++ \
curl \
git \
file \
binutils
# install rustup
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
# rustup directory
ENV PATH /root/.cargo/bin:$PATH
# show backtraces
ENV RUST_BACKTRACE 1
# show tools
RUN rustc -vV && \
cargo -V && \
gcc -v &&\
g++ -v
# build parity
RUN git clone https://github.com/ethcore/parity && \
cd parity && \
git checkout stable && \
git pull && \
cargo build --release --verbose && \
ls /build/parity/target/release/parity && \
strip /build/parity/target/release/parity
RUN file /build/parity/target/release/parity
EXPOSE 8080 8545 8180
ENTRYPOINT ["/build/parity/target/release/parity"]

View File

@@ -4,7 +4,6 @@ WORKDIR /build
RUN apt-get update && \
apt-get install -y \
g++ \
build-essential \
curl \
git \
file \
@@ -33,8 +32,5 @@ RUN git clone https://github.com/ethcore/parity && \
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"]

View File

@@ -1,7 +1,7 @@
[package]
name = "ethash"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.3.0"
authors = ["arkpar <arkadiy@ethcore.io"]
[lib]
@@ -9,4 +9,4 @@ authors = ["Parity Technologies <admin@parity.io>"]
log = "0.3"
sha3 = { path = "../util/sha3" }
primal = "0.2.3"
parking_lot = "0.3"
parking_lot = "0.2.6"

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -91,7 +91,7 @@ pub struct Light {
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 {
@@ -116,17 +116,17 @@ impl Light {
pub fn from_file(block_number: u64) -> io::Result<Light> {
let seed_compute = SeedHashCompute::new();
let path = Light::file_path(seed_compute.get_seedhash(block_number));
let mut file = File::open(path)?;
let mut file = try!(File::open(path));
let cache_size = get_cache_size(block_number);
if file.metadata()?.len() != cache_size as u64 {
if try!(file.metadata()).len() != cache_size as u64 {
return Err(io::Error::new(io::ErrorKind::Other, "Cache file size mismatch"));
}
let num_nodes = cache_size / NODE_BYTES;
let mut nodes: Vec<Node> = Vec::new();
nodes.resize(num_nodes, unsafe { mem::uninitialized() });
let buf = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut u8, cache_size) };
file.read_exact(buf)?;
try!(file.read_exact(buf));
Ok(Light {
cache: nodes,
block_number: block_number,
@@ -134,27 +134,16 @@ impl Light {
})
}
pub fn to_file(&self) -> io::Result<PathBuf> {
pub fn to_file(&self) -> io::Result<()> {
let seed_compute = self.seed_compute.lock();
let path = Light::file_path(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);
fs::remove_file(deprecated)?;
}
}
fs::create_dir_all(path.parent().unwrap())?;
let mut file = File::create(&path)?;
try!(fs::create_dir_all(path.parent().unwrap()));
let mut file = try!(File::create(path));
let cache_size = self.cache.len() * NODE_BYTES;
let buf = unsafe { slice::from_raw_parts(self.cache.as_ptr() as *const u8, cache_size) };
file.write(buf)?;
Ok(path)
try!(file.write(buf));
Ok(())
}
}
@@ -202,9 +191,6 @@ impl SeedHashCompute {
}
}
pub fn slow_get_seedhash(block_number: u64) -> H256 {
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
}
#[inline]
fn fnv_hash(x: u32, y: u32) -> u32 {
@@ -469,18 +455,3 @@ fn test_seed_compute_after_newer() {
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
assert_eq!(seed_compute.get_seedhash(486382), hash);
}
#[test]
fn test_drop_old_data() {
let 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());
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -26,7 +26,7 @@ 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::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty};
use std::sync::Arc;
use parking_lot::Mutex;
@@ -69,19 +69,14 @@ impl EthashManager {
Some(ref e) if *e == epoch => lights.recent.clone(),
_ => match lights.prev_epoch.clone() {
Some(e) if e == epoch => {
// don't swap if recent is newer.
if lights.recent_epoch > lights.prev_epoch {
None
} else {
// swap
let t = lights.prev_epoch;
lights.prev_epoch = lights.recent_epoch;
lights.recent_epoch = t;
let t = lights.prev.clone();
lights.prev = lights.recent.clone();
lights.recent = t;
lights.recent.clone()
}
// swap
let t = lights.prev_epoch;
lights.prev_epoch = lights.recent_epoch;
lights.recent_epoch = t;
let t = lights.prev.clone();
lights.prev = lights.recent.clone();
lights.recent = t;
lights.recent.clone()
}
_ => None,
},

View File

@@ -1,10 +1,10 @@
[package]
description = "Ethcore library"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "ethcore"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.3.0"
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
@@ -14,22 +14,18 @@ build = "build.rs"
log = "0.3"
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"
semver = "0.5"
rayon = "0.4"
semver = "0.2"
bit-set = "0.4"
time = "0.1"
rand = "0.3"
byteorder = "1.0"
transient-hashmap = "0.1"
linked-hash-map = "0.3.0"
lru-cache = "0.1.0"
ethabi = "1.0.0"
evmjit = { path = "../evmjit", optional = true }
clippy = { version = "0.0.103", optional = true}
clippy = { version = "0.0.80", optional = true}
ethash = { path = "../ethash" }
ethcore-util = { path = "../util" }
ethcore-io = { path = "../util/io" }
@@ -37,13 +33,11 @@ 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" }
ethcore-stratum = { path = "../stratum" }
ethcore-bloom-journal = { path = "../util/bloom" }
hardware-wallet = { path = "../hw" }
stats = { path = "../util/stats" }
rand = "0.3"
lru-cache = "0.0.7"
bloomfilter = { git = "https://github.com/ethcore/rust-bloom-filter" }
byteorder = "0.5"
[dependencies.hyper]
git = "https://github.com/ethcore/hyper"
@@ -51,14 +45,10 @@ 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"]

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -18,7 +18,6 @@ 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();
ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap();
ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap();
}

View File

@@ -1,30 +0,0 @@
[package]
description = "Parity LES primitives"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "ethcore-light"
version = "1.6.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[build-dependencies]
"ethcore-ipc-codegen" = { path = "../../ipc/codegen", optional = true }
[dependencies]
log = "0.3"
ethcore = { path = ".."}
ethcore-util = { path = "../../util" }
ethcore-network = { path = "../../util/network" }
ethcore-io = { path = "../../util/io" }
ethcore-ipc = { path = "../../ipc/rpc", optional = true }
rlp = { path = "../../util/rlp" }
time = "0.1"
smallvec = "0.3.1"
futures = "0.1"
rand = "0.3"
itertools = "0.5"
stats = { path = "../../util/stats" }
[features]
default = []
ipc = ["ethcore-ipc", "ethcore-ipc-codegen"]

View File

@@ -1,175 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Cache for data fetched from the network.
//!
//! Stores ancient block headers, bodies, receipts, and total difficulties.
//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted
//! vector of all gas prices from a recent range of blocks.
use ethcore::encoded;
use ethcore::header::BlockNumber;
use ethcore::receipt::Receipt;
use stats::Corpus;
use time::{SteadyTime, Duration};
use util::{U256, H256};
use util::cache::MemoryLruCache;
/// Configuration for how much data to cache.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheSizes {
/// Maximum size, in bytes, of cached headers.
pub headers: usize,
/// Maximum size, in bytes, of cached canonical hashes.
pub canon_hashes: usize,
/// Maximum size, in bytes, of cached block bodies.
pub bodies: usize,
/// Maximum size, in bytes, of cached block receipts.
pub receipts: usize,
/// Maximum size, in bytes, of cached chain score for the block.
pub chain_score: usize,
}
impl Default for CacheSizes {
fn default() -> Self {
const MB: usize = 1024 * 1024;
CacheSizes {
headers: 10 * MB,
canon_hashes: 3 * MB,
bodies: 20 * MB,
receipts: 10 * MB,
chain_score: 7 * MB,
}
}
}
/// The light client data cache.
///
/// Note that almost all getter methods take `&mut self` due to the necessity to update
/// the underlying LRU-caches on read.
pub struct Cache {
headers: MemoryLruCache<H256, encoded::Header>,
canon_hashes: MemoryLruCache<BlockNumber, H256>,
bodies: MemoryLruCache<H256, encoded::Body>,
receipts: MemoryLruCache<H256, Vec<Receipt>>,
chain_score: MemoryLruCache<H256, U256>,
corpus: Option<(Corpus<U256>, SteadyTime)>,
corpus_expiration: Duration,
}
impl Cache {
/// Create a new data cache with the given sizes and gas price corpus expiration time.
pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self {
Cache {
headers: MemoryLruCache::new(sizes.headers),
canon_hashes: MemoryLruCache::new(sizes.canon_hashes),
bodies: MemoryLruCache::new(sizes.bodies),
receipts: MemoryLruCache::new(sizes.receipts),
chain_score: MemoryLruCache::new(sizes.chain_score),
corpus: None,
corpus_expiration: corpus_expiration,
}
}
/// Query header by hash.
pub fn block_header(&mut self, hash: &H256) -> Option<encoded::Header> {
self.headers.get_mut(hash).map(|x| x.clone())
}
/// Query hash by number.
pub fn block_hash(&mut self, num: &BlockNumber) -> Option<H256> {
self.canon_hashes.get_mut(num).map(|x| x.clone())
}
/// Query block body by block hash.
pub fn block_body(&mut self, hash: &H256) -> Option<encoded::Body> {
self.bodies.get_mut(hash).map(|x| x.clone())
}
/// Query block receipts by block hash.
pub fn block_receipts(&mut self, hash: &H256) -> Option<Vec<Receipt>> {
self.receipts.get_mut(hash).map(|x| x.clone())
}
/// Query chain score by block hash.
pub fn chain_score(&mut self, hash: &H256) -> Option<U256> {
self.chain_score.get_mut(hash).map(|x| x.clone())
}
/// Cache the given header.
pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) {
self.headers.insert(hash, hdr);
}
/// Cache the given canonical block hash.
pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) {
self.canon_hashes.insert(num, hash);
}
/// Cache the given block body.
pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) {
self.bodies.insert(hash, body);
}
/// Cache the given block receipts.
pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec<Receipt>) {
self.receipts.insert(hash, receipts);
}
/// Cache the given chain scoring.
pub fn insert_chain_score(&mut self, hash: H256, score: U256) {
self.chain_score.insert(hash, score);
}
/// Get gas price corpus, if recent enough.
pub fn gas_price_corpus(&self) -> Option<Corpus<U256>> {
let now = SteadyTime::now();
self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| {
if *tm + self.corpus_expiration >= now {
Some(corpus.clone())
} else {
None
}
})
}
/// Set the cached gas price corpus.
pub fn set_gas_price_corpus(&mut self, corpus: Corpus<U256>) {
self.corpus = Some((corpus, SteadyTime::now()))
}
}
#[cfg(test)]
mod tests {
use super::Cache;
use time::Duration;
#[test]
fn corpus_inaccessible() {
let mut cache = Cache::new(Default::default(), Duration::hours(5));
cache.set_gas_price_corpus(vec![].into());
assert_eq!(cache.gas_price_corpus(), Some(vec![].into()));
{
let corpus_time = &mut cache.corpus.as_mut().unwrap().1;
*corpus_time = *corpus_time - Duration::hours(6);
}
assert!(cache.gas_price_corpus().is_none());
}
}

View File

@@ -1,204 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//! Canonical hash trie definitions and helper functions.
//!
//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty.
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
//! favor the the trie root. When the "ancient" blocks need to be accessed, we simply
//! request an inclusion proof of a specific block number against the trie with the
//! root has. A correct proof implies that the claimed block is identical to the one
//! we discarded.
use ethcore::ids::BlockId;
use util::{Bytes, H256, U256, HashDB, MemoryDB};
use util::trie::{self, TrieMut, TrieDBMut, Trie, TrieDB, Recorder};
use rlp::{Stream, RlpStream, UntrustedRlp, View};
// encode a key.
macro_rules! key {
($num: expr) => { ::rlp::encode(&$num) }
}
macro_rules! val {
($hash: expr, $td: expr) => {{
let mut stream = RlpStream::new_list(2);
stream.append(&$hash).append(&$td);
stream.drain()
}}
}
/// The size of each CHT.
pub const SIZE: u64 = 2048;
/// A canonical hash trie. This is generic over any database it can query.
/// See module docs for more details.
#[derive(Debug, Clone)]
pub struct CHT<DB: HashDB> {
db: DB,
root: H256, // the root of this CHT.
number: u64,
}
impl<DB: HashDB> CHT<DB> {
/// Query the root of the CHT.
pub fn root(&self) -> H256 { self.root }
/// Query the number of the CHT.
pub fn number(&self) -> u64 { self.number }
/// Generate an inclusion proof for the entry at a specific block.
/// Nodes before level `from_level` will be omitted.
/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
pub fn prove(&self, num: u64, from_level: u32) -> trie::Result<Option<Vec<Bytes>>> {
if block_to_cht_number(num) != Some(self.number) { return Ok(None) }
let mut recorder = Recorder::with_depth(from_level);
let t = TrieDB::new(&self.db, &self.root)?;
t.get_with(&key!(num), &mut recorder)?;
Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect()))
}
}
/// Block information necessary to build a CHT.
pub struct BlockInfo {
/// The block's hash.
pub hash: H256,
/// The block's parent's hash.
pub parent_hash: H256,
/// The block's total difficulty.
pub total_difficulty: U256,
}
/// Build an in-memory CHT from a closure which provides necessary information
/// about blocks. If the fetcher ever fails to provide the info, the CHT
/// will not be generated.
pub fn build<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB>>
where F: FnMut(BlockId) -> Option<BlockInfo>
{
let mut db = MemoryDB::new();
// start from the last block by number and work backwards.
let last_num = start_number(cht_num + 1) - 1;
let mut id = BlockId::Number(last_num);
let mut root = H256::default();
{
let mut t = TrieDBMut::new(&mut db, &mut root);
for blk_num in (0..SIZE).map(|n| last_num - n) {
let info = match fetcher(id) {
Some(info) => info,
None => return None,
};
id = BlockId::Hash(info.parent_hash);
t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty))
.expect("fresh in-memory database is infallible; qed");
}
}
Some(CHT {
db: db,
root: root,
number: cht_num,
})
}
/// Compute a CHT root from an iterator of (hash, td) pairs. Fails if shorter than
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
/// Discards the trie's nodes.
pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
where I: IntoIterator<Item=(H256, U256)>
{
let mut v = Vec::with_capacity(SIZE as usize);
let start_num = start_number(cht_num) as usize;
for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() {
v.push((key!(i + start_num).to_vec(), val!(h, td).to_vec()))
}
if v.len() == SIZE as usize {
Some(::util::triehash::trie_root(v))
} else {
None
}
}
/// Check a proof for a CHT.
/// Given a set of a trie nodes, a number to query, and a trie root,
/// verify the given trie branch and extract the canonical hash and total difficulty.
// TODO: better support for partially-checked queries.
pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)> {
let mut db = MemoryDB::new();
for node in proof { db.insert(&node[..]); }
let res = match TrieDB::new(&db, &root) {
Err(_) => return None,
Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| {
let rlp = UntrustedRlp::new(val);
rlp.val_at::<H256>(0)
.and_then(|h| rlp.val_at::<U256>(1).map(|td| (h, td)))
.ok()
})
};
match res {
Ok(Some(Some((hash, td)))) => Some((hash, td)),
_ => None,
}
}
/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
match block_num {
0 => None,
n => Some((n - 1) / SIZE),
}
}
/// Get the starting block of a given CHT.
/// CHT 0 includes block 1...SIZE,
/// CHT 1 includes block SIZE + 1 ... 2*SIZE
/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE).
/// This is because the genesis hash is assumed to be known
/// and including it would be redundant.
pub fn start_number(cht_num: u64) -> u64 {
(cht_num * SIZE) + 1
}
#[cfg(test)]
mod tests {
#[test]
fn size_is_lt_usize() {
// to ensure safe casting on the target platform.
assert!(::cht::SIZE < usize::max_value() as u64)
}
#[test]
fn block_to_cht_number() {
assert!(::cht::block_to_cht_number(0).is_none());
assert_eq!(::cht::block_to_cht_number(1).unwrap(), 0);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE + 1).unwrap(), 1);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE).unwrap(), 0);
}
#[test]
fn start_number() {
assert_eq!(::cht::start_number(0), 1);
assert_eq!(::cht::start_number(1), ::cht::SIZE + 1);
assert_eq!(::cht::start_number(2), ::cht::SIZE * 2 + 1);
}
}

View File

@@ -1,443 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Light client header chain.
//!
//! Unlike a full node's `BlockChain` this doesn't store much in the database.
//! It stores candidates for the last 2048-4096 blocks as well as CHT roots for
//! historical blocks all the way to the genesis.
//!
//! This is separate from the `BlockChain` for two reasons:
//! - It stores only headers (and a pruned subset of them)
//! - To allow for flexibility in the database layout once that's incorporated.
// TODO: use DB instead of memory. DB Layout: just the contents of `candidates`/`headers`
//
use std::collections::{BTreeMap, HashMap};
use cht;
use ethcore::block_status::BlockStatus;
use ethcore::error::BlockError;
use ethcore::encoded;
use ethcore::header::Header;
use ethcore::ids::BlockId;
use util::{H256, U256, HeapSizeOf, Mutex, RwLock};
use smallvec::SmallVec;
/// Store at least this many candidate headers at all times.
/// Also functions as the delay for computing CHTs as they aren't
/// relevant to any blocks we've got in memory.
const HISTORY: u64 = 2048;
/// Information about a block.
#[derive(Debug, Clone)]
pub struct BlockDescriptor {
/// The block's hash
pub hash: H256,
/// The block's number
pub number: u64,
/// The block's total difficulty.
pub total_difficulty: U256,
}
// candidate block description.
struct Candidate {
hash: H256,
parent_hash: H256,
total_difficulty: U256,
}
struct Entry {
candidates: SmallVec<[Candidate; 3]>, // 3 arbitrarily chosen
canonical_hash: H256,
}
impl HeapSizeOf for Entry {
fn heap_size_of_children(&self) -> usize {
match self.candidates.spilled() {
false => 0,
true => self.candidates.capacity() * ::std::mem::size_of::<Candidate>(),
}
}
}
/// Header chain. See module docs for more details.
pub struct HeaderChain {
genesis_header: encoded::Header, // special-case the genesis.
candidates: RwLock<BTreeMap<u64, Entry>>,
headers: RwLock<HashMap<H256, encoded::Header>>,
best_block: RwLock<BlockDescriptor>,
cht_roots: Mutex<Vec<H256>>,
}
impl HeaderChain {
/// Create a new header chain given this genesis block.
pub fn new(genesis: &[u8]) -> Self {
use ethcore::views::HeaderView;
let g_view = HeaderView::new(genesis);
HeaderChain {
genesis_header: encoded::Header::new(genesis.to_owned()),
best_block: RwLock::new(BlockDescriptor {
hash: g_view.hash(),
number: 0,
total_difficulty: g_view.difficulty(),
}),
candidates: RwLock::new(BTreeMap::new()),
headers: RwLock::new(HashMap::new()),
cht_roots: Mutex::new(Vec::new()),
}
}
/// Insert a pre-verified header.
///
/// This blindly trusts that the data given to it is sensible.
pub fn insert(&self, header: Header) -> Result<(), BlockError> {
let hash = header.hash();
let number = header.number();
let parent_hash = *header.parent_hash();
// hold candidates the whole time to guard import order.
let mut candidates = self.candidates.write();
// find parent details.
let parent_td =
if number == 1 {
self.genesis_header.difficulty()
} else {
candidates.get(&(number - 1))
.and_then(|entry| entry.candidates.iter().find(|c| c.hash == parent_hash))
.map(|c| c.total_difficulty)
.ok_or_else(|| BlockError::UnknownParent(parent_hash))?
};
let total_difficulty = parent_td + *header.difficulty();
// insert headers and candidates entries.
candidates.entry(number).or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash })
.candidates.push(Candidate {
hash: hash,
parent_hash: parent_hash,
total_difficulty: total_difficulty,
});
let raw = ::rlp::encode(&header).to_vec();
self.headers.write().insert(hash, encoded::Header::new(raw));
// reorganize ancestors so canonical entries are first in their
// respective candidates vectors.
if self.best_block.read().total_difficulty < total_difficulty {
let mut canon_hash = hash;
for (&height, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) {
if height != number && entry.canonical_hash == canon_hash { break; }
trace!(target: "chain", "Setting new canonical block {} for block height {}",
canon_hash, height);
let canon_pos = entry.candidates.iter().position(|x| x.hash == canon_hash)
.expect("blocks are only inserted if parent is present; or this is the block we just added; qed");
// move the new canonical entry to the front and set the
// era's canonical hash.
entry.candidates.swap(0, canon_pos);
entry.canonical_hash = canon_hash;
// what about reorgs > cht::SIZE + HISTORY?
// resetting to the last block of a given CHT should be possible.
canon_hash = entry.candidates[0].parent_hash;
}
trace!(target: "chain", "New best block: ({}, {}), TD {}", number, hash, total_difficulty);
*self.best_block.write() = BlockDescriptor {
hash: hash,
number: number,
total_difficulty: total_difficulty,
};
// produce next CHT root if it's time.
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
if earliest_era + HISTORY + cht::SIZE <= number {
let cht_num = cht::block_to_cht_number(earliest_era)
.expect("fails only for number == 0; genesis never imported; qed");
debug_assert_eq!(cht_num as usize, self.cht_roots.lock().len());
let mut headers = self.headers.write();
let cht_root = {
let mut i = earliest_era;
// iterable function which removes the candidates as it goes
// along. this will only be called until the CHT is complete.
let iter = || {
let era_entry = candidates.remove(&i)
.expect("all eras are sequential with no gaps; qed");
i += 1;
for ancient in &era_entry.candidates {
headers.remove(&ancient.hash);
}
let canon = &era_entry.candidates[0];
(canon.hash, canon.total_difficulty)
};
cht::compute_root(cht_num, ::itertools::repeat_call(iter))
.expect("fails only when too few items; this is checked; qed")
};
debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root);
self.cht_roots.lock().push(cht_root);
}
}
Ok(())
}
/// Get a block header. In the case of query by number, only canonical blocks
/// will be returned.
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
match id {
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
BlockId::Hash(hash) => self.headers.read().get(&hash).cloned(),
BlockId::Number(num) => {
if self.best_block.read().number < num { return None }
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
.and_then(|hash| self.headers.read().get(&hash).cloned())
}
BlockId::Latest | BlockId::Pending => {
let hash = {
let best = self.best_block.read();
if best.number == 0 {
return Some(self.genesis_header.clone())
}
best.hash
};
self.headers.read().get(&hash).cloned()
}
}
}
/// Get the best block's header.
pub fn best_header(&self) -> encoded::Header {
self.block_header(BlockId::Latest).expect("Header for best block always stored; qed")
}
/// Get an iterator over a block and its ancestry.
pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter {
AncestryIter {
next: self.block_header(start),
chain: self,
}
}
/// Get the nth CHT root, if it's been computed.
///
/// CHT root 0 is from block `1..2048`.
/// CHT root 1 is from block `2049..4096`
/// and so on.
///
/// This is because it's assumed that the genesis hash is known,
/// so including it within a CHT would be redundant.
pub fn cht_root(&self, n: usize) -> Option<H256> {
self.cht_roots.lock().get(n).map(|h| h.clone())
}
/// Get the genesis hash.
pub fn genesis_hash(&self) -> H256 {
::util::Hashable::sha3(&self.genesis_header)
}
/// Get the best block's data.
pub fn best_block(&self) -> BlockDescriptor {
self.best_block.read().clone()
}
/// If there is a gap between the genesis and the rest
/// of the stored blocks, return the first post-gap block.
pub fn first_block(&self) -> Option<BlockDescriptor> {
let candidates = self.candidates.read();
match candidates.iter().next() {
None | Some((&1, _)) => None,
Some((&height, entry)) => Some(BlockDescriptor {
number: height,
hash: entry.canonical_hash,
total_difficulty: entry.candidates.iter().find(|x| x.hash == entry.canonical_hash)
.expect("entry always stores canonical candidate; qed").total_difficulty,
})
}
}
/// Get block status.
pub fn status(&self, hash: &H256) -> BlockStatus {
match self.headers.read().contains_key(hash) {
true => BlockStatus::InChain,
false => BlockStatus::Unknown,
}
}
}
impl HeapSizeOf for HeaderChain {
fn heap_size_of_children(&self) -> usize {
self.candidates.read().heap_size_of_children() +
self.headers.read().heap_size_of_children() +
self.cht_roots.lock().heap_size_of_children()
}
}
/// Iterator over a block's ancestry.
pub struct AncestryIter<'a> {
next: Option<encoded::Header>,
chain: &'a HeaderChain,
}
impl<'a> Iterator for AncestryIter<'a> {
type Item = encoded::Header;
fn next(&mut self) -> Option<encoded::Header> {
let next = self.next.take();
if let Some(p_hash) = next.as_ref().map(|hdr| hdr.parent_hash()) {
self.next = self.chain.block_header(BlockId::Hash(p_hash));
}
next
}
}
#[cfg(test)]
mod tests {
use super::HeaderChain;
use ethcore::ids::BlockId;
use ethcore::header::Header;
use ethcore::spec::Spec;
#[test]
fn basic_chain() {
let spec = Spec::new_test();
let genesis_header = spec.genesis_header();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp();
for i in 1..10000 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash();
chain.insert(header).unwrap();
rolling_timestamp += 10;
}
assert!(chain.block_header(BlockId::Number(10)).is_none());
assert!(chain.block_header(BlockId::Number(9000)).is_some());
assert!(chain.cht_root(2).is_some());
assert!(chain.cht_root(3).is_none());
}
#[test]
fn reorganize() {
let spec = Spec::new_test();
let genesis_header = spec.genesis_header();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
let mut parent_hash = genesis_header.hash();
let mut rolling_timestamp = genesis_header.timestamp();
for i in 1..6 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash();
chain.insert(header).unwrap();
rolling_timestamp += 10;
}
{
let mut rolling_timestamp = rolling_timestamp;
let mut parent_hash = parent_hash;
for i in 6..16 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * i.into());
parent_hash = header.hash();
chain.insert(header).unwrap();
rolling_timestamp += 10;
}
}
assert_eq!(chain.best_block().number, 15);
{
let mut rolling_timestamp = rolling_timestamp;
let mut parent_hash = parent_hash;
// import a shorter chain which has better TD.
for i in 6..13 {
let mut header = Header::new();
header.set_parent_hash(parent_hash);
header.set_number(i);
header.set_timestamp(rolling_timestamp);
header.set_difficulty(*genesis_header.difficulty() * (i * i).into());
parent_hash = header.hash();
chain.insert(header).unwrap();
rolling_timestamp += 11;
}
}
let (mut num, mut canon_hash) = (chain.best_block().number, chain.best_block().hash);
assert_eq!(num, 12);
while num > 0 {
let header = chain.block_header(BlockId::Number(num)).unwrap();
assert_eq!(header.hash(), canon_hash);
canon_hash = header.parent_hash();
num -= 1;
}
}
#[test]
fn earliest_is_latest() {
let spec = Spec::new_test();
let genesis_header = spec.genesis_header();
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
assert!(chain.block_header(BlockId::Earliest).is_some());
assert!(chain.block_header(BlockId::Latest).is_some());
assert!(chain.block_header(BlockId::Pending).is_some());
}
}

View File

@@ -1,350 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Light client implementation. Stores data from light sync
use std::sync::Arc;
use ethcore::block_import_error::BlockImportError;
use ethcore::block_status::BlockStatus;
use ethcore::client::{ClientReport, EnvInfo};
use ethcore::engines::Engine;
use ethcore::ids::BlockId;
use ethcore::header::Header;
use ethcore::verification::queue::{self, HeaderQueue};
use ethcore::blockchain_info::BlockChainInfo;
use ethcore::spec::Spec;
use ethcore::service::ClientIoMessage;
use ethcore::encoded;
use io::IoChannel;
use util::{Bytes, H256, Mutex, RwLock};
use self::header_chain::{AncestryIter, HeaderChain};
pub use self::service::Service;
mod header_chain;
mod service;
/// Configuration for the light client.
#[derive(Debug, Default, Clone)]
pub struct Config {
/// Verification queue config.
pub queue: queue::Config,
}
/// Trait for interacting with the header chain abstractly.
pub trait LightChainClient: Send + Sync {
/// Get chain info.
fn chain_info(&self) -> BlockChainInfo;
/// Queue header to be verified. Required that all headers queued have their
/// parent queued prior.
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError>;
/// Attempt to get block header by block id.
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
/// Get the best block header.
fn best_block_header(&self) -> encoded::Header;
/// Get an iterator over a block and its ancestry.
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a>;
/// Get the signing network ID.
fn signing_network_id(&self) -> Option<u64>;
/// Query whether a block is known.
fn is_known(&self, hash: &H256) -> bool;
/// Clear the queue.
fn clear_queue(&self);
/// Flush the queue.
fn flush_queue(&self);
/// Get queue info.
fn queue_info(&self) -> queue::QueueInfo;
/// Get the `i`th CHT root.
fn cht_root(&self, i: usize) -> Option<H256>;
}
/// Something which can be treated as a `LightChainClient`.
pub trait AsLightClient {
/// The kind of light client this can be treated as.
type Client: LightChainClient;
/// Access the underlying light client.
fn as_light_client(&self) -> &Self::Client;
}
impl<T: LightChainClient> AsLightClient for T {
type Client = Self;
fn as_light_client(&self) -> &Self { self }
}
/// Light client implementation.
pub struct Client {
queue: HeaderQueue,
engine: Arc<Engine>,
chain: HeaderChain,
report: RwLock<ClientReport>,
import_lock: Mutex<()>,
}
impl Client {
/// Create a new `Client`.
pub fn new(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self {
Client {
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
engine: spec.engine.clone(),
chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())),
report: RwLock::new(ClientReport::default()),
import_lock: Mutex::new(()),
}
}
/// Import a header to the queue for additional verification.
pub fn import_header(&self, header: Header) -> Result<H256, BlockImportError> {
self.queue.import(header).map_err(Into::into)
}
/// Inquire about the status of a given header.
pub fn status(&self, hash: &H256) -> BlockStatus {
match self.queue.status(hash) {
queue::Status::Unknown => self.chain.status(hash),
other => other.into(),
}
}
/// Get the chain info.
pub fn chain_info(&self) -> BlockChainInfo {
let best_hdr = self.chain.best_header();
let best_td = self.chain.best_block().total_difficulty;
let first_block = self.chain.first_block();
let genesis_hash = self.chain.genesis_hash();
BlockChainInfo {
total_difficulty: best_td,
pending_total_difficulty: best_td + self.queue.total_difficulty(),
genesis_hash: genesis_hash,
best_block_hash: best_hdr.hash(),
best_block_number: best_hdr.number(),
best_block_timestamp: best_hdr.timestamp(),
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
ancient_block_number: if first_block.is_some() { Some(0) } else { None },
first_block_hash: first_block.as_ref().map(|first| first.hash),
first_block_number: first_block.as_ref().map(|first| first.number),
}
}
/// Get the header queue info.
pub fn queue_info(&self) -> queue::QueueInfo {
self.queue.queue_info()
}
/// Get a block header by Id.
pub fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
self.chain.block_header(id)
}
/// Get the best block header.
pub fn best_block_header(&self) -> encoded::Header {
self.chain.best_header()
}
/// Get an iterator over a block and its ancestry.
pub fn ancestry_iter(&self, start: BlockId) -> AncestryIter {
self.chain.ancestry_iter(start)
}
/// Get the signing network id.
pub fn signing_network_id(&self) -> Option<u64> {
self.engine.signing_network_id(&self.latest_env_info())
}
/// Flush the header queue.
pub fn flush_queue(&self) {
self.queue.flush()
}
/// Get the `i`th CHT root.
pub fn cht_root(&self, i: usize) -> Option<H256> {
self.chain.cht_root(i)
}
/// Import a set of pre-verified headers from the queue.
pub fn import_verified(&self) {
const MAX: usize = 256;
let _lock = self.import_lock.lock();
let mut bad = Vec::new();
let mut good = Vec::new();
for verified_header in self.queue.drain(MAX) {
let (num, hash) = (verified_header.number(), verified_header.hash());
match self.chain.insert(verified_header) {
Ok(()) => {
good.push(hash);
self.report.write().blocks_imported += 1;
}
Err(e) => {
debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e);
bad.push(hash);
}
}
}
self.queue.mark_as_bad(&bad);
self.queue.mark_as_good(&good);
}
/// Get a report about blocks imported.
pub fn report(&self) -> ClientReport {
::std::mem::replace(&mut *self.report.write(), ClientReport::default())
}
/// Get blockchain mem usage in bytes.
pub fn chain_mem_used(&self) -> usize {
use util::HeapSizeOf;
self.chain.heap_size_of_children()
}
/// Get a handle to the verification engine.
pub fn engine(&self) -> &Engine {
&*self.engine
}
fn latest_env_info(&self) -> EnvInfo {
let header = self.best_block_header();
EnvInfo {
number: header.number(),
author: header.author(),
timestamp: header.timestamp(),
difficulty: header.difficulty(),
last_hashes: self.build_last_hashes(header.hash()),
gas_used: Default::default(),
gas_limit: header.gas_limit(),
}
}
fn build_last_hashes(&self, mut parent_hash: H256) -> Arc<Vec<H256>> {
let mut v = Vec::with_capacity(256);
for _ in 0..255 {
v.push(parent_hash);
match self.block_header(BlockId::Hash(parent_hash)) {
Some(header) => parent_hash = header.hash(),
None => break,
}
}
Arc::new(v)
}
}
impl LightChainClient for Client {
fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) }
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError> {
self.import_header(header)
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
Client::block_header(self, id)
}
fn best_block_header(&self) -> encoded::Header {
Client::best_block_header(self)
}
fn ancestry_iter<'a>(&'a self, start: BlockId) -> Box<Iterator<Item=encoded::Header> + 'a> {
Box::new(Client::ancestry_iter(self, start))
}
fn signing_network_id(&self) -> Option<u64> {
Client::signing_network_id(self)
}
fn is_known(&self, hash: &H256) -> bool {
self.status(hash) == BlockStatus::InChain
}
fn clear_queue(&self) {
self.queue.clear()
}
fn flush_queue(&self) {
Client::flush_queue(self);
}
fn queue_info(&self) -> queue::QueueInfo {
self.queue.queue_info()
}
fn cht_root(&self, i: usize) -> Option<H256> {
Client::cht_root(self, i)
}
}
// dummy implementation, should be removed when a `TestClient` is added.
impl ::provider::Provider for Client {
fn chain_info(&self) -> BlockChainInfo {
Client::chain_info(self)
}
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
None
}
fn earliest_state(&self) -> Option<u64> {
None
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
Client::block_header(self, id)
}
fn block_body(&self, _id: BlockId) -> Option<encoded::Body> {
None
}
fn block_receipts(&self, _hash: &H256) -> Option<Bytes> {
None
}
fn state_proof(&self, _req: ::request::StateProof) -> Vec<Bytes> {
Vec::new()
}
fn contract_code(&self, _req: ::request::ContractCode) -> Bytes {
Vec::new()
}
fn header_proof(&self, _req: ::request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
None
}
fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> {
Vec::new()
}
}

View File

@@ -1,73 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Minimal IO service for light client.
//! Just handles block import messages and passes them to the client.
use std::sync::Arc;
use ethcore::service::ClientIoMessage;
use ethcore::spec::Spec;
use io::{IoContext, IoError, IoHandler, IoService};
use super::{Client, Config as ClientConfig};
/// Light client service.
pub struct Service {
client: Arc<Client>,
_io_service: IoService<ClientIoMessage>,
}
impl Service {
/// Start the service: initialize I/O workers and client itself.
pub fn start(config: ClientConfig, spec: &Spec) -> Result<Self, IoError> {
let io_service = try!(IoService::<ClientIoMessage>::start());
let client = Arc::new(Client::new(config, spec, io_service.channel()));
try!(io_service.register_handler(Arc::new(ImportBlocks(client.clone()))));
Ok(Service {
client: client,
_io_service: io_service,
})
}
/// Get a handle to the client.
pub fn client(&self) -> &Arc<Client> {
&self.client
}
}
struct ImportBlocks(Arc<Client>);
impl IoHandler<ClientIoMessage> for ImportBlocks {
fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) {
if let ClientIoMessage::BlockVerified = *message {
self.0.import_verified();
}
}
}
#[cfg(test)]
mod tests {
use super::Service;
use ethcore::spec::Spec;
#[test]
fn it_works() {
let spec = Spec::new_test();
Service::start(Default::default(), &spec).unwrap();
}
}

View File

@@ -1,78 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Light client logic and implementation.
//!
//! A "light" client stores very little chain-related data locally
//! unlike a full node, which stores all blocks, headers, receipts, and more.
//!
//! This enables the client to have a much lower resource footprint in
//! exchange for the cost of having to ask the network for state data
//! while responding to queries. This makes a light client unsuitable for
//! low-latency applications, but perfectly suitable for simple everyday
//! use-cases like sending transactions from a personal account.
//!
//! The light client performs a header-only sync, doing verification and pruning
//! historical blocks. Upon pruning, batches of 2048 blocks have a number => hash
//! mapping sealed into "canonical hash tries" which can later be used to verify
//! historical block queries from peers.
#![deny(missing_docs)]
pub mod client;
pub mod cht;
pub mod net;
pub mod on_demand;
pub mod transaction_queue;
pub mod cache;
#[cfg(not(feature = "ipc"))]
pub mod provider;
#[cfg(feature = "ipc")]
pub mod provider {
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/provider.rs"));
}
#[cfg(feature = "ipc")]
pub mod remote {
pub use provider::LightProviderClient;
}
mod types;
pub use self::provider::Provider;
pub use self::transaction_queue::TransactionQueue;
pub use types::les_request as request;
#[macro_use]
extern crate log;
extern crate ethcore;
extern crate ethcore_util as util;
extern crate ethcore_network as network;
extern crate ethcore_io as io;
extern crate rlp;
extern crate smallvec;
extern crate time;
extern crate futures;
extern crate rand;
extern crate itertools;
extern crate stats;
#[cfg(feature = "ipc")]
extern crate ethcore_ipc as ipc;

View File

@@ -1,337 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! LES buffer flow management.
//!
//! Every request in the LES protocol leads to a reduction
//! of the requester's buffer value as a rate-limiting mechanism.
//! This buffer value will recharge at a set rate.
//!
//! This module provides an interface for configuration of buffer
//! flow costs and recharge rates.
//!
//! Current default costs are picked completely arbitrarily, not based
//! on any empirical timings or mathematical models.
use request;
use super::packet;
use super::error::Error;
use rlp::*;
use util::U256;
use time::{Duration, SteadyTime};
/// A request cost specification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cost(pub U256, pub U256);
/// Buffer value.
///
/// Produced and recharged using `FlowParams`.
/// Definitive updates can be made as well -- these will reset the recharge
/// point to the time of the update.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Buffer {
estimate: U256,
recharge_point: SteadyTime,
}
impl Buffer {
/// Get the current buffer value.
pub fn current(&self) -> U256 { self.estimate.clone() }
/// Make a definitive update.
/// This will be the value obtained after receiving
/// a response to a request.
pub fn update_to(&mut self, value: U256) {
self.estimate = value;
self.recharge_point = SteadyTime::now();
}
/// Attempt to apply the given cost to the buffer.
///
/// If successful, the cost will be deducted successfully.
///
/// If unsuccessful, the structure will be unaltered an an
/// error will be produced.
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
match cost > self.estimate {
true => Err(Error::BufferEmpty),
false => {
self.estimate = self.estimate - cost;
Ok(())
}
}
}
}
/// A cost table, mapping requests to base and per-request costs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CostTable {
headers: Cost,
bodies: Cost,
receipts: Cost,
state_proofs: Cost,
contract_codes: Cost,
header_proofs: Cost,
}
impl Default for CostTable {
fn default() -> Self {
// arbitrarily chosen constants.
CostTable {
headers: Cost(100000.into(), 10000.into()),
bodies: Cost(150000.into(), 15000.into()),
receipts: Cost(50000.into(), 5000.into()),
state_proofs: Cost(250000.into(), 25000.into()),
contract_codes: Cost(200000.into(), 20000.into()),
header_proofs: Cost(150000.into(), 15000.into()),
}
}
}
impl RlpEncodable for CostTable {
fn rlp_append(&self, s: &mut RlpStream) {
fn append_cost(s: &mut RlpStream, msg_id: u8, cost: &Cost) {
s.begin_list(3)
.append(&msg_id)
.append(&cost.0)
.append(&cost.1);
}
s.begin_list(6);
append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers);
append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies);
append_cost(s, packet::GET_RECEIPTS, &self.receipts);
append_cost(s, packet::GET_PROOFS, &self.state_proofs);
append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes);
append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs);
}
}
impl RlpDecodable for CostTable {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let rlp = decoder.as_rlp();
let mut headers = None;
let mut bodies = None;
let mut receipts = None;
let mut state_proofs = None;
let mut contract_codes = None;
let mut header_proofs = None;
for row in rlp.iter() {
let msg_id: u8 = row.val_at(0)?;
let cost = {
let base = row.val_at(1)?;
let per = row.val_at(2)?;
Cost(base, per)
};
match msg_id {
packet::GET_BLOCK_HEADERS => headers = Some(cost),
packet::GET_BLOCK_BODIES => bodies = Some(cost),
packet::GET_RECEIPTS => receipts = Some(cost),
packet::GET_PROOFS => state_proofs = Some(cost),
packet::GET_CONTRACT_CODES => contract_codes = Some(cost),
packet::GET_HEADER_PROOFS => header_proofs = Some(cost),
_ => return Err(DecoderError::Custom("Unrecognized message in cost table")),
}
}
Ok(CostTable {
headers: headers.ok_or(DecoderError::Custom("No headers cost specified"))?,
bodies: bodies.ok_or(DecoderError::Custom("No bodies cost specified"))?,
receipts: receipts.ok_or(DecoderError::Custom("No receipts cost specified"))?,
state_proofs: state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))?,
contract_codes: contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))?,
header_proofs: header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))?,
})
}
}
/// A buffer-flow manager handles costs, recharge, limits
#[derive(Debug, Clone, PartialEq)]
pub struct FlowParams {
costs: CostTable,
limit: U256,
recharge: U256,
}
impl FlowParams {
/// Create new flow parameters from a request cost table,
/// buffer limit, and (minimum) rate of recharge.
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
FlowParams {
costs: costs,
limit: limit,
recharge: recharge,
}
}
/// Create effectively infinite flow params.
pub fn free() -> Self {
let free_cost = Cost(0.into(), 0.into());
FlowParams {
limit: (!0u64).into(),
recharge: 1.into(),
costs: CostTable {
headers: free_cost.clone(),
bodies: free_cost.clone(),
receipts: free_cost.clone(),
state_proofs: free_cost.clone(),
contract_codes: free_cost.clone(),
header_proofs: free_cost.clone(),
}
}
}
/// Get a reference to the buffer limit.
pub fn limit(&self) -> &U256 { &self.limit }
/// Get a reference to the cost table.
pub fn cost_table(&self) -> &CostTable { &self.costs }
/// Get a reference to the recharge rate.
pub fn recharge_rate(&self) -> &U256 { &self.recharge }
/// Compute the actual cost of a request, given the kind of request
/// and number of requests made.
pub fn compute_cost(&self, kind: request::Kind, amount: usize) -> U256 {
let cost = match kind {
request::Kind::Headers => &self.costs.headers,
request::Kind::Bodies => &self.costs.bodies,
request::Kind::Receipts => &self.costs.receipts,
request::Kind::StateProofs => &self.costs.state_proofs,
request::Kind::Codes => &self.costs.contract_codes,
request::Kind::HeaderProofs => &self.costs.header_proofs,
};
let amount: U256 = amount.into();
cost.0 + (amount * cost.1)
}
/// Compute the maximum number of costs of a specific kind which can be made
/// with the given buffer.
/// Saturates at `usize::max()`. This is not a problem in practice because
/// this amount of requests is already prohibitively large.
pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize {
use util::Uint;
use std::usize;
let cost = match kind {
request::Kind::Headers => &self.costs.headers,
request::Kind::Bodies => &self.costs.bodies,
request::Kind::Receipts => &self.costs.receipts,
request::Kind::StateProofs => &self.costs.state_proofs,
request::Kind::Codes => &self.costs.contract_codes,
request::Kind::HeaderProofs => &self.costs.header_proofs,
};
let start = buffer.current();
if start <= cost.0 {
return 0;
} else if cost.1 == U256::zero() {
return usize::MAX;
}
let max = (start - cost.0) / cost.1;
if max >= usize::MAX.into() {
usize::MAX
} else {
max.as_u64() as usize
}
}
/// Create initial buffer parameter.
pub fn create_buffer(&self) -> Buffer {
Buffer {
estimate: self.limit,
recharge_point: SteadyTime::now(),
}
}
/// Recharge the buffer based on time passed since last
/// update.
pub fn recharge(&self, buf: &mut Buffer) {
let now = SteadyTime::now();
// recompute and update only in terms of full seconds elapsed
// in order to keep the estimate as an underestimate.
let elapsed = (now - buf.recharge_point).num_seconds();
buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed);
let elapsed: U256 = elapsed.into();
buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge));
}
/// Refund some buffer which was previously deducted.
/// Does not update the recharge timestamp.
pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) {
buf.estimate = buf.estimate + refund_amount;
if buf.estimate > self.limit {
buf.estimate = self.limit
}
}
}
impl Default for FlowParams {
fn default() -> Self {
FlowParams {
limit: 50_000_000.into(),
costs: CostTable::default(),
recharge: 100_000.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_serialize_cost_table() {
let costs = CostTable::default();
let serialized = ::rlp::encode(&costs);
let new_costs: CostTable = ::rlp::decode(&*serialized);
assert_eq!(costs, new_costs);
}
#[test]
fn buffer_mechanism() {
use std::thread;
use std::time::Duration;
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
let mut buffer = flow_params.create_buffer();
assert!(buffer.deduct_cost(101.into()).is_err());
assert!(buffer.deduct_cost(10.into()).is_ok());
thread::sleep(Duration::from_secs(1));
flow_params.recharge(&mut buffer);
assert_eq!(buffer.estimate, 100.into());
}
}

View File

@@ -1,192 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! I/O and event context generalizations.
use network::{NetworkContext, PeerId, NodeId};
use super::{Announcement, LightProtocol, ReqId};
use super::error::Error;
use request::{self, Request};
/// An I/O context which allows sending and receiving packets as well as
/// disconnecting peers. This is used as a generalization of the portions
/// of a p2p network which the light protocol structure makes use of.
pub trait IoContext {
/// Send a packet to a specific peer.
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>);
/// Respond to a peer's message. Only works if this context is a byproduct
/// of a packet handler.
fn respond(&self, packet_id: u8, packet_body: Vec<u8>);
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disable a peer -- this is a disconnect + a time-out.
fn disable_peer(&self, peer: PeerId);
/// Get a peer's protocol version.
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
/// Persistent peer id
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
}
impl<'a> IoContext for NetworkContext<'a> {
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.send(peer, packet_id, packet_body) {
debug!(target: "les", "Error sending packet to peer {}: {}", peer, e);
}
}
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.respond(packet_id, packet_body) {
debug!(target: "les", "Error responding to peer message: {}", e);
}
}
fn disconnect_peer(&self, peer: PeerId) {
NetworkContext::disconnect_peer(self, peer);
}
fn disable_peer(&self, peer: PeerId) {
NetworkContext::disable_peer(self, peer);
}
fn protocol_version(&self, peer: PeerId) -> Option<u8> {
self.protocol_version(self.subprotocol_name(), peer)
}
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId> {
self.session_info(peer).and_then(|info| info.id)
}
}
/// Basic context for the protocol.
pub trait BasicContext {
/// Returns the relevant's peer persistent Id (aka NodeId).
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
/// Make a request from a peer.
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error>;
/// Make an announcement of new capabilities to the rest of the peers.
// TODO: maybe just put this on a timer in LightProtocol?
fn make_announcement(&self, announcement: Announcement);
/// Find the maximum number of requests of a specific type which can be made from
/// supplied peer.
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize;
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disable a peer.
fn disable_peer(&self, peer: PeerId);
}
/// Context for a protocol event which has a peer ID attached.
pub trait EventContext: BasicContext {
/// Get the peer relevant to the event e.g. message sender,
/// disconnected/connected peer.
fn peer(&self) -> PeerId;
/// Treat the event context as a basic context.
fn as_basic(&self) -> &BasicContext;
}
/// Basic context.
pub struct TickCtx<'a> {
/// Io context to enable dispatch.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
}
impl<'a> BasicContext for TickCtx<'a> {
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
self.proto.request_from(self.io, &peer, request)
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
self.proto.max_requests(peer, kind)
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
}
/// Concrete implementation of `EventContext` over the light protocol struct and
/// an io context.
pub struct Ctx<'a> {
/// Io context to enable immediate response to events.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
/// Relevant peer for event.
pub peer: PeerId,
}
impl<'a> BasicContext for Ctx<'a> {
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
self.proto.request_from(self.io, &peer, request)
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
self.proto.max_requests(peer, kind)
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
}
impl<'a> EventContext for Ctx<'a> {
fn peer(&self) -> PeerId {
self.peer
}
fn as_basic(&self) -> &BasicContext {
&*self
}
}

View File

@@ -1,118 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Defines error types and levels of punishment to use upon
//! encountering.
use rlp::DecoderError;
use network::NetworkError;
use std::fmt;
/// Levels of punishment.
///
/// Currently just encompasses two different kinds of disconnect and
/// no punishment, but this is where reputation systems might come into play.
// In ascending order
#[derive(Debug, PartialEq, Eq)]
pub enum Punishment {
/// Perform no punishment.
None,
/// Disconnect the peer, but don't prevent them from reconnecting.
Disconnect,
/// Disconnect the peer and prevent them from reconnecting.
Disable,
}
/// Kinds of errors which can be encountered in the course of LES.
#[derive(Debug)]
pub enum Error {
/// An RLP decoding error.
Rlp(DecoderError),
/// A network error.
Network(NetworkError),
/// Out of buffer.
BufferEmpty,
/// Unrecognized packet code.
UnrecognizedPacket(u8),
/// Unexpected handshake.
UnexpectedHandshake,
/// Peer on wrong network (wrong NetworkId or genesis hash)
WrongNetwork,
/// Unknown peer.
UnknownPeer,
/// Unsolicited response.
UnsolicitedResponse,
/// Not a server.
NotServer,
/// Unsupported protocol version.
UnsupportedProtocolVersion(u8),
/// Bad protocol version.
BadProtocolVersion,
/// Peer is overburdened.
Overburdened,
}
impl Error {
/// What level of punishment does this error warrant?
pub fn punishment(&self) -> Punishment {
match *self {
Error::Rlp(_) => Punishment::Disable,
Error::Network(_) => Punishment::None,
Error::BufferEmpty => Punishment::Disable,
Error::UnrecognizedPacket(_) => Punishment::Disconnect,
Error::UnexpectedHandshake => Punishment::Disconnect,
Error::WrongNetwork => Punishment::Disable,
Error::UnknownPeer => Punishment::Disconnect,
Error::UnsolicitedResponse => Punishment::Disable,
Error::NotServer => Punishment::Disable,
Error::UnsupportedProtocolVersion(_) => Punishment::Disable,
Error::BadProtocolVersion => Punishment::Disable,
Error::Overburdened => Punishment::None,
}
}
}
impl From<DecoderError> for Error {
fn from(err: DecoderError) -> Self {
Error::Rlp(err)
}
}
impl From<NetworkError> for Error {
fn from(err: NetworkError) -> Self {
Error::Network(err)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Rlp(ref err) => err.fmt(f),
Error::Network(ref err) => err.fmt(f),
Error::BufferEmpty => write!(f, "Out of buffer"),
Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
Error::WrongNetwork => write!(f, "Wrong network"),
Error::UnknownPeer => write!(f, "Unknown peer"),
Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"),
Error::NotServer => write!(f, "Peer not a server."),
Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv),
Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"),
Error::Overburdened => write!(f, "Peer overburdened"),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,148 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Pending request set.
//!
//! Stores pending requests and does timeout computation according to the rule
//! that only the earliest submitted request within the structure may time out.
//!
//! Whenever a request becomes the earliest, its timeout period begins at that moment.
use std::collections::{BTreeMap, HashMap};
use std::iter::FromIterator;
use request::{self, Request};
use net::{timeout, ReqId};
use time::{Duration, SteadyTime};
/// Request set.
#[derive(Debug)]
pub struct RequestSet {
counter: u64,
base: Option<SteadyTime>,
ids: HashMap<ReqId, u64>,
reqs: BTreeMap<u64, Request>,
}
impl Default for RequestSet {
fn default() -> Self {
RequestSet {
counter: 0,
base: None,
ids: HashMap::new(),
reqs: BTreeMap::new(),
}
}
}
impl RequestSet {
/// Push a request onto the stack.
pub fn insert(&mut self, req_id: ReqId, req: Request, now: SteadyTime) {
let counter = self.counter;
self.ids.insert(req_id, counter);
self.reqs.insert(counter, req);
if self.reqs.keys().next().map_or(true, |x| *x == counter) {
self.base = Some(now);
}
self.counter += 1;
}
/// Remove a request from the stack.
pub fn remove(&mut self, req_id: &ReqId, now: SteadyTime) -> Option<Request> {
let id = match self.ids.remove(&req_id) {
Some(id) => id,
None => return None,
};
let req = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed");
match self.reqs.keys().next() {
Some(k) if *k > id => self.base = Some(now),
None => self.base = None,
_ => {}
}
Some(req)
}
/// Check for timeout against the given time. Returns true if
/// has timed out, false otherwise.
pub fn check_timeout(&self, now: SteadyTime) -> bool {
let base = match self.base.as_ref().cloned() {
Some(base) => base,
None => return false,
};
let kind = self.reqs.values()
.next()
.map(|r| r.kind())
.expect("base time implies `reqs` non-empty; qed");
let kind_timeout = match kind {
request::Kind::Headers => timeout::HEADERS,
request::Kind::Bodies => timeout::BODIES,
request::Kind::Receipts => timeout::RECEIPTS,
request::Kind::StateProofs => timeout::PROOFS,
request::Kind::Codes => timeout::CONTRACT_CODES,
request::Kind::HeaderProofs => timeout::HEADER_PROOFS,
};
base + Duration::milliseconds(kind_timeout) <= now
}
/// Collect all pending request ids.
pub fn collect_ids<F>(&self) -> F where F: FromIterator<ReqId> {
self.ids.keys().cloned().collect()
}
/// Number of requests in the set.
pub fn len(&self) -> usize {
self.ids.len()
}
/// Whether the set is empty.
pub fn is_empty(&self) -> bool { self.len() == 0 }
}
#[cfg(test)]
mod tests {
use net::{timeout, ReqId};
use request::{Request, Receipts};
use time::{SteadyTime, Duration};
use super::RequestSet;
#[test]
fn multi_timeout() {
let test_begin = SteadyTime::now();
let mut req_set = RequestSet::default();
let the_req = Request::Receipts(Receipts { block_hashes: Vec::new() });
req_set.insert(ReqId(0), the_req.clone(), test_begin);
req_set.insert(ReqId(1), the_req, test_begin + Duration::seconds(1));
assert_eq!(req_set.base, Some(test_begin));
let test_end = test_begin + Duration::milliseconds(timeout::RECEIPTS);
assert!(req_set.check_timeout(test_end));
req_set.remove(&ReqId(0), test_begin + Duration::seconds(1)).unwrap();
assert!(!req_set.check_timeout(test_end));
assert!(req_set.check_timeout(test_end + Duration::seconds(1)));
}
}

View File

@@ -1,575 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Peer status and capabilities.
use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View};
use util::{H256, U256};
use super::buffer_flow::FlowParams;
// recognized handshake/announcement keys.
// unknown keys are to be skipped, known keys have a defined order.
// their string values are defined in the LES spec.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
enum Key {
ProtocolVersion,
NetworkId,
HeadTD,
HeadHash,
HeadNum,
GenesisHash,
ServeHeaders,
ServeChainSince,
ServeStateSince,
TxRelay,
BufferLimit,
BufferCostTable,
BufferRechargeRate,
}
impl Key {
// get the string value of this key.
fn as_str(&self) -> &'static str {
match *self {
Key::ProtocolVersion => "protocolVersion",
Key::NetworkId => "networkId",
Key::HeadTD => "headTd",
Key::HeadHash => "headHash",
Key::HeadNum => "headNum",
Key::GenesisHash => "genesisHash",
Key::ServeHeaders => "serveHeaders",
Key::ServeChainSince => "serveChainSince",
Key::ServeStateSince => "serveStateSince",
Key::TxRelay => "txRelay",
Key::BufferLimit => "flowControl/BL",
Key::BufferCostTable => "flowControl/MRC",
Key::BufferRechargeRate => "flowControl/MRR",
}
}
// try to parse the key value from a string.
fn from_str(s: &str) -> Option<Self> {
match s {
"protocolVersion" => Some(Key::ProtocolVersion),
"networkId" => Some(Key::NetworkId),
"headTd" => Some(Key::HeadTD),
"headHash" => Some(Key::HeadHash),
"headNum" => Some(Key::HeadNum),
"genesisHash" => Some(Key::GenesisHash),
"serveHeaders" => Some(Key::ServeHeaders),
"serveChainSince" => Some(Key::ServeChainSince),
"serveStateSince" => Some(Key::ServeStateSince),
"txRelay" => Some(Key::TxRelay),
"flowControl/BL" => Some(Key::BufferLimit),
"flowControl/MRC" => Some(Key::BufferCostTable),
"flowControl/MRR" => Some(Key::BufferRechargeRate),
_ => None
}
}
}
// helper for decoding key-value pairs in the handshake or an announcement.
struct Parser<'a> {
pos: usize,
rlp: UntrustedRlp<'a>,
}
impl<'a> Parser<'a> {
// expect a specific next key, and decode the value.
// error on unexpected key or invalid value.
fn expect<T: RlpDecodable>(&mut self, key: Key) -> Result<T, DecoderError> {
self.expect_raw(key).and_then(|item| item.as_val())
}
// expect a specific next key, and get the value's RLP.
// if the key isn't found, the position isn't advanced.
fn expect_raw(&mut self, key: Key) -> Result<UntrustedRlp<'a>, DecoderError> {
trace!(target: "les", "Expecting key {}", key.as_str());
let pre_pos = self.pos;
if let Some((k, val)) = self.get_next()? {
if k == key { return Ok(val) }
}
self.pos = pre_pos;
Err(DecoderError::Custom("Missing expected key"))
}
// get the next key and value RLP.
fn get_next(&mut self) -> Result<Option<(Key, UntrustedRlp<'a>)>, DecoderError> {
while self.pos < self.rlp.item_count() {
let pair = self.rlp.at(self.pos)?;
let k: String = pair.val_at(0)?;
self.pos += 1;
match Key::from_str(&k) {
Some(key) => return Ok(Some((key , pair.at(1)?))),
None => continue,
}
}
Ok(None)
}
}
// Helper for encoding a key-value pair
fn encode_pair<T: RlpEncodable>(key: Key, val: &T) -> Vec<u8> {
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append(val);
s.out()
}
// Helper for encoding a flag.
fn encode_flag(key: Key) -> Vec<u8> {
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append_empty_data();
s.out()
}
/// A peer status message.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Status {
/// Protocol version.
pub protocol_version: u32,
/// Network id of this peer.
pub network_id: u64,
/// Total difficulty of the head of the chain.
pub head_td: U256,
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Genesis hash
pub genesis_hash: H256,
/// Last announced chain head and reorg depth to common ancestor.
pub last_head: Option<(H256, u64)>,
}
impl Status {
/// Update the status from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.last_head = Some((self.head_hash, announcement.reorg_depth));
self.head_td = announcement.head_td;
self.head_hash = announcement.head_hash;
self.head_num = announcement.head_num;
}
}
/// Peer capabilities.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Capabilities {
/// Whether this peer can serve headers
pub serve_headers: bool,
/// Earliest block number it can serve block/receipt requests for.
/// `None` means no requests will be servable.
pub serve_chain_since: Option<u64>,
/// Earliest block number it can serve state requests for.
/// `None` means no requests will be servable.
pub serve_state_since: Option<u64>,
/// Whether it can relay transactions to the eth network.
pub tx_relay: bool,
}
impl Default for Capabilities {
fn default() -> Self {
Capabilities {
serve_headers: true,
serve_chain_since: None,
serve_state_since: None,
tx_relay: false,
}
}
}
impl Capabilities {
/// Update the capabilities from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.serve_headers = self.serve_headers || announcement.serve_headers;
self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since);
self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since);
self.tx_relay = self.tx_relay || announcement.tx_relay;
}
}
/// Attempt to parse a handshake message into its three parts:
/// - chain status
/// - serving capabilities
/// - buffer flow parameters
pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> {
let mut parser = Parser {
pos: 0,
rlp: rlp,
};
let status = Status {
protocol_version: parser.expect(Key::ProtocolVersion)?,
network_id: parser.expect(Key::NetworkId)?,
head_td: parser.expect(Key::HeadTD)?,
head_hash: parser.expect(Key::HeadHash)?,
head_num: parser.expect(Key::HeadNum)?,
genesis_hash: parser.expect(Key::GenesisHash)?,
last_head: None,
};
let capabilities = Capabilities {
serve_headers: parser.expect_raw(Key::ServeHeaders).is_ok(),
serve_chain_since: parser.expect(Key::ServeChainSince).ok(),
serve_state_since: parser.expect(Key::ServeStateSince).ok(),
tx_relay: parser.expect_raw(Key::TxRelay).is_ok(),
};
let flow_params = match (
parser.expect(Key::BufferLimit),
parser.expect(Key::BufferCostTable),
parser.expect(Key::BufferRechargeRate)
) {
(Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)),
_ => None,
};
Ok((status, capabilities, flow_params))
}
/// Write a handshake, given status, capabilities, and flow parameters.
pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec<u8> {
let mut pairs = Vec::new();
pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version));
pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64)));
pairs.push(encode_pair(Key::HeadTD, &status.head_td));
pairs.push(encode_pair(Key::HeadHash, &status.head_hash));
pairs.push(encode_pair(Key::HeadNum, &status.head_num));
pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash));
if capabilities.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = capabilities.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = capabilities.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if capabilities.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
if let Some(flow_params) = flow_params {
pairs.push(encode_pair(Key::BufferLimit, flow_params.limit()));
pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table()));
pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate()));
}
let mut stream = RlpStream::new_list(pairs.len());
for pair in pairs {
stream.append_raw(&pair, 1);
}
stream.out()
}
/// An announcement of new chain head or capabilities made by a peer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Announcement {
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Head total difficulty
pub head_td: U256,
/// reorg depth to common ancestor of last announced head.
pub reorg_depth: u64,
/// optional new header-serving capability. false means "no change"
pub serve_headers: bool,
/// optional new state-serving capability
pub serve_state_since: Option<u64>,
/// optional new chain-serving capability
pub serve_chain_since: Option<u64>,
/// optional new transaction-relay capability. false means "no change"
pub tx_relay: bool,
// TODO: changes in buffer flow?
}
/// Parse an announcement.
pub fn parse_announcement(rlp: UntrustedRlp) -> Result<Announcement, DecoderError> {
let mut last_key = None;
let mut announcement = Announcement {
head_hash: rlp.val_at(0)?,
head_num: rlp.val_at(1)?,
head_td: rlp.val_at(2)?,
reorg_depth: rlp.val_at(3)?,
serve_headers: false,
serve_state_since: None,
serve_chain_since: None,
tx_relay: false,
};
let mut parser = Parser {
pos: 4,
rlp: rlp,
};
while let Some((key, item)) = parser.get_next()? {
if Some(key) <= last_key { return Err(DecoderError::Custom("Invalid announcement key ordering")) }
last_key = Some(key);
match key {
Key::ServeHeaders => announcement.serve_headers = true,
Key::ServeStateSince => announcement.serve_state_since = Some(item.as_val()?),
Key::ServeChainSince => announcement.serve_chain_since = Some(item.as_val()?),
Key::TxRelay => announcement.tx_relay = true,
_ => return Err(DecoderError::Custom("Nonsensical key in announcement")),
}
}
Ok(announcement)
}
/// Write an announcement out.
pub fn write_announcement(announcement: &Announcement) -> Vec<u8> {
let mut pairs = Vec::new();
if announcement.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = announcement.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = announcement.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if announcement.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
let mut stream = RlpStream::new_list(4 + pairs.len());
stream
.append(&announcement.head_hash)
.append(&announcement.head_num)
.append(&announcement.head_td)
.append(&announcement.reorg_depth);
for item in pairs {
stream.append_raw(&item, 1);
}
stream.out()
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::buffer_flow::FlowParams;
use util::{U256, H256, FixedHash};
use rlp::{RlpStream, Stream ,UntrustedRlp, View};
#[test]
fn full_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let (read_status, read_capabilities, read_flow)
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn partial_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let (read_status, read_capabilities, read_flow)
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn skip_unknown_keys() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let interleaved = {
let handshake = UntrustedRlp::new(&handshake);
let mut stream = RlpStream::new_list(handshake.item_count() * 3);
for item in handshake.iter() {
stream.append_raw(item.as_raw(), 1);
let (mut s1, mut s2) = (RlpStream::new_list(2), RlpStream::new_list(2));
s1.append(&"foo").append_empty_data();
s2.append(&"bar").append_empty_data();
stream.append_raw(&s1.out(), 1);
stream.append_raw(&s2.out(), 1);
}
stream.out()
};
let (read_status, read_capabilities, read_flow)
= parse_handshake(UntrustedRlp::new(&interleaved)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn announcement_roundtrip() {
let announcement = Announcement {
head_hash: H256::random(),
head_num: 100_000,
head_td: 1_000_000.into(),
reorg_depth: 4,
serve_headers: false,
serve_state_since: Some(99_000),
serve_chain_since: Some(1),
tx_relay: true,
};
let serialized = write_announcement(&announcement);
let read = parse_announcement(UntrustedRlp::new(&serialized)).unwrap();
assert_eq!(read, announcement);
}
#[test]
fn keys_out_of_order() {
use super::{Key, encode_pair, encode_flag};
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10u64)
.append(&100_000u64)
.append(&2u64)
.append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1)
.append_raw(&encode_flag(Key::ServeHeaders), 1);
let out = stream.drain();
assert!(parse_announcement(UntrustedRlp::new(&out)).is_err());
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10u64)
.append(&100_000u64)
.append(&2u64)
.append_raw(&encode_flag(Key::ServeHeaders), 1)
.append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1);
let out = stream.drain();
assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok());
}
#[test]
fn optional_flow() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let handshake = write_handshake(&status, &capabilities, None);
let (read_status, read_capabilities, read_flow)
= parse_handshake(UntrustedRlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert!(read_flow.is_none());
}
}

View File

@@ -1,567 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the `LightProtocol` implementation.
//! These don't test of the higher level logic on top of
use ethcore::blockchain_info::BlockChainInfo;
use ethcore::client::{EachBlockWith, TestBlockChainClient};
use ethcore::ids::BlockId;
use ethcore::transaction::PendingTransaction;
use ethcore::encoded;
use network::{PeerId, NodeId};
use net::buffer_flow::FlowParams;
use net::context::IoContext;
use net::status::{Capabilities, Status, write_handshake};
use net::{encode_request, LightProtocol, Params, packet, Peer};
use provider::Provider;
use request::{self, Request, Headers};
use rlp::*;
use util::{Bytes, H256, U256};
use std::sync::Arc;
// expected result from a call.
#[derive(Debug, PartialEq, Eq)]
enum Expect {
/// Expect to have message sent to peer.
Send(PeerId, u8, Vec<u8>),
/// Expect this response.
Respond(u8, Vec<u8>),
/// Expect a punishment (disconnect/disable)
Punish(PeerId),
/// Expect nothing.
Nothing,
}
impl IoContext for Expect {
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
assert_eq!(self, &Expect::Send(peer, packet_id, packet_body));
}
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
assert_eq!(self, &Expect::Respond(packet_id, packet_body));
}
fn disconnect_peer(&self, peer: PeerId) {
assert_eq!(self, &Expect::Punish(peer));
}
fn disable_peer(&self, peer: PeerId) {
assert_eq!(self, &Expect::Punish(peer));
}
fn protocol_version(&self, _peer: PeerId) -> Option<u8> {
Some(super::MAX_PROTOCOL_VERSION)
}
fn persistent_peer_id(&self, _peer: PeerId) -> Option<NodeId> {
None
}
}
// can't implement directly for Arc due to cross-crate orphan rules.
struct TestProvider(Arc<TestProviderInner>);
struct TestProviderInner {
client: TestBlockChainClient,
}
impl Provider for TestProvider {
fn chain_info(&self) -> BlockChainInfo {
self.0.client.chain_info()
}
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
self.0.client.reorg_depth(a, b)
}
fn earliest_state(&self) -> Option<u64> {
None
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
self.0.client.block_header(id)
}
fn block_body(&self, id: BlockId) -> Option<encoded::Body> {
self.0.client.block_body(id)
}
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
self.0.client.block_receipts(&hash)
}
fn state_proof(&self, req: request::StateProof) -> Vec<Bytes> {
match req.key2 {
Some(_) => vec![::util::sha3::SHA3_NULL_RLP.to_vec()],
None => {
// sort of a leaf node
let mut stream = RlpStream::new_list(2);
stream.append(&req.key1).append_empty_data();
vec![stream.out()]
}
}
}
fn contract_code(&self, req: request::ContractCode) -> Bytes {
req.account_key.iter().chain(req.account_key.iter()).cloned().collect()
}
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
None
}
fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.0.client.ready_transactions()
}
}
fn make_flow_params() -> FlowParams {
FlowParams::new(5_000_000.into(), Default::default(), 100_000.into())
}
fn capabilities() -> Capabilities {
Capabilities {
serve_headers: true,
serve_chain_since: Some(1),
serve_state_since: Some(1),
tx_relay: true,
}
}
// helper for setting up the protocol handler and provider.
fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc<TestProviderInner>, LightProtocol) {
let provider = Arc::new(TestProviderInner {
client: TestBlockChainClient::new(),
});
let proto = LightProtocol::new(Arc::new(TestProvider(provider.clone())), Params {
network_id: 2,
flow_params: flow_params,
capabilities: capabilities,
});
(provider, proto)
}
fn status(chain_info: BlockChainInfo) -> Status {
Status {
protocol_version: 1,
network_id: 2,
head_td: chain_info.total_difficulty,
head_hash: chain_info.best_block_hash,
head_num: chain_info.best_block_number,
genesis_hash: chain_info.genesis_hash,
last_head: None,
}
}
#[test]
fn handshake_expected() {
let flow_params = make_flow_params();
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let status = status(provider.client.chain_info());
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
}
#[test]
#[should_panic]
fn genesis_mismatch() {
let flow_params = make_flow_params();
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let mut status = status(provider.client.chain_info());
status.genesis_hash = H256::default();
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
}
#[test]
fn buffer_overflow() {
let flow_params = make_flow_params();
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
}
{
let my_status = write_handshake(&status, &capabilities, Some(&flow_params));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
}
// 1000 requests is far too many for the default flow params.
let request = encode_request(&Request::Headers(Headers {
start: 1.into(),
max: 1000,
skip: 0,
reverse: false,
}), 111);
proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request);
}
// test the basic request types -- these just make sure that requests are parsed
// and sent to the provider correctly as well as testing response formatting.
#[test]
fn get_block_headers() {
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let cur_status = status(provider.client.chain_info());
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
provider.client.add_blocks(100, EachBlockWith::Nothing);
let cur_status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
}
let request = Headers {
start: 1.into(),
max: 10,
skip: 0,
reverse: false,
};
let req_id = 111;
let request_body = encode_request(&Request::Headers(request.clone()), req_id);
let response = {
let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect();
assert_eq!(headers.len(), 10);
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
let mut response_stream = RlpStream::new_list(3);
response_stream.append(&req_id).append(&new_buf).begin_list(10);
for header in headers {
response_stream.append_raw(&header.into_inner(), 1);
}
response_stream.out()
};
let expected = Expect::Respond(packet::BLOCK_HEADERS, response);
proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body);
}
#[test]
fn get_block_bodies() {
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let cur_status = status(provider.client.chain_info());
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
provider.client.add_blocks(100, EachBlockWith::Nothing);
let cur_status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
}
let request = request::Bodies {
block_hashes: (0..10).map(|i|
provider.client.block_header(BlockId::Number(i)).unwrap().hash()
).collect()
};
let req_id = 111;
let request_body = encode_request(&Request::Bodies(request.clone()), req_id);
let response = {
let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockId::Number(i + 1)).unwrap()).collect();
assert_eq!(bodies.len(), 10);
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
let mut response_stream = RlpStream::new_list(3);
response_stream.append(&req_id).append(&new_buf).begin_list(10);
for body in bodies {
response_stream.append_raw(&body.into_inner(), 1);
}
response_stream.out()
};
let expected = Expect::Respond(packet::BLOCK_BODIES, response);
proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body);
}
#[test]
fn get_block_receipts() {
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let cur_status = status(provider.client.chain_info());
let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params));
provider.client.add_blocks(1000, EachBlockWith::Nothing);
let cur_status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
}
// find the first 10 block hashes starting with `f` because receipts are only provided
// by the test client in that case.
let block_hashes: Vec<_> = (0..1000).map(|i|
provider.client.block_header(BlockId::Number(i)).unwrap().hash()
).filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect();
let request = request::Receipts {
block_hashes: block_hashes.clone(),
};
let req_id = 111;
let request_body = encode_request(&Request::Receipts(request.clone()), req_id);
let response = {
let receipts: Vec<_> = block_hashes.iter()
.map(|hash| provider.client.block_receipts(hash).unwrap())
.collect();
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
let mut response_stream = RlpStream::new_list(3);
response_stream.append(&req_id).append(&new_buf).begin_list(receipts.len());
for block_receipts in receipts {
response_stream.append_raw(&block_receipts, 1);
}
response_stream.out()
};
let expected = Expect::Respond(packet::RECEIPTS, response);
proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body);
}
#[test]
fn get_state_proofs() {
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let cur_status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone()));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body);
}
let req_id = 112;
let key1 = U256::from(11223344).into();
let key2 = U256::from(99988887).into();
let request = Request::StateProofs (request::StateProofs {
requests: vec![
request::StateProof { block: H256::default(), key1: key1, key2: None, from_level: 0 },
request::StateProof { block: H256::default(), key1: key1, key2: Some(key2), from_level: 0},
]
});
let request_body = encode_request(&request, req_id);
let response = {
let proofs = vec![
{ let mut stream = RlpStream::new_list(2); stream.append(&key1).append_empty_data(); vec![stream.out()] },
vec![::util::sha3::SHA3_NULL_RLP.to_vec()],
];
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
let mut response_stream = RlpStream::new_list(3);
response_stream.append(&req_id).append(&new_buf).begin_list(2);
for proof in proofs {
response_stream.begin_list(proof.len());
for node in proof {
response_stream.append_raw(&node, 1);
}
}
response_stream.out()
};
let expected = Expect::Respond(packet::PROOFS, response);
proto.handle_packet(&expected, &1, packet::GET_PROOFS, &request_body);
}
#[test]
fn get_contract_code() {
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let cur_status = status(provider.client.chain_info());
{
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone()));
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body);
}
let req_id = 112;
let key1 = U256::from(11223344).into();
let key2 = U256::from(99988887).into();
let request = Request::Codes (request::ContractCodes {
code_requests: vec![
request::ContractCode { block_hash: H256::default(), account_key: key1 },
request::ContractCode { block_hash: H256::default(), account_key: key2 },
],
});
let request_body = encode_request(&request, req_id);
let response = {
let codes: Vec<Vec<_>> = vec![
key1.iter().chain(key1.iter()).cloned().collect(),
key2.iter().chain(key2.iter()).cloned().collect(),
];
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
let mut response_stream = RlpStream::new_list(3);
response_stream.append(&req_id).append(&new_buf).begin_list(2);
for code in codes {
response_stream.append(&code);
}
response_stream.out()
};
let expected = Expect::Respond(packet::CONTRACT_CODES, response);
proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body);
}
#[test]
fn id_guard() {
use super::request_set::RequestSet;
use super::ReqId;
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
let capabilities = capabilities();
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
let req_id_1 = ReqId(5143);
let req_id_2 = ReqId(1111);
let req = Request::Headers(request::Headers {
start: 5u64.into(),
max: 100,
skip: 0,
reverse: false,
});
let peer_id = 9876;
let mut pending_requests = RequestSet::default();
pending_requests.insert(req_id_1, req.clone(), ::time::SteadyTime::now());
pending_requests.insert(req_id_2, req, ::time::SteadyTime::now());
proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer {
local_buffer: flow_params.create_buffer(),
status: status(provider.client.chain_info()),
capabilities: capabilities.clone(),
remote_flow: Some((flow_params.create_buffer(), flow_params)),
sent_head: provider.client.chain_info().best_block_hash,
last_update: ::time::SteadyTime::now(),
pending_requests: pending_requests,
failed_requests: Vec::new(),
}));
// first, supply wrong request type.
{
let mut stream = RlpStream::new_list(3);
stream.append(&req_id_1.0);
stream.append(&4_000_000usize);
stream.begin_list(0);
let packet = stream.out();
assert!(proto.block_bodies(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_err());
}
// next, do an unexpected response.
{
let mut stream = RlpStream::new_list(3);
stream.append(&10000usize);
stream.append(&3_000_000usize);
stream.begin_list(0);
let packet = stream.out();
assert!(proto.receipts(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_err());
}
// lastly, do a valid (but empty) response.
{
let mut stream = RlpStream::new_list(3);
stream.append(&req_id_2.0);
stream.append(&3_000_000usize);
stream.begin_list(0);
let packet = stream.out();
assert!(proto.block_headers(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_ok());
}
let peers = proto.peers.read();
if let Some(ref peer_info) = peers.get(&peer_id) {
let peer_info = peer_info.lock();
assert!(peer_info.pending_requests.collect_ids::<Vec<_>>().is_empty());
assert_eq!(peer_info.failed_requests, &[req_id_1]);
}
}

View File

@@ -1,737 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! On-demand chain requests over LES. This is a major building block for RPCs.
//! The request service is implemented using Futures. Higher level request handlers
//! will take the raw data received here and extract meaningful results from it.
use std::collections::HashMap;
use std::sync::Arc;
use ethcore::basic_account::BasicAccount;
use ethcore::encoded;
use ethcore::receipt::Receipt;
use futures::{Async, Poll, Future};
use futures::sync::oneshot::{self, Sender, Receiver};
use network::PeerId;
use rlp::{RlpStream, Stream};
use util::{Bytes, RwLock, Mutex, U256};
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP};
use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId};
use cache::Cache;
use types::les_request::{self as les_request, Request as LesRequest};
pub mod request;
// relevant peer info.
struct Peer {
status: Status,
capabilities: Capabilities,
}
// Which portions of a CHT proof should be sent.
enum ChtProofSender {
Both(Sender<(encoded::Header, U256)>),
Header(Sender<encoded::Header>),
ChainScore(Sender<U256>),
}
// Attempted request info and sender to put received value.
enum Pending {
HeaderByNumber(request::HeaderByNumber, ChtProofSender),
HeaderByHash(request::HeaderByHash, Sender<encoded::Header>),
Block(request::Body, Sender<encoded::Block>),
BlockReceipts(request::BlockReceipts, Sender<Vec<Receipt>>),
Account(request::Account, Sender<BasicAccount>),
Code(request::Code, Sender<Bytes>),
}
/// On demand request service. See module docs for more details.
/// Accumulates info about all peers' capabilities and dispatches
/// requests to them accordingly.
pub struct OnDemand {
peers: RwLock<HashMap<PeerId, Peer>>,
pending_requests: RwLock<HashMap<ReqId, Pending>>,
cache: Arc<Mutex<Cache>>,
orphaned_requests: RwLock<Vec<Pending>>,
}
impl OnDemand {
/// Create a new `OnDemand` service with the given cache.
pub fn new(cache: Arc<Mutex<Cache>>) -> Self {
OnDemand {
peers: RwLock::new(HashMap::new()),
pending_requests: RwLock::new(HashMap::new()),
cache: cache,
orphaned_requests: RwLock::new(Vec::new()),
}
}
/// Request a header by block number and CHT root hash.
/// Returns the header.
pub fn header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<encoded::Header> {
let (sender, receiver) = oneshot::channel();
let cached = {
let mut cache = self.cache.lock();
cache.block_hash(&req.num()).and_then(|hash| cache.block_header(&hash))
};
match cached {
Some(hdr) => sender.complete(hdr),
None => self.dispatch_header_by_number(ctx, req, ChtProofSender::Header(sender)),
}
receiver
}
/// Request a canonical block's chain score.
/// Returns the chain score.
pub fn chain_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<U256> {
let (sender, receiver) = oneshot::channel();
let cached = {
let mut cache = self.cache.lock();
cache.block_hash(&req.num()).and_then(|hash| cache.chain_score(&hash))
};
match cached {
Some(score) => sender.complete(score),
None => self.dispatch_header_by_number(ctx, req, ChtProofSender::ChainScore(sender)),
}
receiver
}
/// Request a canonical block's chain score.
/// Returns the header and chain score.
pub fn header_and_score_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber) -> Receiver<(encoded::Header, U256)> {
let (sender, receiver) = oneshot::channel();
let cached = {
let mut cache = self.cache.lock();
let hash = cache.block_hash(&req.num());
(
hash.clone().and_then(|hash| cache.block_header(&hash)),
hash.and_then(|hash| cache.chain_score(&hash)),
)
};
match cached {
(Some(hdr), Some(score)) => sender.complete((hdr, score)),
_ => self.dispatch_header_by_number(ctx, req, ChtProofSender::Both(sender)),
}
receiver
}
// dispatch the request, completing the request if no peers available.
fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: ChtProofSender) {
let num = req.num();
let cht_num = req.cht_num();
let les_req = LesRequest::HeaderProofs(les_request::HeaderProofs {
requests: vec![les_request::HeaderProof {
cht_number: cht_num,
block_number: num,
from_level: 0,
}],
});
let pending = Pending::HeaderByNumber(req, sender);
// we're looking for a peer with serveHeaders who's far enough along in the
// chain.
for (id, peer) in self.peers.read().iter() {
if peer.capabilities.serve_headers && peer.status.head_num >= num {
match ctx.request_from(*id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending,
);
return
},
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
/// Request a header by hash. This is less accurate than by-number because we don't know
/// where in the chain this header lies, and therefore can't find a peer who is supposed to have
/// it as easily.
pub fn header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash) -> Receiver<encoded::Header> {
let (sender, receiver) = oneshot::channel();
match self.cache.lock().block_header(&req.0) {
Some(hdr) => sender.complete(hdr),
None => self.dispatch_header_by_hash(ctx, req, sender),
}
receiver
}
fn dispatch_header_by_hash(&self, ctx: &BasicContext, req: request::HeaderByHash, sender: Sender<encoded::Header>) {
let les_req = LesRequest::Headers(les_request::Headers {
start: req.0.into(),
max: 1,
skip: 0,
reverse: false,
});
// all we've got is a hash, so we'll just guess at peers who might have
// it randomly.
let mut potential_peers = self.peers.read().iter()
.filter(|&(_, peer)| peer.capabilities.serve_headers)
.map(|(id, _)| *id)
.collect::<Vec<_>>();
let mut rng = ::rand::thread_rng();
::rand::Rng::shuffle(&mut rng, &mut potential_peers);
let pending = Pending::HeaderByHash(req, sender);
for id in potential_peers {
match ctx.request_from(id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending,
);
return
}
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
/// Request a block, given its header. Block bodies are requestable by hash only,
/// and the header is required anyway to verify and complete the block body
/// -- this just doesn't obscure the network query.
pub fn block(&self, ctx: &BasicContext, req: request::Body) -> Receiver<encoded::Block> {
let (sender, receiver) = oneshot::channel();
// fast path for empty body.
if req.header.transactions_root() == SHA3_NULL_RLP && req.header.uncles_hash() == SHA3_EMPTY_LIST_RLP {
let mut stream = RlpStream::new_list(3);
stream.append_raw(&req.header.into_inner(), 1);
stream.begin_list(0);
stream.begin_list(0);
sender.complete(encoded::Block::new(stream.out()))
} else {
match self.cache.lock().block_body(&req.hash) {
Some(body) => {
let mut stream = RlpStream::new_list(3);
stream.append_raw(&req.header.into_inner(), 1);
stream.append_raw(&body.into_inner(), 2);
sender.complete(encoded::Block::new(stream.out()));
}
None => self.dispatch_block(ctx, req, sender),
}
}
receiver
}
fn dispatch_block(&self, ctx: &BasicContext, req: request::Body, sender: Sender<encoded::Block>) {
let num = req.header.number();
let les_req = LesRequest::Bodies(les_request::Bodies {
block_hashes: vec![req.hash],
});
let pending = Pending::Block(req, sender);
// we're looking for a peer with serveChainSince(num)
for (id, peer) in self.peers.read().iter() {
if peer.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x >= num) {
match ctx.request_from(*id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending,
);
return
}
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
/// Request the receipts for a block. The header serves two purposes:
/// provide the block hash to fetch receipts for, and for verification of the receipts root.
pub fn block_receipts(&self, ctx: &BasicContext, req: request::BlockReceipts) -> Receiver<Vec<Receipt>> {
let (sender, receiver) = oneshot::channel();
// fast path for empty receipts.
if req.0.receipts_root() == SHA3_NULL_RLP {
sender.complete(Vec::new())
} else {
match self.cache.lock().block_receipts(&req.0.hash()) {
Some(receipts) => sender.complete(receipts),
None => self.dispatch_block_receipts(ctx, req, sender),
}
}
receiver
}
fn dispatch_block_receipts(&self, ctx: &BasicContext, req: request::BlockReceipts, sender: Sender<Vec<Receipt>>) {
let num = req.0.number();
let les_req = LesRequest::Receipts(les_request::Receipts {
block_hashes: vec![req.0.hash()],
});
let pending = Pending::BlockReceipts(req, sender);
// we're looking for a peer with serveChainSince(num)
for (id, peer) in self.peers.read().iter() {
if peer.capabilities.serve_chain_since.as_ref().map_or(false, |x| *x >= num) {
match ctx.request_from(*id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending,
);
return
}
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
/// Request an account by address and block header -- which gives a hash to query and a state root
/// to verify against.
pub fn account(&self, ctx: &BasicContext, req: request::Account) -> Receiver<BasicAccount> {
let (sender, receiver) = oneshot::channel();
self.dispatch_account(ctx, req, sender);
receiver
}
fn dispatch_account(&self, ctx: &BasicContext, req: request::Account, sender: Sender<BasicAccount>) {
let num = req.header.number();
let les_req = LesRequest::StateProofs(les_request::StateProofs {
requests: vec![les_request::StateProof {
block: req.header.hash(),
key1: ::util::Hashable::sha3(&req.address),
key2: None,
from_level: 0,
}],
});
let pending = Pending::Account(req, sender);
// we're looking for a peer with serveStateSince(num)
for (id, peer) in self.peers.read().iter() {
if peer.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= num) {
match ctx.request_from(*id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending,
);
return
}
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
/// Request code by address, known code hash, and block header.
pub fn code(&self, ctx: &BasicContext, req: request::Code) -> Receiver<Bytes> {
let (sender, receiver) = oneshot::channel();
// fast path for no code.
if req.code_hash == ::util::sha3::SHA3_EMPTY {
sender.complete(Vec::new())
} else {
self.dispatch_code(ctx, req, sender);
}
receiver
}
fn dispatch_code(&self, ctx: &BasicContext, req: request::Code, sender: Sender<Bytes>) {
let num = req.block_id.1;
let les_req = LesRequest::Codes(les_request::ContractCodes {
code_requests: vec![les_request::ContractCode {
block_hash: req.block_id.0,
account_key: ::util::Hashable::sha3(&req.address),
}]
});
let pending = Pending::Code(req, sender);
// we're looking for a peer with serveStateSince(num)
for (id, peer) in self.peers.read().iter() {
if peer.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= num) {
match ctx.request_from(*id, les_req.clone()) {
Ok(req_id) => {
trace!(target: "on_demand", "Assigning request to peer {}", id);
self.pending_requests.write().insert(
req_id,
pending
);
return
}
Err(e) =>
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
}
}
}
trace!(target: "on_demand", "No suitable peer for request");
self.orphaned_requests.write().push(pending)
}
// dispatch orphaned requests, and discard those for which the corresponding
// receiver has been dropped.
fn dispatch_orphaned(&self, ctx: &BasicContext) {
// wrapper future for calling `poll_cancel` on our `Senders` to preserve
// the invariant that it's always within a task.
struct CheckHangup<'a, T: 'a>(&'a mut Sender<T>);
impl<'a, T: 'a> Future for CheckHangup<'a, T> {
type Item = bool;
type Error = ();
fn poll(&mut self) -> Poll<bool, ()> {
Ok(Async::Ready(match self.0.poll_cancel() {
Ok(Async::NotReady) => false, // hasn't hung up.
_ => true, // has hung up.
}))
}
}
// check whether a sender's hung up (using `wait` to preserve the task invariant)
// returns true if has hung up, false otherwise.
fn check_hangup<T>(send: &mut Sender<T>) -> bool {
CheckHangup(send).wait().expect("CheckHangup always returns ok; qed")
}
if self.orphaned_requests.read().is_empty() { return }
let to_dispatch = ::std::mem::replace(&mut *self.orphaned_requests.write(), Vec::new());
for orphaned in to_dispatch {
match orphaned {
Pending::HeaderByNumber(req, mut sender) => {
let hangup = match sender {
ChtProofSender::Both(ref mut s) => check_hangup(s),
ChtProofSender::Header(ref mut s) => check_hangup(s),
ChtProofSender::ChainScore(ref mut s) => check_hangup(s),
};
if !hangup { self.dispatch_header_by_number(ctx, req, sender) }
}
Pending::HeaderByHash(req, mut sender) =>
if !check_hangup(&mut sender) { self.dispatch_header_by_hash(ctx, req, sender) },
Pending::Block(req, mut sender) =>
if !check_hangup(&mut sender) { self.dispatch_block(ctx, req, sender) },
Pending::BlockReceipts(req, mut sender) =>
if !check_hangup(&mut sender) { self.dispatch_block_receipts(ctx, req, sender) },
Pending::Account(req, mut sender) =>
if !check_hangup(&mut sender) { self.dispatch_account(ctx, req, sender) },
Pending::Code(req, mut sender) =>
if !check_hangup(&mut sender) { self.dispatch_code(ctx, req, sender) },
}
}
}
}
impl Handler for OnDemand {
fn on_connect(&self, ctx: &EventContext, status: &Status, capabilities: &Capabilities) {
self.peers.write().insert(ctx.peer(), Peer { status: status.clone(), capabilities: capabilities.clone() });
self.dispatch_orphaned(ctx.as_basic());
}
fn on_disconnect(&self, ctx: &EventContext, unfulfilled: &[ReqId]) {
self.peers.write().remove(&ctx.peer());
let ctx = ctx.as_basic();
{
let mut orphaned = self.orphaned_requests.write();
for unfulfilled in unfulfilled {
if let Some(pending) = self.pending_requests.write().remove(unfulfilled) {
trace!(target: "on_demand", "Attempting to reassign dropped request");
orphaned.push(pending);
}
}
}
self.dispatch_orphaned(ctx);
}
fn on_announcement(&self, ctx: &EventContext, announcement: &Announcement) {
let mut peers = self.peers.write();
if let Some(ref mut peer) = peers.get_mut(&ctx.peer()) {
peer.status.update_from(&announcement);
peer.capabilities.update_from(&announcement);
}
self.dispatch_orphaned(ctx.as_basic());
}
fn on_header_proofs(&self, ctx: &EventContext, req_id: ReqId, proofs: &[(Bytes, Vec<Bytes>)]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::HeaderByNumber(req, sender) => {
if let Some(&(ref header, ref proof)) = proofs.get(0) {
match req.check_response(header, proof) {
Ok((header, score)) => {
let mut cache = self.cache.lock();
let hash = header.hash();
cache.insert_block_header(hash, header.clone());
cache.insert_block_hash(header.number(), hash);
cache.insert_chain_score(hash, score);
match sender {
ChtProofSender::Both(sender) => sender.complete((header, score)),
ChtProofSender::Header(sender) => sender.complete(header),
ChtProofSender::ChainScore(sender) => sender.complete(score),
}
return
}
Err(e) => {
warn!("Error handling response for header request: {:?}", e);
ctx.disable_peer(peer);
}
}
}
self.dispatch_header_by_number(ctx.as_basic(), req, sender);
}
_ => panic!("Only header by number request fetches header proofs; qed"),
}
}
fn on_block_headers(&self, ctx: &EventContext, req_id: ReqId, headers: &[Bytes]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::HeaderByHash(req, sender) => {
if let Some(ref header) = headers.get(0) {
match req.check_response(header) {
Ok(header) => {
self.cache.lock().insert_block_header(req.0, header.clone());
sender.complete(header);
return
}
Err(e) => {
warn!("Error handling response for header request: {:?}", e);
ctx.disable_peer(peer);
}
}
}
self.dispatch_header_by_hash(ctx.as_basic(), req, sender);
}
_ => panic!("Only header by hash request fetches headers; qed"),
}
}
fn on_block_bodies(&self, ctx: &EventContext, req_id: ReqId, bodies: &[Bytes]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::Block(req, sender) => {
if let Some(ref body) = bodies.get(0) {
match req.check_response(body) {
Ok(block) => {
let body = encoded::Body::new(body.to_vec());
self.cache.lock().insert_block_body(req.hash, body);
sender.complete(block);
return
}
Err(e) => {
warn!("Error handling response for block request: {:?}", e);
ctx.disable_peer(peer);
}
}
}
self.dispatch_block(ctx.as_basic(), req, sender);
}
_ => panic!("Only block request fetches bodies; qed"),
}
}
fn on_receipts(&self, ctx: &EventContext, req_id: ReqId, receipts: &[Vec<Receipt>]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::BlockReceipts(req, sender) => {
if let Some(ref receipts) = receipts.get(0) {
match req.check_response(receipts) {
Ok(receipts) => {
let hash = req.0.hash();
self.cache.lock().insert_block_receipts(hash, receipts.clone());
sender.complete(receipts);
return
}
Err(e) => {
warn!("Error handling response for receipts request: {:?}", e);
ctx.disable_peer(peer);
}
}
}
self.dispatch_block_receipts(ctx.as_basic(), req, sender);
}
_ => panic!("Only receipts request fetches receipts; qed"),
}
}
fn on_state_proofs(&self, ctx: &EventContext, req_id: ReqId, proofs: &[Vec<Bytes>]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::Account(req, sender) => {
if let Some(ref proof) = proofs.get(0) {
match req.check_response(proof) {
Ok(proof) => {
sender.complete(proof);
return
}
Err(e) => {
warn!("Error handling response for state request: {:?}", e);
ctx.disable_peer(peer);
}
}
}
self.dispatch_account(ctx.as_basic(), req, sender);
}
_ => panic!("Only account request fetches state proof; qed"),
}
}
fn on_code(&self, ctx: &EventContext, req_id: ReqId, codes: &[Bytes]) {
let peer = ctx.peer();
let req = match self.pending_requests.write().remove(&req_id) {
Some(req) => req,
None => return,
};
match req {
Pending::Code(req, sender) => {
if let Some(code) = codes.get(0) {
match req.check_response(code.as_slice()) {
Ok(()) => {
sender.complete(code.clone());
return
}
Err(e) => {
warn!("Error handling response for code request: {:?}", e);
ctx.disable_peer(peer);
}
}
self.dispatch_code(ctx.as_basic(), req, sender);
}
}
_ => panic!("Only code request fetches code; qed"),
}
}
fn tick(&self, ctx: &BasicContext) {
self.dispatch_orphaned(ctx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use cache::Cache;
use net::{Announcement, BasicContext, ReqId, Error as LesError};
use request::{Request as LesRequest, Kind as LesRequestKind};
use network::{PeerId, NodeId};
use time::Duration;
use util::{H256, Mutex};
struct FakeContext;
impl BasicContext for FakeContext {
fn persistent_peer_id(&self, _: PeerId) -> Option<NodeId> { None }
fn request_from(&self, _: PeerId, _: LesRequest) -> Result<ReqId, LesError> {
unimplemented!()
}
fn make_announcement(&self, _: Announcement) { }
fn max_requests(&self, _: PeerId, _: LesRequestKind) -> usize { 0 }
fn disconnect_peer(&self, _: PeerId) { }
fn disable_peer(&self, _: PeerId) { }
}
#[test]
fn detects_hangup() {
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::hours(6))));
let on_demand = OnDemand::new(cache);
let result = on_demand.header_by_hash(&FakeContext, request::HeaderByHash(H256::default()));
assert!(on_demand.orphaned_requests.read().len() == 1);
drop(result);
on_demand.dispatch_orphaned(&FakeContext);
assert!(on_demand.orphaned_requests.read().is_empty());
}
}

View File

@@ -1,389 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Request types, verification, and verification errors.
use ethcore::basic_account::BasicAccount;
use ethcore::encoded;
use ethcore::receipt::Receipt;
use rlp::{RlpStream, Stream, UntrustedRlp, View};
use util::{Address, Bytes, HashDB, H256, U256};
use util::memorydb::MemoryDB;
use util::sha3::Hashable;
use util::trie::{Trie, TrieDB, TrieError};
/// Errors in verification.
#[derive(Debug, PartialEq)]
pub enum Error {
/// RLP decoder error.
Decoder(::rlp::DecoderError),
/// Trie lookup error (result of bad proof)
Trie(TrieError),
/// Bad inclusion proof
BadProof,
/// Wrong header number.
WrongNumber(u64, u64),
/// Wrong hash.
WrongHash(H256, H256),
/// Wrong trie root.
WrongTrieRoot(H256, H256),
}
impl From<::rlp::DecoderError> for Error {
fn from(err: ::rlp::DecoderError) -> Self {
Error::Decoder(err)
}
}
impl From<Box<TrieError>> for Error {
fn from(err: Box<TrieError>) -> Self {
Error::Trie(*err)
}
}
/// Request for a header by number.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderByNumber {
/// The header's number.
num: u64,
/// The cht number for the given block number.
cht_num: u64,
/// The root of the CHT containing this header.
cht_root: H256,
}
impl HeaderByNumber {
/// Construct a new header-by-number request. Fails if the given number is 0.
/// Provide the expected CHT root to compare against.
pub fn new(num: u64, cht_root: H256) -> Option<Self> {
::cht::block_to_cht_number(num).map(|cht_num| HeaderByNumber {
num: num,
cht_num: cht_num,
cht_root: cht_root,
})
}
/// Access the requested block number.
pub fn num(&self) -> u64 { self.num }
/// Access the CHT number.
pub fn cht_num(&self) -> u64 { self.cht_num }
/// Access the expected CHT root.
pub fn cht_root(&self) -> H256 { self.cht_root }
/// Check a response with a header and cht proof.
pub fn check_response(&self, header: &[u8], proof: &[Bytes]) -> Result<(encoded::Header, U256), Error> {
let (expected_hash, td) = match ::cht::check_proof(proof, self.num, self.cht_root) {
Some((expected_hash, td)) => (expected_hash, td),
None => return Err(Error::BadProof),
};
// and compare the hash to the found header.
let found_hash = header.sha3();
match expected_hash == found_hash {
true => Ok((encoded::Header::new(header.to_vec()), td)),
false => Err(Error::WrongHash(expected_hash, found_hash)),
}
}
}
/// Request for a header by hash.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderByHash(pub H256);
impl HeaderByHash {
/// Check a response for the header.
pub fn check_response(&self, header: &[u8]) -> Result<encoded::Header, Error> {
let hash = header.sha3();
match hash == self.0 {
true => Ok(encoded::Header::new(header.to_vec())),
false => Err(Error::WrongHash(self.0, hash)),
}
}
}
/// Request for a block, with header and precomputed hash.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Body {
/// The block's header.
pub header: encoded::Header,
/// The block's hash.
pub hash: H256,
}
impl Body {
/// Create a request for a block body from a given header.
pub fn new(header: encoded::Header) -> Self {
let hash = header.hash();
Body {
header: header,
hash: hash,
}
}
/// Check a response for this block body.
pub fn check_response(&self, body: &[u8]) -> Result<encoded::Block, Error> {
let body_view = UntrustedRlp::new(&body);
// check the integrity of the the body against the header
let tx_root = ::util::triehash::ordered_trie_root(body_view.at(0)?.iter().map(|r| r.as_raw().to_vec()));
if tx_root != self.header.transactions_root() {
return Err(Error::WrongTrieRoot(self.header.transactions_root(), tx_root));
}
let uncles_hash = body_view.at(1)?.as_raw().sha3();
if uncles_hash != self.header.uncles_hash() {
return Err(Error::WrongHash(self.header.uncles_hash(), uncles_hash));
}
// concatenate the header and the body.
let mut stream = RlpStream::new_list(3);
stream.append_raw(self.header.rlp().as_raw(), 1);
stream.append_raw(body, 2);
Ok(encoded::Block::new(stream.out()))
}
}
/// Request for a block's receipts with header for verification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockReceipts(pub encoded::Header);
impl BlockReceipts {
/// Check a response with receipts against the stored header.
pub fn check_response(&self, receipts: &[Receipt]) -> Result<Vec<Receipt>, Error> {
let receipts_root = self.0.receipts_root();
let found_root = ::util::triehash::ordered_trie_root(receipts.iter().map(|r| ::rlp::encode(r).to_vec()));
match receipts_root == found_root {
true => Ok(receipts.to_vec()),
false => Err(Error::WrongTrieRoot(receipts_root, found_root)),
}
}
}
/// Request for an account structure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Account {
/// Header for verification.
pub header: encoded::Header,
/// Address requested.
pub address: Address,
}
impl Account {
/// Check a response with an account against the stored header.
pub fn check_response(&self, proof: &[Bytes]) -> Result<BasicAccount, Error> {
let state_root = self.header.state_root();
let mut db = MemoryDB::new();
for node in proof { db.insert(&node[..]); }
match TrieDB::new(&db, &state_root).and_then(|t| t.get(&self.address.sha3()))? {
Some(val) => {
let rlp = UntrustedRlp::new(&val);
Ok(BasicAccount {
nonce: rlp.val_at(0)?,
balance: rlp.val_at(1)?,
storage_root: rlp.val_at(2)?,
code_hash: rlp.val_at(3)?,
})
},
None => Err(Error::BadProof)
}
}
}
/// Request for account code.
pub struct Code {
/// Block hash, number pair.
pub block_id: (H256, u64),
/// Address requested.
pub address: Address,
/// Account's code hash.
pub code_hash: H256,
}
impl Code {
/// Check a response with code against the code hash.
pub fn check_response(&self, code: &[u8]) -> Result<(), Error> {
let found_hash = code.sha3();
if found_hash == self.code_hash {
Ok(())
} else {
Err(Error::WrongHash(self.code_hash, found_hash))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use util::{MemoryDB, Address, H256, FixedHash};
use util::trie::{Trie, TrieMut, SecTrieDB, SecTrieDBMut};
use util::trie::recorder::Recorder;
use ethcore::client::{BlockChainClient, TestBlockChainClient, EachBlockWith};
use ethcore::header::Header;
use ethcore::encoded;
use ethcore::receipt::Receipt;
#[test]
fn no_invalid_header_by_number() {
assert!(HeaderByNumber::new(0, Default::default()).is_none())
}
#[test]
fn check_header_by_number() {
use ::cht;
let test_client = TestBlockChainClient::new();
test_client.add_blocks(10500, EachBlockWith::Nothing);
let cht = {
let fetcher = |id| {
let hdr = test_client.block_header(id).unwrap();
let td = test_client.block_total_difficulty(id).unwrap();
Some(cht::BlockInfo {
hash: hdr.hash(),
parent_hash: hdr.parent_hash(),
total_difficulty: td,
})
};
cht::build(cht::block_to_cht_number(10_000).unwrap(), fetcher).unwrap()
};
let proof = cht.prove(10_000, 0).unwrap().unwrap();
let req = HeaderByNumber::new(10_000, cht.root()).unwrap();
let raw_header = test_client.block_header(::ethcore::ids::BlockId::Number(10_000)).unwrap();
assert!(req.check_response(&raw_header.into_inner(), &proof[..]).is_ok());
}
#[test]
fn check_header_by_hash() {
let mut header = Header::new();
header.set_number(10_000);
header.set_extra_data(b"test_header".to_vec());
let hash = header.hash();
let raw_header = ::rlp::encode(&header);
assert!(HeaderByHash(hash).check_response(&*raw_header).is_ok())
}
#[test]
fn check_body() {
use rlp::{RlpStream, Stream};
let header = Header::new();
let mut body_stream = RlpStream::new_list(2);
body_stream.begin_list(0).begin_list(0);
let req = Body {
header: encoded::Header::new(::rlp::encode(&header).to_vec()),
hash: header.hash(),
};
assert!(req.check_response(&*body_stream.drain()).is_ok())
}
#[test]
fn check_receipts() {
let receipts = (0..5).map(|_| Receipt {
state_root: Some(H256::random()),
gas_used: 21_000u64.into(),
log_bloom: Default::default(),
logs: Vec::new(),
}).collect::<Vec<_>>();
let mut header = Header::new();
let receipts_root = ::util::triehash::ordered_trie_root(
receipts.iter().map(|x| ::rlp::encode(x).to_vec())
);
header.set_receipts_root(receipts_root);
let req = BlockReceipts(encoded::Header::new(::rlp::encode(&header).to_vec()));
assert!(req.check_response(&receipts).is_ok())
}
#[test]
fn check_state_proof() {
use rlp::{RlpStream, Stream};
let mut root = H256::default();
let mut db = MemoryDB::new();
let mut header = Header::new();
header.set_number(123_456);
header.set_extra_data(b"test_header".to_vec());
let addr = Address::random();
let rand_acc = || {
let mut stream = RlpStream::new_list(4);
stream.append(&2u64)
.append(&100_000_000u64)
.append(&H256::random())
.append(&H256::random());
stream.out()
};
{
let mut trie = SecTrieDBMut::new(&mut db, &mut root);
for _ in 0..100 {
let address = Address::random();
trie.insert(&*address, &rand_acc()).unwrap();
}
trie.insert(&*addr, &rand_acc()).unwrap();
}
let proof = {
let trie = SecTrieDB::new(&db, &root).unwrap();
let mut recorder = Recorder::new();
trie.get_with(&*addr, &mut recorder).unwrap().unwrap();
recorder.drain().into_iter().map(|r| r.data).collect::<Vec<_>>()
};
header.set_state_root(root.clone());
let req = Account {
header: encoded::Header::new(::rlp::encode(&header).to_vec()),
address: addr,
};
assert!(req.check_response(&proof[..]).is_ok());
}
#[test]
fn check_code() {
let code = vec![1u8; 256];
let req = Code {
block_id: (Default::default(), 2),
address: Default::default(),
code_hash: ::util::Hashable::sha3(&code),
};
assert!(req.check_response(&code).is_ok());
assert!(req.check_response(&[]).is_err());
}
}

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