Merge remote-tracking branch 'parity/master' into auth-round-no-mocknet

This commit is contained in:
keorn 2016-10-24 15:32:30 +01:00
commit aa05823afe
1204 changed files with 65392 additions and 2958 deletions

View File

@ -121,7 +121,6 @@ linux-armv7:
stage: build stage: build
image: ethcore/rust-armv7:latest image: ethcore/rust-armv7:latest
only: only:
- master
- beta - beta
- tags - tags
- stable - stable
@ -150,7 +149,6 @@ linux-arm:
stage: build stage: build
image: ethcore/rust-arm:latest image: ethcore/rust-arm:latest
only: only:
- master
- beta - beta
- tags - tags
- stable - stable
@ -179,7 +177,6 @@ linux-armv6:
stage: build stage: build
image: ethcore/rust-armv6:latest image: ethcore/rust-armv6:latest
only: only:
- master
- beta - beta
- tags - tags
- stable - stable
@ -208,7 +205,6 @@ linux-aarch64:
stage: build stage: build
image: ethcore/rust-aarch64:latest image: ethcore/rust-aarch64:latest
only: only:
- master
- beta - beta
- tags - tags
- stable - stable
@ -266,13 +262,14 @@ windows:
- set RUST_BACKTRACE=1 - set RUST_BACKTRACE=1
- set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings
- rustup default stable-x86_64-pc-windows-msvc - rustup default stable-x86_64-pc-windows-msvc
- git submodule update --init
- cargo build --release --verbose - cargo build --release --verbose
- 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/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 - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
- cd nsis - cd nsis
- makensis.exe installer.nsi - makensis.exe installer.nsi
- cp installer.exe InstallParity.exe - copy installer.exe InstallParity.exe
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe - signtool sign /f %keyfile% /p %certpass% InstallParity.exe
- md5sums InstallParity.exe > InstallParity.exe.md5 - md5sums InstallParity.exe > InstallParity.exe.md5
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5 - zip win-installer.zip InstallParity.exe InstallParity.exe.md5
@ -299,8 +296,9 @@ windows:
paths: paths:
- target/release/parity.exe - target/release/parity.exe
- target/release/parity.pdb - target/release/parity.pdb
- nsis/installer.exe - nsis/InstallParity.exe
name: "x86_64-pc-windows-msvc_parity" name: "x86_64-pc-windows-msvc_parity"
allow_failure: true
test-linux: test-linux:
stage: test stage: test
before_script: before_script:
@ -312,3 +310,46 @@ test-linux:
- rust-test - rust-test
dependencies: dependencies:
- linux-stable - linux-stable
js-release:
stage: build
image: ethcore/javascript:latest
only:
- js
- master
- beta
- tags
- stable
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/build.sh
- ./js/scripts/release.sh
tags:
- javascript
js-lint:
stage: test
image: ethcore/javascript:latest
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/lint.sh
tags:
- javascript-test
js-test:
stage: test
image: ethcore/javascript:latest
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/test.sh
tags:
- javascript-test
js-pack:
stage: test
image: ethcore/javascript:latest
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/build.sh
tags:
- javascript-test

3
.gitmodules vendored
View File

@ -2,3 +2,6 @@
path = ethcore/res/ethereum/tests path = ethcore/res/ethereum/tests
url = https://github.com/ethereum/tests.git url = https://github.com/ethereum/tests.git
branch = develop branch = develop
[submodule "js/build"]
path = js/build
url = https://github.com/ethcore/js-precompiled

132
Cargo.lock generated
View File

@ -264,7 +264,7 @@ name = "ethash"
version = "1.4.0" version = "1.4.0"
dependencies = [ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sha3 0.1.0", "sha3 0.1.0",
] ]
@ -295,7 +295,7 @@ dependencies = [
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.0.7 (git+https://github.com/contain-rs/lru-cache)",
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -319,6 +319,9 @@ dependencies = [
[[package]] [[package]]
name = "ethcore-bloom-journal" name = "ethcore-bloom-journal"
version = "0.1.0" version = "0.1.0"
dependencies = [
"siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ethcore-dapps" name = "ethcore-dapps"
@ -332,14 +335,15 @@ dependencies = [
"fetch 0.1.0", "fetch 0.1.0",
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
"parity-dapps-glue 1.4.0",
"parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
"parity-dapps-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-ui 1.4.0",
"parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -364,7 +368,7 @@ dependencies = [
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -453,7 +457,7 @@ dependencies = [
"libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@ -482,7 +486,7 @@ dependencies = [
"fetch 0.1.0", "fetch 0.1.0",
"json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)",
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -505,7 +509,8 @@ dependencies = [
"ethcore-util 1.4.0", "ethcore-util 1.4.0",
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps-signer 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-glue 1.4.0",
"parity-ui 1.4.0",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ws 0.5.2 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)", "ws 0.5.2 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)",
@ -547,8 +552,9 @@ dependencies = [
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)", "rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@ -633,7 +639,7 @@ dependencies = [
"ethcore-util 1.4.0", "ethcore-util 1.4.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -839,7 +845,7 @@ version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -847,8 +853,8 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-http-server" name = "jsonrpc-http-server"
version = "6.1.0" version = "6.1.1"
source = "git+https://github.com/ethcore/jsonrpc-http-server.git#2766c6708f66f6f4e667211461d220b49c0d9fdf" source = "git+https://github.com/ethcore/jsonrpc-http-server.git#ee72e4778583daf901b5692468fc622f46abecb6"
dependencies = [ dependencies = [
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -882,7 +888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.0.9" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -898,9 +904,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "lru-cache" name = "lru-cache"
version = "0.0.7" version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/contain-rs/lru-cache#13255e33c45ceb69a4b143f235a4322df5fb580e"
dependencies = [ dependencies = [
"linked-hash-map 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1164,10 +1170,28 @@ name = "odds"
version = "0.2.12" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "owning_ref"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "parity-dapps" name = "parity-dapps"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7"
dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quasi_codegen 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-dapps-glue"
version = "1.4.0"
dependencies = [ dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1181,38 +1205,21 @@ dependencies = [
[[package]] [[package]]
name = "parity-dapps-home" name = "parity-dapps-home"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7"
dependencies = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
] ]
[[package]] [[package]]
name = "parity-dapps-signer" name = "parity-ui"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc"
dependencies = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-glue 1.4.0",
]
[[package]]
name = "parity-dapps-status"
version = "1.4.0"
source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc"
dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
]
[[package]]
name = "parity-dapps-wallet"
version = "1.4.0"
source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc"
dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
] ]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.2.6" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1221,6 +1228,27 @@ dependencies = [
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "parking_lot"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.7.14" version = "0.7.14"
@ -1406,7 +1434,7 @@ dependencies = [
[[package]] [[package]]
name = "rocksdb" name = "rocksdb"
version = "0.4.5" version = "0.4.5"
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
dependencies = [ dependencies = [
"libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)", "rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)",
@ -1415,7 +1443,7 @@ dependencies = [
[[package]] [[package]]
name = "rocksdb-sys" name = "rocksdb-sys"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
dependencies = [ dependencies = [
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1544,6 +1572,11 @@ dependencies = [
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "siphasher"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.1.3" version = "0.1.3"
@ -1851,7 +1884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "ws" name = "ws"
version = "0.5.2" version = "0.5.2"
source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#afbff59776ce16ccec5ee9e218b8891830ee6fdf" source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#609b21fdab96c8fffedec8699755ce3bea9454cb"
dependencies = [ dependencies = [
"bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)",
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1946,15 +1979,15 @@ dependencies = [
"checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "<none>" "checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "<none>"
"checksum json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)" = "<none>" "checksum json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)" = "<none>"
"checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414" "checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414"
"checksum jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)" = "<none>" "checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)" = "<none>"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
"checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" "checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2"
"checksum linked-hash-map 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "83f7ff3baae999fdf921cccf54b61842bb3b26868d50d02dff48052ebec8dd79" "checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48"
"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
"checksum lru-cache 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "42d50dcb5d9f145df83b1043207e1ac0c37c9c779c4e128ca4655abc3f3cbf8c" "checksum lru-cache 0.0.7 (git+https://github.com/contain-rs/lru-cache)" = "<none>"
"checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" "checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
"checksum mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74cc2587bf97c49f3f5bab62860d6abf3902ca73b66b51d9b049fbdcd727bd2" "checksum mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74cc2587bf97c49f3f5bab62860d6abf3902ca73b66b51d9b049fbdcd727bd2"
@ -1982,12 +2015,12 @@ dependencies = [
"checksum num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "51fedae97a05f7353612fe017ab705a37e6db8f4d67c5c6fe739a9e70d6eed09" "checksum num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "51fedae97a05f7353612fe017ab705a37e6db8f4d67c5c6fe739a9e70d6eed09"
"checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77" "checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77"
"checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1" "checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1"
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
"checksum parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>" "checksum parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>"
"checksum parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>" "checksum parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>"
"checksum parity-dapps-signer 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>" "checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6"
"checksum parity-dapps-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>" "checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125"
"checksum parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
"checksum parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e0fd1be2c3cf5fef20a6d18fec252c4f3c87c14fc3039002eb7d4ed91e436826"
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
"checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d" "checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d"
"checksum phf_generator 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "db005608fd99800c8c74106a7c894cf582055b689aa14a79462cefdcb7dc1cc3" "checksum phf_generator 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "db005608fd99800c8c74106a7c894cf582055b689aa14a79462cefdcb7dc1cc3"
@ -2024,6 +2057,7 @@ dependencies = [
"checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21" "checksum serde_codegen_internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f877e2781ed0a323295d1c9f0e26556117b5a11489fc47b1848dfb98b3173d21"
"checksum serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e10f8a9d94b06cf5d3bef66475f04c8ff90950f1be7004c357ff9472ccbaebc" "checksum serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e10f8a9d94b06cf5d3bef66475f04c8ff90950f1be7004c357ff9472ccbaebc"
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd"
"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" "checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
"checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "<none>" "checksum slab 0.2.0 (git+https://github.com/carllerche/slab?rev=5476efcafb)" = "<none>"
"checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4" "checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4"

View File

@ -58,7 +58,7 @@ version = "0.9"
default-features = false default-features = false
[features] [features]
default = ["ui", "use-precompiled-js", "ipc"] default = ["ui", "use-precompiled-js"]
ui = ["dapps", "ethcore-signer/ui"] ui = ["dapps", "ethcore-signer/ui"]
use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"] use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"]
dapps = ["ethcore-dapps"] dapps = ["ethcore-dapps"]

View File

@ -27,11 +27,14 @@ ethcore-devtools = { path = "../devtools" }
ethcore-rpc = { path = "../rpc" } ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
fetch = { path = "../util/fetch" } fetch = { path = "../util/fetch" }
parity-ui = { path = "./ui" }
parity-dapps-glue = { path = "./js-glue" }
mime = "0.2"
### DEPRECATED
parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } 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-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 } ### /DEPRECATED
mime_guess = { version = "1.6.1" } mime_guess = { version = "1.6.1" }
clippy = { version = "0.0.90", optional = true} clippy = { version = "0.0.90", optional = true}
@ -39,13 +42,11 @@ clippy = { version = "0.0.90", optional = true}
serde_codegen = { version = "0.8", optional = true } serde_codegen = { version = "0.8", optional = true }
[features] [features]
default = ["serde_codegen", "extra-dapps"] default = ["serde_codegen"]
extra-dapps = ["parity-dapps-wallet"]
nightly = ["serde_macros"] nightly = ["serde_macros"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
use-precompiled-js = [ use-precompiled-js = [
"parity-dapps-status/use-precompiled-js", "parity-ui/use-precompiled-js",
"parity-dapps-home/use-precompiled-js", "parity-dapps-home/use-precompiled-js",
"parity-dapps-wallet/use-precompiled-js"
] ]

31
dapps/js-glue/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
description = "Base Package for all Parity built-in dapps"
name = "parity-dapps-glue"
version = "1.4.0"
license = "GPL-3.0"
authors = ["Ethcore <admin@ethcore.io"]
build = "build.rs"
[build-dependencies]
quasi_codegen = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
[dependencies]
glob = { version = "0.2.11" }
mime_guess = { version = "1.6.1" }
aster = { version = "0.17", default-features = false }
quasi = { version = "0.11", default-features = false }
quasi_macros = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
syntex_syntax = { version = "0.33", optional = true }
clippy = { version = "0.0.90", optional = true }
[features]
dev = ["clippy"]
default = ["with-syntex"]
nightly = ["quasi_macros"]
nightly-testing = ["clippy"]
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
use-precompiled-js = []

65
dapps/js-glue/README.md Normal file
View File

@ -0,0 +1,65 @@
# 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".
```

45
dapps/js-glue/build.rs Normal file
View File

@ -0,0 +1,45 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[cfg(feature = "with-syntex")]
mod inner {
extern crate syntex;
extern crate quasi_codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let mut registry = syntex::Registry::new();
quasi_codegen::register(&mut registry);
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.rs");
registry.expand("", &src, &dst).unwrap();
}
}
#[cfg(not(feature = "with-syntex"))]
mod inner {
pub fn main() {}
}
fn main() {
inner::main();
}

View File

@ -0,0 +1,66 @@
#[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

@ -0,0 +1,189 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate aster;
extern crate glob;
extern crate mime_guess;
use self::mime_guess::guess_mime_type;
use std::path::{self, Path, PathBuf};
use std::ops::Deref;
use syntax::ast::{MetaItem, Item};
use syntax::ast;
use syntax::attr;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;
use syntax::print::pprust::{lit_to_string};
use syntax::parse::token::{InternedString};
pub fn expand_webapp_implementation(
cx: &mut ExtCtxt,
span: Span,
meta_item: &MetaItem,
annotatable: &Annotatable,
push: &mut FnMut(Annotatable)
) {
let item = match *annotatable {
Annotatable::Item(ref item) => item,
_ => {
cx.span_err(meta_item.span, "`#[derive(WebAppFiles)]` may only be applied to struct implementations");
return;
},
};
let builder = aster::AstBuilder::new().span(span);
implement_webapp(cx, &builder, &item, push);
}
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
let static_files_dir = extract_path(cx, item);
let src = Path::new("src");
let static_files = {
let mut buf = src.to_path_buf();
buf.push(static_files_dir.deref());
buf
};
let search_location = {
let mut buf = static_files.to_path_buf();
buf.push("**");
buf.push("*");
buf
};
let files = glob::glob(search_location.to_str().expect("Valid UTF8 path"))
.expect("The sources directory is missing.")
.collect::<Result<Vec<PathBuf>, glob::GlobError>>()
.expect("There should be no error when reading a list of files.");
let statements = files
.iter()
.filter(|path_buf| path_buf.is_file())
.map(|path_buf| {
let path = path_buf.as_path();
let filename = path.file_name().and_then(|s| s.to_str()).expect("Only UTF8 paths.");
let mime_type = guess_mime_type(filename).to_string();
let file_path = as_uri(path.strip_prefix(&static_files).ok().expect("Prefix is always there, cause it's absolute path;qed"));
let file_path_in_source = path.to_str().expect("Only UTF8 paths.");
let path_lit = builder.expr().str(file_path.as_str());
let mime_lit = builder.expr().str(mime_type.as_str());
let web_path_lit = builder.expr().str(file_path_in_source);
let separator_lit = builder.expr().str(path::MAIN_SEPARATOR.to_string().as_str());
let concat_id = builder.id("concat!");
let env_id = builder.id("env!");
let macro_id = builder.id("include_bytes!");
let content = quote_expr!(
cx,
$macro_id($concat_id($env_id("CARGO_MANIFEST_DIR"), $separator_lit, $web_path_lit))
);
quote_stmt!(
cx,
files.insert($path_lit, File { path: $path_lit, content_type: $mime_lit, content: $content });
).expect("The statement is always ok, because it just uses literals.")
}).collect::<Vec<ast::Stmt>>();
let type_name = item.ident;
let files_impl = quote_item!(cx,
impl $type_name {
fn files() -> ::std::collections::HashMap<&'static str, File> {
let mut files = ::std::collections::HashMap::new();
$statements
files
}
}
).unwrap();
push(Annotatable::Item(files_impl));
}
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
for meta_items in item.attrs().iter().filter_map(webapp_meta_items) {
for meta_item in meta_items {
match meta_item.node {
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"path" => {
if let Some(s) = get_str_from_lit(cx, name, lit) {
return s.deref().to_owned();
}
},
_ => {},
}
}
}
// default
"web".to_owned()
}
fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<InternedString> {
match lit.node {
ast::LitKind::Str(ref s, _) => Some(s.clone()),
_ => {
cx.span_err(
lit.span,
&format!("webapp annotation `{}` must be a string, not `{}`",
name,
lit_to_string(lit)
)
);
return None;
}
}
}
fn webapp_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
match attr.node.value.node {
ast::MetaItemKind::List(ref name, ref items) if name == &"webapp" => {
attr::mark_used(&attr);
Some(items)
}
_ => None
}
}
fn as_uri(path: &Path) -> String {
let mut s = String::new();
for component in path.iter() {
s.push_str(component.to_str().expect("Only UTF-8 filenames are supported."));
s.push('/');
}
s[0..s.len()-1].into()
}
#[test]
fn should_convert_path_separators_on_all_platforms() {
// given
let p = {
let mut p = PathBuf::new();
p.push("web");
p.push("src");
p.push("index.html");
p
};
// when
let path = as_uri(&p);
// then
assert_eq!(path, "web/src/index.html".to_owned());
}

89
dapps/js-glue/src/js.rs Normal file
View File

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

39
dapps/js-glue/src/lib.rs Normal file
View File

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

View File

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

View File

@ -20,11 +20,15 @@ use endpoint::Handler;
use handlers::{ContentHandler, EchoHandler}; use handlers::{ContentHandler, EchoHandler};
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> { pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned())) 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> { 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())) 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_response(local_domain: &str) -> Box<Handler> { pub fn ping_response(local_domain: &str) -> Box<Handler> {

View File

@ -47,15 +47,17 @@ impl ContentCache {
} }
pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> { pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> {
let mut len = self.cache.len(); let len = self.cache.len();
if len <= expected_size { if len <= expected_size {
return Vec::new(); return Vec::new();
} }
let mut removed = Vec::with_capacity(len - expected_size); let mut removed = Vec::with_capacity(len - expected_size);
while len > expected_size {
let entry = self.cache.pop_front().unwrap(); 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 { match entry.1 {
ContentStatus::Fetching(ref fetch) => { ContentStatus::Fetching(ref fetch) => {
trace!(target: "dapps", "Aborting {} because of limit.", entry.0); trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
@ -73,7 +75,6 @@ impl ContentCache {
} }
removed.push(entry); removed.push(entry);
len -= 1;
} }
removed removed
} }

View File

@ -85,7 +85,7 @@ impl<R: URLHint> ContentFetcher<R> {
// fallback to resolver // fallback to resolver
if let Ok(content_id) = content_id.from_hex() { if let Ok(content_id) = content_id.from_hex() {
// if app_id is valid, but we are syncing always return true. // if app_id is valid, but we are syncing always return true.
if self.sync.is_major_syncing() { if self.sync.is_major_importing() {
return true; return true;
} }
// else try to resolve the app_id // else try to resolve the app_id
@ -99,7 +99,7 @@ impl<R: URLHint> ContentFetcher<R> {
let mut cache = self.cache.lock(); let mut cache = self.cache.lock();
let content_id = path.app_id.clone(); let content_id = path.app_id.clone();
if self.sync.is_major_syncing() { if self.sync.is_major_importing() {
return Box::new(ContentHandler::error( return Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Sync In Progress", "Sync In Progress",

View File

@ -17,7 +17,8 @@
use endpoint::{Endpoints, Endpoint}; use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint; use page::PageEndpoint;
use proxypac::ProxyPac; use proxypac::ProxyPac;
use parity_dapps::WebApp; use parity_dapps::{self, WebApp};
use parity_dapps_glue::WebApp as NewWebApp;
mod cache; mod cache;
mod fs; mod fs;
@ -25,8 +26,8 @@ pub mod urlhint;
pub mod fetcher; pub mod fetcher;
pub mod manifest; pub mod manifest;
extern crate parity_dapps_status;
extern crate parity_dapps_home; extern crate parity_dapps_home;
extern crate parity_ui;
pub const DAPPS_DOMAIN : &'static str = ".parity"; pub const DAPPS_DOMAIN : &'static str = ".parity";
pub const RPC_PATH : &'static str = "rpc"; pub const RPC_PATH : &'static str = "rpc";
@ -48,33 +49,69 @@ pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned())) Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned()))
} }
pub fn all_endpoints(dapps_path: String) -> Endpoints { pub fn all_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins // fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path); 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 // NOTE [ToDr] Dapps will be currently embeded on 8180
wallet_page(&mut pages); pages.insert("ui".into(), Box::new(
PageEndpoint::new_safe_to_embed(NewUi::default(), signer_port)
));
pages.insert("proxy".into(), ProxyPac::boxed());
insert::<parity_dapps_home::App>(&mut pages, "home");
pages pages
} }
#[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) {}
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) { fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default()))); pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
} }
// TODO [ToDr] Temporary wrapper until we get rid of old built-ins.
use std::collections::HashMap;
struct NewUi {
app: parity_ui::App,
files: HashMap<&'static str, parity_dapps::File>,
}
impl Default for NewUi {
fn default() -> Self {
let app = parity_ui::App::default();
let files = {
let mut files = HashMap::new();
for (k, v) in &app.files {
files.insert(*k, parity_dapps::File {
path: v.path,
content: v.content,
content_type: v.content_type,
});
}
files
};
NewUi {
app: app,
files: files,
}
}
}
impl WebApp for NewUi {
fn file(&self, path: &str) -> Option<&parity_dapps::File> {
self.files.get(path)
}
fn info(&self) -> parity_dapps::Info {
let info = self.app.info();
parity_dapps::Info {
name: info.name,
version: info.version,
author: info.author,
description: info.description,
icon_url: info.icon_url,
}
}
}

View File

@ -156,9 +156,9 @@ impl URLHintContract {
} }
let mut it = vec.into_iter(); let mut it = vec.into_iter();
let account_slash_repo = it.next().unwrap(); let account_slash_repo = it.next().expect("element 0 of 3-len vector known to exist; qed");
let commit = it.next().unwrap(); let commit = it.next().expect("element 1 of 3-len vector known to exist; qed");
let owner = it.next().unwrap(); let owner = it.next().expect("element 2 of 3-len vector known to exist qed");
match (account_slash_repo, commit, owner) { match (account_slash_repo, commit, owner) {
(Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => { (Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => {

View File

@ -19,57 +19,60 @@
use std::io::Write; use std::io::Write;
use hyper::{header, server, Decoder, Encoder, Next}; use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream; use hyper::net::HttpStream;
use hyper::mime::Mime;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use util::version; use util::version;
use handlers::add_security_headers;
#[derive(Clone)] #[derive(Clone)]
pub struct ContentHandler { pub struct ContentHandler {
code: StatusCode, code: StatusCode,
content: String, content: String,
mimetype: String, mimetype: Mime,
write_pos: usize, write_pos: usize,
safe_to_embed_at_port: Option<u16>,
} }
impl ContentHandler { impl ContentHandler {
pub fn ok(content: String, mimetype: String) -> Self { pub fn ok(content: String, mimetype: Mime) -> Self {
ContentHandler { Self::new(StatusCode::Ok, content, mimetype)
code: StatusCode::Ok,
content: content,
mimetype: mimetype,
write_pos: 0
}
} }
pub fn not_found(content: String, mimetype: String) -> Self { pub fn not_found(content: String, mimetype: Mime) -> Self {
ContentHandler { Self::new(StatusCode::NotFound, content, mimetype)
code: StatusCode::NotFound,
content: content,
mimetype: mimetype,
write_pos: 0
}
} }
pub fn html(code: StatusCode, content: String) -> Self { pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self {
Self::new(code, content, "text/html".into()) Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at)
} }
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> Self { pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> Self {
Self::error_embeddable(code, title, message, details, None)
}
pub fn error_embeddable(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option<u16>) -> Self {
Self::html(code, format!( Self::html(code, format!(
include_str!("../error_tpl.html"), include_str!("../error_tpl.html"),
title=title, title=title,
message=message, message=message,
details=details.unwrap_or_else(|| ""), details=details.unwrap_or_else(|| ""),
version=version(), version=version(),
)) ), embeddable_at)
} }
pub fn new(code: StatusCode, content: String, mimetype: String) -> Self { 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_at: Option<u16>) -> Self {
ContentHandler { ContentHandler {
code: code, code: code,
content: content, content: content,
mimetype: mimetype, mimetype: mimetype,
write_pos: 0, write_pos: 0,
safe_to_embed_at_port: embeddable_at,
} }
} }
} }
@ -85,7 +88,8 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code); res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap())); res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone());
Next::write() Next::write()
} }

View File

@ -82,7 +82,7 @@ impl server::Handler<HttpStream> for EchoHandler {
// Don't even read the payload if origin is forbidden! // Don't even read the payload if origin is forbidden!
if let Cors::Forbidden = self.cors { if let Cors::Forbidden = self.cors {
self.handler = Some(ContentHandler::ok(String::new(), "text/plain".into())); self.handler = Some(ContentHandler::ok(String::new(), mime!(Text/Plain)));
Next::write() Next::write()
} else { } else {
Next::read() Next::read()
@ -92,7 +92,7 @@ impl server::Handler<HttpStream> for EchoHandler {
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next { fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
match decoder.read_to_string(&mut self.content) { match decoder.read_to_string(&mut self.content) {
Ok(0) => { Ok(0) => {
self.handler = Some(ContentHandler::ok(self.content.clone(), "application/json".into())); self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
Next::write() Next::write()
}, },
Ok(_) => Next::read(), Ok(_) => Next::read(),
@ -114,11 +114,15 @@ impl server::Handler<HttpStream> for EchoHandler {
])); ]));
headers.set(header::AccessControlAllowOrigin::Value(domain.clone())); headers.set(header::AccessControlAllowOrigin::Value(domain.clone()));
} }
self.handler.as_mut().unwrap().on_response(res) self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.on_response(res)
} }
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next { fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.as_mut().unwrap().on_response_writable(encoder) self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.on_response_writable(encoder)
} }
} }

View File

@ -31,6 +31,24 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
use url::Url; use url::Url;
use hyper::{server, header, net, uri}; use hyper::{server, header, net, uri};
/// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option<u16>) {
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header:
if let Some(port) = embeddable_at {
headers.set_raw(
"X-Frame-Options",
vec![format!("ALLOW-FROM http://127.0.0.1:{}", port).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> { pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> {
match *req.uri() { match *req.uri() {
uri::RequestUri::AbsoluteUri(ref url) => { uri::RequestUri::AbsoluteUri(ref url) => {

View File

@ -43,10 +43,8 @@
#![warn(missing_docs)] #![warn(missing_docs)]
#![cfg_attr(feature="nightly", plugin(clippy))] #![cfg_attr(feature="nightly", plugin(clippy))]
#[macro_use]
extern crate log;
extern crate url as url_lib;
extern crate hyper; extern crate hyper;
extern crate url as url_lib;
extern crate unicase; extern crate unicase;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
@ -57,14 +55,24 @@ extern crate jsonrpc_core;
extern crate jsonrpc_http_server; extern crate jsonrpc_http_server;
extern crate mime_guess; extern crate mime_guess;
extern crate rustc_serialize; extern crate rustc_serialize;
extern crate parity_dapps;
extern crate ethcore_rpc; extern crate ethcore_rpc;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate linked_hash_map; extern crate linked_hash_map;
extern crate fetch; extern crate fetch;
#[macro_use]
extern crate log;
#[macro_use]
extern crate mime;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;
extern crate parity_dapps_glue;
// TODO [ToDr] - Deprecate when we get rid of old dapps.
extern crate parity_dapps;
mod endpoint; mod endpoint;
mod apps; mod apps;
mod page; mod page;
@ -92,11 +100,11 @@ static DAPPS_DOMAIN : &'static str = ".parity";
/// Indicates sync status /// Indicates sync status
pub trait SyncStatus: Send + Sync { pub trait SyncStatus: Send + Sync {
/// Returns true if there is a major sync happening. /// Returns true if there is a major sync happening.
fn is_major_syncing(&self) -> bool; fn is_major_importing(&self) -> bool;
} }
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync { impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
fn is_major_syncing(&self) -> bool { self() } fn is_major_importing(&self) -> bool { self() }
} }
/// Webapps HTTP+RPC server build. /// Webapps HTTP+RPC server build.
@ -105,6 +113,7 @@ pub struct ServerBuilder {
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
signer_port: Option<u16>,
} }
impl Extendable for ServerBuilder { impl Extendable for ServerBuilder {
@ -121,6 +130,7 @@ impl ServerBuilder {
handler: Arc::new(IoHandler::new()), handler: Arc::new(IoHandler::new()),
registrar: registrar, registrar: registrar,
sync_status: Arc::new(|| false), sync_status: Arc::new(|| false),
signer_port: None,
} }
} }
@ -129,6 +139,11 @@ impl ServerBuilder {
self.sync_status = status; self.sync_status = status;
} }
/// Change default signer port.
pub fn with_signer_port(&mut self, signer_port: Option<u16>) {
self.signer_port = signer_port;
}
/// Asynchronously start server with no authentication, /// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error. /// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> { pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
@ -138,6 +153,7 @@ impl ServerBuilder {
NoAuth, NoAuth,
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.signer_port.clone(),
self.registrar.clone(), self.registrar.clone(),
self.sync_status.clone(), self.sync_status.clone(),
) )
@ -152,6 +168,7 @@ impl ServerBuilder {
HttpBasicAuth::single_user(username, password), HttpBasicAuth::single_user(username, password),
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.signer_port.clone(),
self.registrar.clone(), self.registrar.clone(),
self.sync_status.clone(), self.sync_status.clone(),
) )
@ -186,13 +203,14 @@ impl Server {
authorization: A, authorization: A,
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
dapps_path: String, dapps_path: String,
signer_port: Option<u16>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
) -> Result<Server, ServerError> { ) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None)); let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization); let authorization = Arc::new(authorization);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status)); let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status));
let endpoints = Arc::new(apps::all_endpoints(dapps_path)); let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port));
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));

View File

@ -25,7 +25,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`) /// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>, pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!) /// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed: bool, safe_to_embed_at_port: Option<u16>,
info: EndpointInfo, info: EndpointInfo,
} }
@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: None, prefix: None,
safe_to_embed: false, safe_to_embed_at_port: None,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: Some(prefix), prefix: Some(prefix),
safe_to_embed: false, safe_to_embed_at_port: None,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe /// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking). /// even from different origin. It might be dangerous (clickjacking).
/// Use wisely! /// Use wisely!
pub fn new_safe_to_embed(app: T) -> Self { pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self {
let info = app.info(); let info = app.info();
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: None, prefix: None,
safe_to_embed: true, safe_to_embed_at_port: port,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -79,8 +79,8 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
app: BuiltinDapp::new(self.app.clone()), app: BuiltinDapp::new(self.app.clone()),
prefix: self.prefix.clone(), prefix: self.prefix.clone(),
path: path, path: path,
file: Default::default(), file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
safe_to_embed: self.safe_to_embed, safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
}) })
} }
} }

View File

@ -22,7 +22,7 @@ use hyper::net::HttpStream;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next}; use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath; use endpoint::EndpointPath;
use handlers::ContentHandler; use handlers::{ContentHandler, add_security_headers};
/// Represents a file that can be sent to client. /// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally. /// Implementation should keep track of bytes already sent internally.
@ -57,13 +57,14 @@ pub enum ServedFile<T: Dapp> {
Error(ContentHandler), Error(ContentHandler),
} }
impl<T: Dapp> Default for ServedFile<T> { impl<T: Dapp> ServedFile<T> {
fn default() -> Self { pub fn new(embeddable_at: Option<u16>) -> Self {
ServedFile::Error(ContentHandler::error( ServedFile::Error(ContentHandler::error_embeddable(
StatusCode::NotFound, StatusCode::NotFound,
"404 Not Found", "404 Not Found",
"Requested dapp resource was not found.", "Requested dapp resource was not found.",
None None,
embeddable_at,
)) ))
} }
} }
@ -81,7 +82,7 @@ pub struct PageHandler<T: Dapp> {
/// Requested path. /// Requested path.
pub path: EndpointPath, pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe). /// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed: bool, pub safe_to_embed_at_port: Option<u16>,
} }
impl<T: Dapp> PageHandler<T> { impl<T: Dapp> PageHandler<T> {
@ -115,7 +116,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
self.app.file(&self.extract_path(url.path())) self.app.file(&self.extract_path(url.path()))
}, },
_ => None, _ => None,
}.map_or_else(|| ServedFile::default(), |f| ServedFile::File(f)); }.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f));
Next::write() Next::write()
} }
@ -127,10 +128,14 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
match self.file { match self.file {
ServedFile::File(ref f) => { ServedFile::File(ref f) => {
res.set_status(StatusCode::Ok); res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
if !self.safe_to_embed { match f.content_type().parse() {
res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
Err(()) => debug!(target: "page_handler", "invalid MIME type: {}", f.content_type()),
} }
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port);
Next::write() Next::write()
}, },
ServedFile::Error(ref mut handler) => { ServedFile::Error(ref mut handler) => {
@ -212,8 +217,8 @@ fn should_extract_path_with_appid() {
port: 8080, port: 8080,
using_dapps_domains: true, using_dapps_domains: true,
}, },
file: Default::default(), file: ServedFile::new(None),
safe_to_embed: true, safe_to_embed_at_port: None,
}; };
// when // when

View File

@ -61,16 +61,16 @@ impl Endpoint for LocalPageEndpoint {
app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() }, app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() },
prefix: None, prefix: None,
path: path, path: path,
file: Default::default(), file: handler::ServedFile::new(None),
safe_to_embed: false, safe_to_embed_at_port: None,
}) })
} else { } else {
Box::new(handler::PageHandler { Box::new(handler::PageHandler {
app: LocalDapp { path: self.path.clone() }, app: LocalDapp { path: self.path.clone() },
prefix: None, prefix: None,
path: path, path: path,
file: Default::default(), file: handler::ServedFile::new(None),
safe_to_embed: false, safe_to_embed_at_port: None,
}) })
} }
} }

View File

@ -42,7 +42,7 @@ function FindProxyForURL(url, host) {{
}} }}
"#, "#,
DAPPS_DOMAIN, path.host, path.port); DAPPS_DOMAIN, path.host, path.port);
Box::new(ContentHandler::ok(content, "application/javascript".to_owned())) Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
} }
} }

View File

@ -81,11 +81,15 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
self.handler = match endpoint { self.handler = match endpoint {
// First check special endpoints // First check special endpoints
(ref path, ref endpoint) if self.special.contains_key(endpoint) => { (ref path, ref endpoint) if self.special.contains_key(endpoint) => {
self.special.get(endpoint).unwrap().to_async_handler(path.clone().unwrap_or_default(), control) self.special.get(endpoint)
.expect("special known to contain key; qed")
.to_async_handler(path.clone().unwrap_or_default(), control)
}, },
// Then delegate to dapp // Then delegate to dapp
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => { (Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
self.endpoints.get(&path.app_id).unwrap().to_async_handler(path.clone(), control) self.endpoints.get(&path.app_id)
.expect("special known to contain key; qed")
.to_async_handler(path.clone(), control)
}, },
// Try to resolve and fetch the dapp // Try to resolve and fetch the dapp
(Some(ref path), _) if self.fetch.contains(&path.app_id) => { (Some(ref path), _) if self.fetch.contains(&path.app_id) => {
@ -108,7 +112,9 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
}, },
// RPC by default // RPC by default
_ => { _ => {
self.special.get(&SpecialEndpoint::Rpc).unwrap().to_async_handler(EndpointPath::default(), control) self.special.get(&SpecialEndpoint::Rpc)
.expect("RPC endpoint always stored; qed")
.to_async_handler(EndpointPath::default(), control)
} }
}; };
@ -143,7 +149,9 @@ impl<A: Authorization> Router<A> {
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
) -> Self { ) -> Self {
let handler = special.get(&SpecialEndpoint::Utils).unwrap().to_handler(EndpointPath::default()); let handler = special.get(&SpecialEndpoint::Utils)
.expect("Utils endpoint always stored; qed")
.to_handler(EndpointPath::default());
Router { Router {
control: Some(control), control: Some(control),
main_page: main_page, main_page: main_page,

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, serve_with_registrar, request}; use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
#[test] #[test]
fn should_return_error() { fn should_return_error() {
@ -36,6 +36,7 @@ fn should_return_error() {
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert_eq!(response.headers.get(0).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_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] #[test]
@ -58,6 +59,7 @@ fn should_serve_apps() {
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json");
assert!(response.body.contains("Parity Home Screen"), response.body); assert!(response.body.contains("Parity Home Screen"), response.body);
assert_security_headers(&response.headers);
} }
#[test] #[test]
@ -80,6 +82,7 @@ fn should_handle_ping() {
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json");
assert_eq!(response.body, "0\n\n".to_owned()); assert_eq!(response.body, "0\n\n".to_owned());
assert_security_headers(&response.headers);
} }
@ -101,5 +104,6 @@ fn should_try_to_resolve_dapp() {
// then // then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2); assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers(&response.headers);
} }

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_with_auth, request}; use tests::helpers::{serve_with_auth, request, assert_security_headers};
#[test] #[test]
fn should_require_authorization() { fn should_require_authorization() {
@ -76,4 +76,5 @@ fn should_allow_on_valid_auth() {
// then // then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_security_headers(&response.headers);
} }

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_with_registrar, request}; use tests::helpers::{serve_with_registrar, request, assert_security_headers};
#[test] #[test]
fn should_resolve_dapp() { fn should_resolve_dapp() {
@ -34,5 +34,6 @@ fn should_resolve_dapp() {
// then // then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2); assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers(&response.headers);
} }

View File

@ -92,3 +92,7 @@ pub fn serve() -> Server {
pub fn request(server: Server, request: &str) -> http_client::Response { pub fn request(server: Server, request: &str) -> http_client::Response {
http_client::request(server.addr(), request) http_client::request(server.addr(), request)
} }
pub fn assert_security_headers(headers: &[String]) {
http_client::assert_security_headers_present(headers)
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, request}; use tests::helpers::{serve, request, assert_security_headers};
#[test] #[test]
fn should_redirect_to_home() { fn should_redirect_to_home() {
@ -74,6 +74,7 @@ fn should_display_404_on_invalid_dapp() {
// then // then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert!(response.body.contains("href=\"/home/")); assert!(response.body.contains("href=\"/home/"));
assert_security_headers(&response.headers);
} }
#[test] #[test]
@ -94,6 +95,7 @@ fn should_display_404_on_invalid_dapp_with_domain() {
// then // then
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert!(response.body.contains("href=\"http://home.parity/")); assert!(response.body.contains("href=\"http://home.parity/"));
assert_security_headers(&response.headers);
} }
#[test] #[test]
@ -160,6 +162,7 @@ fn should_serve_proxy_pac() {
// then // then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body, "86\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); assert_eq!(response.body, "86\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
assert_security_headers(&response.headers);
} }
#[test] #[test]
@ -181,5 +184,6 @@ fn should_serve_utils() {
// then // then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body.contains("function(){"), true); assert_eq!(response.body.contains("function(){"), true);
assert_security_headers(&response.headers);
} }

19
dapps/ui/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
description = "Parity built-in dapps."
name = "parity-ui"
version = "1.4.0"
license = "GPL-3.0"
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[features]
default = ["with-syntex"]
use-precompiled-js = ["parity-dapps-glue/use-precompiled-js"]
with-syntex = ["parity-dapps-glue/with-syntex"]
[build-dependencies]
parity-dapps-glue = { path = "../js-glue" }
[dependencies]
parity-dapps-glue = { path = "../js-glue" }

22
dapps/ui/build.rs Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate parity_dapps_glue;
fn main() {
parity_dapps_glue::js::build(concat!(env!("CARGO_MANIFEST_DIR"), "/../../js"));
parity_dapps_glue::generate();
}

22
dapps/ui/src/lib.rs Normal file
View File

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

55
dapps/ui/src/lib.rs.in Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate parity_dapps_glue;
use std::collections::HashMap;
use parity_dapps_glue::{WebApp, File, Info};
#[derive(WebAppFiles)]
#[webapp(path = "../../../js/build")]
pub struct App {
pub files: HashMap<&'static str, File>,
}
impl Default for App {
fn default() -> App {
App {
files: Self::files(),
}
}
}
impl WebApp for App {
fn file(&self, path: &str) -> Option<&File> {
self.files.get(path)
}
fn info(&self) -> Info {
Info {
name: "Parity UI",
version: env!("CARGO_PKG_VERSION"),
author: "Ethcore <admin@ethcore.io>",
description: "New UI for Parity. (Experimental)",
icon_url: "icon.png",
}
}
}
#[test]
fn test_js() {
parity_dapps_glue::js::build(concat!(env!("CARGO_MANIFEST_DIR"), "/../../js"));
}

View File

@ -64,3 +64,18 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
} }
} }
/// Check if all required security headers are present
pub fn assert_security_headers_present(headers: &[String]) {
assert!(
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
"X-Frame-Options 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

@ -0,0 +1,39 @@
FROM ubuntu:14.04
WORKDIR /build
# install tools and dependencies
RUN apt-get update && \
apt-get install -y \
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

@ -9,4 +9,4 @@ authors = ["arkpar <arkadiy@ethcore.io"]
log = "0.3" log = "0.3"
sha3 = { path = "../util/sha3" } sha3 = { path = "../util/sha3" }
primal = "0.2.3" primal = "0.2.3"
parking_lot = "0.2.6" parking_lot = "0.3"

View File

@ -37,7 +37,7 @@ ethkey = { path = "../ethkey" }
ethcore-ipc-nano = { path = "../ipc/nano" } ethcore-ipc-nano = { path = "../ipc/nano" }
rlp = { path = "../util/rlp" } rlp = { path = "../util/rlp" }
rand = "0.3" rand = "0.3"
lru-cache = "0.0.7" lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
ethcore-bloom-journal = { path = "../util/bloom" } ethcore-bloom-journal = { path = "../util/bloom" }
byteorder = "0.5" byteorder = "0.5"

View File

@ -10,7 +10,8 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x118c30" "homesteadTransition": "0x118c30",
"eip150Transition": "0x2625a0"
} }
} }
}, },

View File

@ -0,0 +1,43 @@
{
"name": "Homestead (Test)",
"engine": {
"Ethash": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"homesteadTransition": "0x0",
"eip150Transition": "0x0"
}
}
},
"params": {
"accountStartNonce": "0x00",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit": "0x1388"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
}
}

View File

@ -11,10 +11,11 @@
"durationLimit": "0x3C", "durationLimit": "0x3C",
"blockReward": "0x6f05b59d3b200000", "blockReward": "0x6f05b59d3b200000",
"registrar" : "0x6c221ca53705f3497ec90ca7b84c59ae7382fc21", "registrar" : "0x6c221ca53705f3497ec90ca7b84c59ae7382fc21",
"frontierCompatibilityModeLimit": "0x30d40", "homesteadTransition": "0x30d40",
"difficultyHardforkTransition": "0x59d9", "difficultyHardforkTransition": "0x59d9",
"difficultyHardforkBoundDivisor": "0x0200", "difficultyHardforkBoundDivisor": "0x0200",
"bombDefuseTransition": "0x30d40" "bombDefuseTransition": "0x30d40",
"eip150Transition": "0x7fffffffffffffff"
} }
} }
}, },

View File

@ -8,8 +8,8 @@
"difficultyBoundDivisor": "0x0800", "difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea",
"frontierCompatibilityModeLimit": "0x118c30", "homesteadTransition": "0x118c30",
"daoHardforkTransition": "0x1d4c00", "daoHardforkTransition": "0x1d4c00",
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
"daoHardforkAccounts": [ "daoHardforkAccounts": [
@ -129,7 +129,8 @@
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97", "0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a" "0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
] ],
"eip150Transition": "0x259518"
} }
} }
}, },
@ -163,7 +164,8 @@
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",
"enode://248f12bc8b18d5289358085520ac78cd8076485211e6d96ab0bc93d6cd25442db0ce3a937dc404f64f207b0b9aed50e25e98ce32af5ac7cb321ff285b97de485@zero.parity.io:30303" "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@zero.parity.io:30303",
"enode://cc92c4c40d612a10c877ca023ef0496c843fbc92b6c6c0d55ce0b863d51d821c4bd70daebb54324a6086374e6dc05708fed39862b275f169cb678e655da9d07d@136.243.154.246:30303"
], ],
"accounts": { "accounts": {
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@ -9,7 +9,7 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x118c30", "homesteadTransition": "0x118c30",
"daoHardforkTransition": "0x1d4c00", "daoHardforkTransition": "0x1d4c00",
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
"daoHardforkAccounts": [ "daoHardforkAccounts": [
@ -129,7 +129,8 @@
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97", "0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a" "0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
] ],
"eip150Transition": "0x7fffffffffffffff"
} }
} }
}, },

View File

@ -9,7 +9,8 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0xffffffffffffffff" "homesteadTransition": "0x7fffffffffffffff",
"eip150Transition": "0x7fffffffffffffff"
} }
} }
}, },

View File

@ -9,7 +9,8 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x0" "homesteadTransition": "0x0",
"eip150Transition": "0x7fffffffffffffff"
} }
} }
}, },

View File

@ -9,7 +9,8 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
"frontierCompatibilityModeLimit": "0x789b0" "homesteadTransition": "0x789b0",
"eip150Transition": "0x1b34d8"
} }
} }
}, },
@ -17,7 +18,9 @@
"accountStartNonce": "0x0100000", "accountStartNonce": "0x0100000",
"maximumExtraDataSize": "0x20", "maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388", "minGasLimit": "0x1388",
"networkID" : "0x2" "networkID" : "0x2",
"forkBlock": "0x1b34d8",
"forkCanonHash": "0xf376243aeff1f256d970714c3de9fd78fa4e63cf63e32a51fe1169e375d98145"
}, },
"genesis": { "genesis": {
"seal": { "seal": {

View File

@ -8,7 +8,9 @@
"difficultyBoundDivisor": "0x0800", "difficultyBoundDivisor": "0x0800",
"durationLimit": "0x08", "durationLimit": "0x08",
"blockReward": "0x14D1120D7B160000", "blockReward": "0x14D1120D7B160000",
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050" "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050",
"homesteadTransition": "0x7fffffffffffffff",
"eip150Transition": "0x7fffffffffffffff"
} }
} }
}, },

@ -1 +1 @@
Subproject commit 8f07dbc8294a32db5ebe8098925fcefc2eab3e71 Subproject commit 97066e40ccd061f727deb5cd860e4d9135aa2551

View File

@ -1,5 +1,5 @@
{ {
"name": "DAO hard-fork consensus test", "name": "EIP150.1b hard-fork consensus test",
"engine": { "engine": {
"Ethash": { "Ethash": {
"params": { "params": {
@ -9,7 +9,7 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x5", "homesteadTransition": "0x5",
"daoHardforkTransition": "0x8", "daoHardforkTransition": "0x8",
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
"daoHardforkAccounts": [ "daoHardforkAccounts": [
@ -129,7 +129,8 @@
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97", "0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a" "0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
] ],
"eip150Transition": "0xa"
} }
} }
}, },

View File

@ -23,7 +23,7 @@ use std::time::{Instant, Duration};
use util::{Mutex, RwLock}; use util::{Mutex, RwLock};
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
use ethstore::dir::{KeyDirectory}; use ethstore::dir::{KeyDirectory};
use ethstore::ethkey::{Address, Message, Secret, Random, Generator}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
use ethjson::misc::AccountMeta; use ethjson::misc::AccountMeta;
pub use ethstore::ethkey::Signature; pub use ethstore::ethkey::Signature;
@ -176,15 +176,23 @@ impl AccountProvider {
AccountProvider { AccountProvider {
unlocked: Mutex::new(HashMap::new()), unlocked: Mutex::new(HashMap::new()),
address_book: Mutex::new(AddressBook::new(Default::default())), address_book: Mutex::new(AddressBook::new(Default::default())),
sstore: Box::new(EthStore::open(Box::new(NullDir::default())).unwrap()) sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
.expect("NullDir load always succeeds; qed"))
} }
} }
/// Creates new random account. /// Creates new random account.
pub fn new_account(&self, password: &str) -> Result<Address, Error> { pub fn new_account(&self, password: &str) -> Result<Address, Error> {
let secret = Random.generate().unwrap().secret().clone(); self.new_account_and_public(password).map(|d| d.0)
}
/// Creates new random account and returns address and public key
pub fn new_account_and_public(&self, password: &str) -> Result<(Address, Public), Error> {
let acc = Random.generate().expect("secp context has generation capabilities; qed");
let public = acc.public().clone();
let secret = acc.secret().clone();
let address = try!(self.sstore.insert_account(secret, password)); let address = try!(self.sstore.insert_account(secret, password));
Ok(address) Ok((address, public))
} }
/// Inserts new account into underlying store. /// Inserts new account into underlying store.
@ -280,6 +288,21 @@ impl AccountProvider {
Ok(()) Ok(())
} }
fn password(&self, account: &Address) -> Result<String, Error> {
let mut unlocked = self.unlocked.lock();
let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone();
if let Unlock::Temp = data.unlock {
unlocked.remove(account).expect("data exists: so key must exist: qed");
}
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
if start.elapsed() > Duration::from_millis(*duration as u64) {
unlocked.remove(account).expect("data exists: so key must exist: qed");
return Err(Error::NotUnlocked);
}
}
Ok(data.password.clone())
}
/// Unlocks account permanently. /// Unlocks account permanently.
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> { pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
self.unlock_account(account, password, Unlock::Perm) self.unlock_account(account, password, Unlock::Perm)
@ -301,51 +324,16 @@ impl AccountProvider {
unlocked.get(&account).is_some() unlocked.get(&account).is_some()
} }
/// Signs the message. Account must be unlocked. /// Signs the message. If password is not provided the account must be unlocked.
pub fn sign(&self, account: Address, message: Message) -> Result<Signature, Error> { pub fn sign(&self, account: Address, password: Option<String>, message: Message) -> Result<Signature, Error> {
let data = { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
let mut unlocked = self.unlocked.lock(); Ok(try!(self.sstore.sign(&account, &password, &message)))
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
if let Unlock::Temp = data.unlock {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
}
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
if start.elapsed() > Duration::from_millis(*duration as u64) {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
return Err(Error::NotUnlocked);
}
}
data
};
let signature = try!(self.sstore.sign(&account, &data.password, &message));
Ok(signature)
} }
/// Decrypts a message. Account must be unlocked. /// Decrypts a message. If password is not provided the account must be unlocked.
pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> { pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let data = { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
let mut unlocked = self.unlocked.lock(); Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message)))
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
if let Unlock::Temp = data.unlock {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
}
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
if start.elapsed() > Duration::from_millis(*duration as u64) {
unlocked.remove(&account).expect("data exists: so key must exist: qed");
return Err(Error::NotUnlocked);
}
}
data
};
Ok(try!(self.sstore.decrypt(&account, &data.password, shared_mac, message)))
}
/// Unlocks an account, signs the message, and locks it again.
pub fn sign_with_password(&self, account: Address, password: String, message: Message) -> Result<Signature, Error> {
let signature = try!(self.sstore.sign(&account, &password, &message));
Ok(signature)
} }
/// Returns the underlying `SecretStore` reference if one exists. /// Returns the underlying `SecretStore` reference if one exists.
@ -386,8 +374,8 @@ mod tests {
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err()); assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok()); assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_err()); assert!(ap.sign(kp.address(), None, Default::default()).is_err());
} }
#[test] #[test]
@ -397,11 +385,11 @@ mod tests {
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err()); assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok()); assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok()); assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
} }
#[test] #[test]
@ -411,8 +399,8 @@ mod tests {
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 2000).is_err()); assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 2000).is_err());
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok()); assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
::std::thread::sleep(Duration::from_millis(2000)); ::std::thread::sleep(Duration::from_millis(2000));
assert!(ap.sign(kp.address(), Default::default()).is_err()); assert!(ap.sign(kp.address(), None, Default::default()).is_err());
} }
} }

View File

@ -350,7 +350,7 @@ impl<'x> OpenBlock<'x> {
let t = outcome.trace; let t = outcome.trace;
self.block.traces.as_mut().map(|traces| traces.push(t)); self.block.traces.as_mut().map(|traces| traces.push(t));
self.block.receipts.push(outcome.receipt); self.block.receipts.push(outcome.receipt);
Ok(self.block.receipts.last().unwrap()) Ok(self.block.receipts.last().expect("receipt just pushed; qed"))
} }
Err(x) => Err(From::from(x)) Err(x) => Err(From::from(x))
} }
@ -404,6 +404,10 @@ impl<'x> OpenBlock<'x> {
uncle_bytes: uncle_bytes, uncle_bytes: uncle_bytes,
} }
} }
#[cfg(test)]
/// Return mutable block reference. To be used in tests only.
pub fn block_mut (&mut self) -> &mut ExecutedBlock { &mut self.block }
} }
impl<'x> IsBlock for OpenBlock<'x> { impl<'x> IsBlock for OpenBlock<'x> {

View File

@ -29,3 +29,12 @@ pub struct BestBlock {
/// Best block uncompressed bytes /// Best block uncompressed bytes
pub block: Bytes, pub block: Bytes,
} }
/// Best ancient block info. If the blockchain has a gap this keeps track of where it starts.
#[derive(Default)]
pub struct BestAncientBlock {
/// Best block hash.
pub hash: H256,
/// Best block number.
pub number: BlockNumber,
}

View File

@ -27,7 +27,8 @@ use log_entry::{LogEntry, LocalizedLogEntry};
use receipt::Receipt; use receipt::Receipt;
use blooms::{Bloom, BloomGroup}; use blooms::{Bloom, BloomGroup};
use blockchain::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainData}; use blockchain::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainData};
use blockchain::best_block::BestBlock; use blockchain::best_block::{BestBlock, BestAncientBlock};
use types::blockchain_info::BlockChainInfo;
use types::tree_route::TreeRoute; use types::tree_route::TreeRoute;
use blockchain::update::ExtrasUpdate; use blockchain::update::ExtrasUpdate;
use blockchain::{CacheSize, ImportRoute, Config}; use blockchain::{CacheSize, ImportRoute, Config};
@ -43,16 +44,24 @@ pub trait BlockProvider {
/// (though not necessarily a part of the canon chain). /// (though not necessarily a part of the canon chain).
fn is_known(&self, hash: &H256) -> bool; fn is_known(&self, hash: &H256) -> bool;
/// Get the first block which this chain holds. /// Get the first block of the best part of the chain.
/// Return `None` if there is no gap and the first block is the genesis.
/// Any queries of blocks which precede this one are not guaranteed to /// Any queries of blocks which precede this one are not guaranteed to
/// succeed. /// succeed.
fn first_block(&self) -> H256; fn first_block(&self) -> Option<H256>;
/// Get the number of the first block. /// Get the number of the first block.
fn first_block_number(&self) -> BlockNumber { fn first_block_number(&self) -> Option<BlockNumber> {
self.block_number(&self.first_block()).expect("First block always stored; qed") self.first_block().map(|b| self.block_number(&b).expect("First block is always set to an existing block or `None`. Existing block always has a number; qed"))
} }
/// Get the best block of an first block sequence if there is a gap.
fn best_ancient_block(&self) -> Option<H256>;
/// Get the number of the first block.
fn best_ancient_number(&self) -> Option<BlockNumber> {
self.best_ancient_block().map(|h| self.block_number(&h).expect("Ancient block is always set to an existing block or `None`. Existing block always has a number; qed"))
}
/// Get raw block data /// Get raw block data
fn block(&self, hash: &H256) -> Option<Bytes>; fn block(&self, hash: &H256) -> Option<Bytes>;
@ -123,7 +132,8 @@ pub trait BlockProvider {
/// Returns the header of the genesis block. /// Returns the header of the genesis block.
fn genesis_header(&self) -> Header { fn genesis_header(&self) -> Header {
self.block_header(&self.genesis_hash()).unwrap() self.block_header(&self.genesis_hash())
.expect("Genesis header always stored; qed")
} }
/// Returns numbers of blocks containing given bloom. /// Returns numbers of blocks containing given bloom.
@ -160,9 +170,14 @@ impl bc::group::BloomGroupDatabase for BlockChain {
pub struct BlockChain { pub struct BlockChain {
// All locks must be captured in the order declared here. // All locks must be captured in the order declared here.
blooms_config: bc::Config, blooms_config: bc::Config,
first_block: H256,
best_block: RwLock<BestBlock>, best_block: RwLock<BestBlock>,
// Stores best block of the first uninterrupted sequence of blocks. `None` if there are no gaps.
// Only updated with `insert_unordered_block`.
best_ancient_block: RwLock<Option<BestAncientBlock>>,
// Stores the last block of the last sequence of blocks. `None` if there are no gaps.
// This is calculated on start and does not get updated.
first_block: Option<H256>,
// block cache // block cache
block_headers: RwLock<HashMap<H256, Bytes>>, block_headers: RwLock<HashMap<H256, Bytes>>,
@ -191,8 +206,16 @@ impl BlockProvider for BlockChain {
self.db.exists_with_cache(db::COL_EXTRA, &self.block_details, hash) self.db.exists_with_cache(db::COL_EXTRA, &self.block_details, hash)
} }
fn first_block(&self) -> H256 { fn first_block(&self) -> Option<H256> {
self.first_block self.first_block.clone()
}
fn best_ancient_block(&self) -> Option<H256> {
self.best_ancient_block.read().as_ref().map(|b| b.hash.clone())
}
fn best_ancient_number(&self) -> Option<BlockNumber> {
self.best_ancient_block.read().as_ref().map(|b| b.number)
} }
/// Get raw block data /// Get raw block data
@ -332,7 +355,10 @@ impl BlockProvider for BlockChain {
.filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts))) .filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts)))
.filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes()))) .filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes())))
.flat_map(|(number, hash, mut receipts, mut hashes)| { .flat_map(|(number, hash, mut receipts, mut hashes)| {
assert_eq!(receipts.len(), hashes.len()); if receipts.len() != hashes.len() {
warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len());
assert!(false);
}
log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len()); log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len());
let receipts_len = receipts.len(); let receipts_len = receipts.len();
@ -397,8 +423,9 @@ impl BlockChain {
levels: LOG_BLOOMS_LEVELS, levels: LOG_BLOOMS_LEVELS,
elements_per_index: LOG_BLOOMS_ELEMENTS_PER_INDEX, elements_per_index: LOG_BLOOMS_ELEMENTS_PER_INDEX,
}, },
first_block: H256::zero(), first_block: None,
best_block: RwLock::new(BestBlock::default()), best_block: RwLock::new(BestBlock::default()),
best_ancient_block: RwLock::new(None),
block_headers: RwLock::new(HashMap::new()), block_headers: RwLock::new(HashMap::new()),
block_bodies: RwLock::new(HashMap::new()), block_bodies: RwLock::new(HashMap::new()),
block_details: RwLock::new(HashMap::new()), block_details: RwLock::new(HashMap::new()),
@ -440,7 +467,6 @@ impl BlockChain {
batch.write(db::COL_EXTRA, &header.number(), &hash); batch.write(db::COL_EXTRA, &header.number(), &hash);
batch.put(db::COL_EXTRA, b"best", &hash); batch.put(db::COL_EXTRA, b"best", &hash);
batch.put(db::COL_EXTRA, b"first", &hash);
bc.db.write(batch).expect("Low level database error. Some issue with disk?"); bc.db.write(batch).expect("Low level database error. Some issue with disk?");
hash hash
} }
@ -452,12 +478,21 @@ impl BlockChain {
let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty; let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty;
let best_block_rlp = bc.block(&best_block_hash).unwrap(); let best_block_rlp = bc.block(&best_block_hash).unwrap();
let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map_or(Vec::new(), |v| v.to_vec()); let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec());
let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h));
let best_ancient_number;
if best_ancient.is_none() && best_block_number > 1 && bc.block_hash(1).is_none() {
best_ancient = Some(bc.genesis_hash());
best_ancient_number = Some(0);
} else {
best_ancient_number = best_ancient.as_ref().and_then(|h| bc.block_number(h));
}
// binary search for the first block. // binary search for the first block.
if raw_first.is_empty() { match raw_first {
None => {
let (mut f, mut hash) = (best_block_number, best_block_hash); let (mut f, mut hash) = (best_block_number, best_block_hash);
let mut l = 0; let mut l = best_ancient_number.unwrap_or(0);
loop { loop {
if l >= f { break; } if l >= f { break; }
@ -471,13 +506,17 @@ impl BlockChain {
} }
} }
if hash != bc.genesis_hash() {
trace!("First block calculated: {:?}", hash);
let mut batch = db.transaction(); let mut batch = db.transaction();
batch.put(db::COL_EXTRA, b"first", &hash); batch.put(db::COL_EXTRA, b"first", &hash);
db.write(batch).expect("Low level database error."); db.write(batch).expect("Low level database error.");
bc.first_block = Some(hash);
bc.first_block = hash; }
} else { },
bc.first_block = H256::from_slice(&raw_first); Some(raw_first) => {
bc.first_block = Some(H256::from_slice(&raw_first));
},
} }
// and write them // and write them
@ -488,6 +527,14 @@ impl BlockChain {
hash: best_block_hash, hash: best_block_hash,
block: best_block_rlp, block: best_block_rlp,
}; };
if let (Some(hash), Some(number)) = (best_ancient, best_ancient_number) {
let mut best_ancient_block = bc.best_ancient_block.write();
*best_ancient_block = Some(BestAncientBlock {
hash: hash,
number: number,
});
}
} }
bc bc
@ -641,11 +688,12 @@ impl BlockChain {
/// Inserts a verified, known block from the canonical chain. /// Inserts a verified, known block from the canonical chain.
/// ///
/// Can be performed out-of-order, but care must be taken that the final chain is in a correct state. /// Can be performed out-of-order, but care must be taken that the final chain is in a correct state.
/// This is used by snapshot restoration. /// This is used by snapshot restoration and when downloading missing blocks for the chain gap.
/// /// `is_best` forces the best block to be updated to this block.
/// `is_ancient` forces the best block of the first block sequence to be updated to this block.
/// Supply a dummy parent total difficulty when the parent block may not be in the chain. /// Supply a dummy parent total difficulty when the parent block may not be in the chain.
/// Returns true if the block is disconnected. /// Returns true if the block is disconnected.
pub fn insert_snapshot_block(&self, bytes: &[u8], receipts: Vec<Receipt>, parent_td: Option<U256>, is_best: bool) -> bool { pub fn insert_unordered_block(&self, batch: &mut DBTransaction, bytes: &[u8], receipts: Vec<Receipt>, parent_td: Option<U256>, is_best: bool, is_ancient: bool) -> bool {
let block = BlockView::new(bytes); let block = BlockView::new(bytes);
let header = block.header_view(); let header = block.header_view();
let hash = header.sha3(); let hash = header.sha3();
@ -656,8 +704,6 @@ impl BlockChain {
assert!(self.pending_best_block.read().is_none()); assert!(self.pending_best_block.read().is_none());
let mut batch = self.db.transaction();
let block_rlp = UntrustedRlp::new(bytes); let block_rlp = UntrustedRlp::new(bytes);
let compressed_header = block_rlp.at(0).unwrap().compress(RlpType::Blocks); let compressed_header = block_rlp.at(0).unwrap().compress(RlpType::Blocks);
let compressed_body = UntrustedRlp::new(&Self::block_to_body(bytes)).compress(RlpType::Blocks); let compressed_body = UntrustedRlp::new(&Self::block_to_body(bytes)).compress(RlpType::Blocks);
@ -671,13 +717,13 @@ impl BlockChain {
if let Some(parent_details) = maybe_parent { if let Some(parent_details) = maybe_parent {
// parent known to be in chain. // parent known to be in chain.
let info = BlockInfo { let info = BlockInfo {
hash: hash, hash: hash.clone(),
number: header.number(), number: header.number(),
total_difficulty: parent_details.total_difficulty + header.difficulty(), total_difficulty: parent_details.total_difficulty + header.difficulty(),
location: BlockLocation::CanonChain, location: BlockLocation::CanonChain,
}; };
self.prepare_update(&mut batch, ExtrasUpdate { self.prepare_update(batch, ExtrasUpdate {
block_hashes: self.prepare_block_hashes_update(bytes, &info), block_hashes: self.prepare_block_hashes_update(bytes, &info),
block_details: self.prepare_block_details_update(bytes, &info), block_details: self.prepare_block_details_update(bytes, &info),
block_receipts: self.prepare_block_receipts_update(receipts, &info), block_receipts: self.prepare_block_receipts_update(receipts, &info),
@ -686,7 +732,21 @@ impl BlockChain {
info: info, info: info,
block: bytes block: bytes
}, is_best); }, is_best);
self.db.write(batch).unwrap();
if is_ancient {
let mut best_ancient_block = self.best_ancient_block.write();
let ancient_number = best_ancient_block.as_ref().map_or(0, |b| b.number);
if self.block_hash(header.number() + 1).is_some() {
batch.delete(db::COL_EXTRA, b"ancient");
*best_ancient_block = None;
} else if header.number() > ancient_number {
batch.put(db::COL_EXTRA, b"ancient", &hash);
*best_ancient_block = Some(BestAncientBlock {
hash: hash,
number: header.number(),
});
}
}
false false
} else { } else {
@ -711,7 +771,7 @@ impl BlockChain {
let mut update = HashMap::new(); let mut update = HashMap::new();
update.insert(hash, block_details); update.insert(hash, block_details);
self.prepare_update(&mut batch, ExtrasUpdate { self.prepare_update(batch, ExtrasUpdate {
block_hashes: self.prepare_block_hashes_update(bytes, &info), block_hashes: self.prepare_block_hashes_update(bytes, &info),
block_details: update, block_details: update,
block_receipts: self.prepare_block_receipts_update(receipts, &info), block_receipts: self.prepare_block_receipts_update(receipts, &info),
@ -720,8 +780,6 @@ impl BlockChain {
info: info, info: info,
block: bytes, block: bytes,
}, is_best); }, is_best);
self.db.write(batch).unwrap();
true true
} }
} }
@ -1207,6 +1265,24 @@ impl BlockChain {
body.append_raw(block_rlp.at(2).as_raw(), 1); body.append_raw(block_rlp.at(2).as_raw(), 1);
body.out() body.out()
} }
/// Returns general blockchain information
pub fn chain_info(&self) -> BlockChainInfo {
// ensure data consistencly by locking everything first
let best_block = self.best_block.read();
let best_ancient_block = self.best_ancient_block.read();
BlockChainInfo {
total_difficulty: best_block.total_difficulty.clone(),
pending_total_difficulty: best_block.total_difficulty.clone(),
genesis_hash: self.genesis_hash(),
best_block_hash: best_block.hash.clone(),
best_block_number: best_block.number,
first_block_hash: self.first_block(),
first_block_number: From::from(self.first_block_number()),
ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()),
ancient_block_number: best_ancient_block.as_ref().map(|b| b.number),
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -55,18 +55,23 @@ impl<T> CacheManager<T> where T: Eq + Hash {
} }
for _ in 0..COLLECTION_QUEUE_SIZE { for _ in 0..COLLECTION_QUEUE_SIZE {
let current_size = notify_unused(self.cache_usage.pop_back().unwrap()); if let Some(back) = self.cache_usage.pop_back() {
let current_size = notify_unused(back);
self.cache_usage.push_front(Default::default()); self.cache_usage.push_front(Default::default());
if current_size < self.max_cache_size { if current_size < self.max_cache_size {
break; break
}
} }
} }
} }
fn rotate_cache_if_needed(&mut self) { fn rotate_cache_if_needed(&mut self) {
if self.cache_usage.len() == 0 { return }
if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE { if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE {
let cache = self.cache_usage.pop_back().unwrap(); if let Some(cache) = self.cache_usage.pop_back() {
self.cache_usage.push_front(cache); self.cache_usage.push_front(cache);
} }
} }
} }
}

View File

@ -30,7 +30,7 @@ use util::kvdb::*;
// other // other
use io::*; use io::*;
use views::{HeaderView, BodyView}; use views::{HeaderView, BodyView, BlockView};
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
use header::BlockNumber; use header::BlockNumber;
use state::State; use state::State;
@ -46,7 +46,7 @@ use transaction::{LocalizedTransaction, SignedTransaction, Action};
use blockchain::extras::TransactionAddress; use blockchain::extras::TransactionAddress;
use types::filter::Filter; use types::filter::Filter;
use log_entry::LocalizedLogEntry; use log_entry::LocalizedLogEntry;
use verification::queue::{BlockQueue, QueueInfo as BlockQueueInfo}; use verification::queue::BlockQueue;
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use client::{ use client::{
BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient,
@ -71,9 +71,11 @@ use state_db::StateDB;
pub use types::blockchain_info::BlockChainInfo; pub use types::blockchain_info::BlockChainInfo;
pub use types::block_status::BlockStatus; pub use types::block_status::BlockStatus;
pub use blockchain::CacheSize as BlockChainCacheSize; pub use blockchain::CacheSize as BlockChainCacheSize;
pub use verification::queue::QueueInfo as BlockQueueInfo;
const MAX_TX_QUEUE_SIZE: usize = 4096; const MAX_TX_QUEUE_SIZE: usize = 4096;
const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2; const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2;
const MIN_HISTORY_SIZE: u64 = 8;
impl fmt::Display for BlockChainInfo { impl fmt::Display for BlockChainInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -141,12 +143,9 @@ pub struct Client {
queue_transactions: AtomicUsize, queue_transactions: AtomicUsize,
last_hashes: RwLock<VecDeque<H256>>, last_hashes: RwLock<VecDeque<H256>>,
factories: Factories, factories: Factories,
history: u64,
} }
/// The pruning constant -- how old blocks must be before we
/// assume finality of a given candidate.
pub const HISTORY: u64 = 1200;
impl Client { impl Client {
/// Create a new client with given spec and DB path and custom verifier. /// Create a new client with given spec and DB path and custom verifier.
pub fn new( pub fn new(
@ -170,13 +169,35 @@ impl Client {
}; };
let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE);
let mut state_db = StateDB::new(journal_db); let mut state_db = StateDB::new(journal_db, config.state_cache_size);
if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db)) { if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db)) {
let mut batch = DBTransaction::new(&db); let mut batch = DBTransaction::new(&db);
try!(state_db.commit(&mut batch, 0, &spec.genesis_header().hash(), None)); try!(state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash()));
try!(db.write(batch).map_err(ClientError::Database)); try!(db.write(batch).map_err(ClientError::Database));
} }
trace!("Cleanup journal: DB Earliest = {:?}, Latest = {:?}", state_db.journal_db().earliest_era(), state_db.journal_db().latest_era());
let history = if config.history < MIN_HISTORY_SIZE {
info!(target: "client", "Ignoring pruning history parameter of {}\
, falling back to minimum of {}",
config.history, MIN_HISTORY_SIZE);
MIN_HISTORY_SIZE
} else {
config.history
};
if let (Some(earliest), Some(latest)) = (state_db.journal_db().earliest_era(), state_db.journal_db().latest_era()) {
if latest > earliest && latest - earliest > history {
for era in earliest..(latest - history + 1) {
trace!("Removing era {}", era);
let mut batch = DBTransaction::new(&db);
try!(state_db.mark_canonical(&mut batch, era, &chain.block_hash(era).expect("Old block not found in the database")));
try!(db.write(batch).map_err(ClientError::Database));
}
}
}
if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.journal_db().contains(h.state_root())) { if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.journal_db().contains(h.state_root())) {
warn!("State root not found for block #{} ({})", chain.best_block_number(), chain.best_block_hash().hex()); warn!("State root not found for block #{} ({})", chain.best_block_number(), chain.best_block_hash().hex());
} }
@ -190,7 +211,7 @@ impl Client {
let awake = match config.mode { Mode::Dark(..) => false, _ => true }; let awake = match config.mode { Mode::Dark(..) => false, _ => true };
let factories = Factories { let factories = Factories {
vm: EvmFactory::new(config.vm_type.clone()), vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size),
trie: TrieFactory::new(trie_spec), trie: TrieFactory::new(trie_spec),
accountdb: Default::default(), accountdb: Default::default(),
}; };
@ -217,6 +238,7 @@ impl Client {
queue_transactions: AtomicUsize::new(0), queue_transactions: AtomicUsize::new(0),
last_hashes: RwLock::new(VecDeque::new()), last_hashes: RwLock::new(VecDeque::new()),
factories: factories, factories: factories,
history: history,
}; };
Ok(Arc::new(client)) Ok(Arc::new(client))
} }
@ -275,7 +297,7 @@ impl Client {
let chain = self.chain.read(); let chain = self.chain.read();
// Check the block isn't so old we won't be able to enact it. // Check the block isn't so old we won't be able to enact it.
let best_block_number = chain.best_block_number(); let best_block_number = chain.best_block_number();
if best_block_number >= HISTORY && header.number() <= best_block_number - HISTORY { if best_block_number >= self.history && header.number() <= best_block_number - self.history {
warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number); warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number);
return Err(()); return Err(());
} }
@ -340,16 +362,19 @@ impl Client {
/// This is triggered by a message coming from a block queue when the block is ready for insertion /// This is triggered by a message coming from a block queue when the block is ready for insertion
pub fn import_verified_blocks(&self) -> usize { pub fn import_verified_blocks(&self) -> usize {
let max_blocks_to_import = 64; let max_blocks_to_import = 4;
let (imported_blocks, import_results, invalid_blocks, imported, duration) = { let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = {
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import); let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
let mut invalid_blocks = HashSet::new(); let mut invalid_blocks = HashSet::new();
let mut import_results = Vec::with_capacity(max_blocks_to_import); let mut import_results = Vec::with_capacity(max_blocks_to_import);
let _import_lock = self.import_lock.lock(); let _import_lock = self.import_lock.lock();
let blocks = self.block_queue.drain(max_blocks_to_import);
if blocks.is_empty() {
return 0;
}
let _timer = PerfTimer::new("import_verified_blocks"); let _timer = PerfTimer::new("import_verified_blocks");
let start = precise_time_ns(); let start = precise_time_ns();
let blocks = self.block_queue.drain(max_blocks_to_import);
for block in blocks { for block in blocks {
let header = &block.header; let header = &block.header;
@ -373,23 +398,19 @@ impl Client {
let imported = imported_blocks.len(); let imported = imported_blocks.len();
let invalid_blocks = invalid_blocks.into_iter().collect::<Vec<H256>>(); let invalid_blocks = invalid_blocks.into_iter().collect::<Vec<H256>>();
{
if !invalid_blocks.is_empty() { if !invalid_blocks.is_empty() {
self.block_queue.mark_as_bad(&invalid_blocks); self.block_queue.mark_as_bad(&invalid_blocks);
} }
if !imported_blocks.is_empty() { let is_empty = self.block_queue.mark_as_good(&imported_blocks);
self.block_queue.mark_as_good(&imported_blocks);
}
}
let duration_ns = precise_time_ns() - start; let duration_ns = precise_time_ns() - start;
(imported_blocks, import_results, invalid_blocks, imported, duration_ns) (imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty)
}; };
{ {
if !imported_blocks.is_empty() && self.block_queue.queue_info().is_empty() { if !imported_blocks.is_empty() && is_empty {
let (enacted, retracted) = self.calculate_enacted_retracted(&import_results); let (enacted, retracted) = self.calculate_enacted_retracted(&import_results);
if self.queue_info().is_empty() { if is_empty {
self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted); self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted);
} }
@ -410,17 +431,33 @@ impl Client {
imported imported
} }
/// Import a block with transaction receipts.
/// The block is guaranteed to be the next best blocks in the first block sequence.
/// Does no sealing or transaction validation.
fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> H256 {
let block = BlockView::new(&block_bytes);
let hash = block.header().hash();
let _import_lock = self.import_lock.lock();
{
let _timer = PerfTimer::new("import_old_block");
let chain = self.chain.read();
// Commit results
let receipts = ::rlp::decode(&receipts_bytes);
let mut batch = DBTransaction::new(&self.db.read());
chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true);
// Final commit to the DB
self.db.read().write_buffered(batch);
chain.commit();
}
self.db.read().flush().expect("DB flush failed.");
hash
}
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain { fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain {
let number = block.header().number(); let number = block.header().number();
let parent = block.header().parent_hash().clone(); let parent = block.header().parent_hash().clone();
let chain = self.chain.read(); let chain = self.chain.read();
// Are we committing an era?
let ancient = if number >= HISTORY {
let n = number - HISTORY;
Some((n, chain.block_hash(n).expect("only verified blocks can be commited; verified block has hash; qed")))
} else {
None
};
// Commit results // Commit results
let receipts = block.receipts().to_owned(); let receipts = block.receipts().to_owned();
@ -436,7 +473,17 @@ impl Client {
// already-imported block of the same number. // already-imported block of the same number.
// TODO: Prove it with a test. // TODO: Prove it with a test.
let mut state = block.drain(); let mut state = block.drain();
state.commit(&mut batch, number, hash, ancient).expect("DB commit failed.");
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
if number >= self.history {
let n = number - self.history;
if let Some(ancient_hash) = chain.block_hash(n) {
state.mark_canonical(&mut batch, n, &ancient_hash).expect("DB commit failed");
} else {
debug!(target: "client", "Missing expected hash for block {}", n);
}
}
let route = chain.insert_block(&mut batch, block_data, receipts); let route = chain.insert_block(&mut batch, block_data, receipts);
self.tracedb.read().import(&mut batch, TraceImportRequest { self.tracedb.read().import(&mut batch, TraceImportRequest {
@ -446,6 +493,7 @@ impl Client {
enacted: route.enacted.clone(), enacted: route.enacted.clone(),
retracted: route.retracted.len() retracted: route.retracted.len()
}); });
let is_canon = route.enacted.last().map_or(false, |h| h == hash); let is_canon = route.enacted.last().map_or(false, |h| h == hash);
state.sync_cache(&route.enacted, &route.retracted, is_canon); state.sync_cache(&route.enacted, &route.retracted, is_canon);
// Final commit to the DB // Final commit to the DB
@ -501,7 +549,7 @@ impl Client {
let db = self.state_db.lock().boxed_clone(); let db = self.state_db.lock().boxed_clone();
// early exit for pruned blocks // early exit for pruned blocks
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + HISTORY { if db.is_pruned() && self.chain.read().best_block_number() >= block_number + self.history {
return None; return None;
} }
@ -606,20 +654,23 @@ impl Client {
let best_block_number = self.chain_info().best_block_number; let best_block_number = self.chain_info().best_block_number;
let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at)));
if best_block_number > HISTORY + block_number && db.is_pruned() { if best_block_number > self.history + block_number && db.is_pruned() {
return Err(snapshot::Error::OldBlockPrunedDB.into()); return Err(snapshot::Error::OldBlockPrunedDB.into());
} }
let history = ::std::cmp::min(self.history, 1000);
let start_hash = match at { let start_hash = match at {
BlockID::Latest => { BlockID::Latest => {
let start_num = if best_block_number > 1000 { let start_num = match db.earliest_era() {
best_block_number - 1000 Some(era) => ::std::cmp::max(era, best_block_number - history),
} else { None => best_block_number - history,
0
}; };
self.block_hash(BlockID::Number(start_num)) match self.block_hash(BlockID::Number(start_num)) {
.expect("blocks within HISTORY are always stored.") Some(h) => h,
None => return Err(snapshot::Error::InvalidStartingBlock(at).into()),
}
} }
_ => match self.block_hash(at) { _ => match self.block_hash(at) {
Some(hash) => hash, Some(hash) => hash,
@ -632,6 +683,11 @@ impl Client {
Ok(()) Ok(())
} }
/// Ask the client what the history parameter is.
pub fn pruning_history(&self) -> u64 {
self.history
}
fn block_hash(chain: &BlockChain, id: BlockID) -> Option<H256> { fn block_hash(chain: &BlockChain, id: BlockID) -> Option<H256> {
match id { match id {
BlockID::Hash(hash) => Some(hash), BlockID::Hash(hash) => Some(hash),
@ -688,7 +744,8 @@ impl snapshot::DatabaseRestore for Client {
let db = self.db.write(); let db = self.db.write();
try!(db.restore(new_db)); try!(db.restore(new_db));
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE)); let cache_size = state_db.cache_size();
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size);
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
Ok(()) Ok(())
@ -974,6 +1031,20 @@ impl BlockChainClient for Client {
Ok(try!(self.block_queue.import(unverified))) Ok(try!(self.block_queue.import(unverified)))
} }
fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result<H256, BlockImportError> {
{
// check block order
let header = BlockView::new(&block_bytes).header_view();
if self.chain.read().is_known(&header.hash()) {
return Err(BlockImportError::Import(ImportError::AlreadyInChain));
}
if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown {
return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash())));
}
}
Ok(self.import_old_block(block_bytes, receipts_bytes))
}
fn queue_info(&self) -> BlockQueueInfo { fn queue_info(&self) -> BlockQueueInfo {
self.block_queue.queue_info() self.block_queue.queue_info()
} }
@ -983,14 +1054,7 @@ impl BlockChainClient for Client {
} }
fn chain_info(&self) -> BlockChainInfo { fn chain_info(&self) -> BlockChainInfo {
let chain = self.chain.read(); self.chain.read().chain_info()
BlockChainInfo {
total_difficulty: chain.best_block_total_difficulty(),
pending_total_difficulty: chain.best_block_total_difficulty(),
genesis_hash: chain.genesis_hash(),
best_block_hash: chain.best_block_hash(),
best_block_number: From::from(chain.best_block_number())
}
} }
fn additional_params(&self) -> BTreeMap<String, String> { fn additional_params(&self) -> BTreeMap<String, String> {
@ -1120,21 +1184,22 @@ impl MiningBlockChainClient for Client {
} }
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult { fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
let h = block.header().hash();
let start = precise_time_ns();
let route = {
// scope for self.import_lock
let _import_lock = self.import_lock.lock(); let _import_lock = self.import_lock.lock();
let _timer = PerfTimer::new("import_sealed_block"); let _timer = PerfTimer::new("import_sealed_block");
let start = precise_time_ns();
let h = block.header().hash();
let number = block.header().number(); let number = block.header().number();
let block_data = block.rlp_bytes(); let block_data = block.rlp_bytes();
let route = self.commit_block(block, &h, &block_data); let route = self.commit_block(block, &h, &block_data);
trace!(target: "client", "Imported sealed block #{} ({})", number, h); trace!(target: "client", "Imported sealed block #{} ({})", number, h);
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false); self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
route
};
let (enacted, retracted) = self.calculate_enacted_retracted(&[route]); let (enacted, retracted) = self.calculate_enacted_retracted(&[route]);
self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted);
self.notify(|notify| { self.notify(|notify| {
notify.new_blocks( notify.new_blocks(
vec![h.clone()], vec![h.clone()],

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::str::FromStr; use std::str::FromStr;
use std::path::Path;
pub use std::time::Duration; pub use std::time::Duration;
pub use blockchain::Config as BlockChainConfig; pub use blockchain::Config as BlockChainConfig;
pub use trace::Config as TraceConfig; pub use trace::Config as TraceConfig;
@ -26,23 +27,26 @@ use util::{journaldb, CompactionProfile};
/// Client state db compaction profile /// Client state db compaction profile
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DatabaseCompactionProfile { pub enum DatabaseCompactionProfile {
/// Default compaction profile /// Try to determine compaction profile automatically
Default, Auto,
/// SSD compaction profile
SSD,
/// HDD or other slow storage io compaction profile /// HDD or other slow storage io compaction profile
HDD, HDD,
} }
impl Default for DatabaseCompactionProfile { impl Default for DatabaseCompactionProfile {
fn default() -> Self { fn default() -> Self {
DatabaseCompactionProfile::Default DatabaseCompactionProfile::Auto
} }
} }
impl DatabaseCompactionProfile { impl DatabaseCompactionProfile {
/// Returns corresponding compaction profile. /// Returns corresponding compaction profile.
pub fn compaction_profile(&self) -> CompactionProfile { pub fn compaction_profile(&self, db_path: &Path) -> CompactionProfile {
match *self { match *self {
DatabaseCompactionProfile::Default => Default::default(), DatabaseCompactionProfile::Auto => CompactionProfile::auto(db_path),
DatabaseCompactionProfile::SSD => CompactionProfile::ssd(),
DatabaseCompactionProfile::HDD => CompactionProfile::hdd(), DatabaseCompactionProfile::HDD => CompactionProfile::hdd(),
} }
} }
@ -53,9 +57,10 @@ impl FromStr for DatabaseCompactionProfile {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"ssd" | "default" => Ok(DatabaseCompactionProfile::Default), "auto" => Ok(DatabaseCompactionProfile::Auto),
"ssd" => Ok(DatabaseCompactionProfile::SSD),
"hdd" => Ok(DatabaseCompactionProfile::HDD), "hdd" => Ok(DatabaseCompactionProfile::HDD),
_ => Err("Invalid compaction profile given. Expected hdd/ssd (default).".into()), _ => Err("Invalid compaction profile given. Expected default/hdd/ssd.".into()),
} }
} }
} }
@ -96,7 +101,7 @@ pub struct ClientConfig {
pub pruning: journaldb::Algorithm, pub pruning: journaldb::Algorithm,
/// The name of the client instance. /// The name of the client instance.
pub name: String, pub name: String,
/// State db cache-size if not default /// RocksDB state column cache-size if not default
pub db_cache_size: Option<usize>, pub db_cache_size: Option<usize>,
/// State db compaction profile /// State db compaction profile
pub db_compaction: DatabaseCompactionProfile, pub db_compaction: DatabaseCompactionProfile,
@ -106,6 +111,12 @@ pub struct ClientConfig {
pub mode: Mode, pub mode: Mode,
/// Type of block verifier used by client. /// Type of block verifier used by client.
pub verifier_type: VerifierType, pub verifier_type: VerifierType,
/// State db cache-size.
pub state_cache_size: usize,
/// EVM jump-tables cache size.
pub jump_table_size: usize,
/// State pruning history size.
pub history: u64,
} }
#[cfg(test)] #[cfg(test)]
@ -114,13 +125,13 @@ mod test {
#[test] #[test]
fn test_default_compaction_profile() { fn test_default_compaction_profile() {
assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Default); assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Auto);
} }
#[test] #[test]
fn test_parsing_compaction_profile() { fn test_parsing_compaction_profile() {
assert_eq!(DatabaseCompactionProfile::Default, "ssd".parse().unwrap()); assert_eq!(DatabaseCompactionProfile::Auto, "auto".parse().unwrap());
assert_eq!(DatabaseCompactionProfile::Default, "default".parse().unwrap()); assert_eq!(DatabaseCompactionProfile::SSD, "ssd".parse().unwrap());
assert_eq!(DatabaseCompactionProfile::HDD, "hdd".parse().unwrap()); assert_eq!(DatabaseCompactionProfile::HDD, "hdd".parse().unwrap());
} }

View File

@ -140,7 +140,7 @@ impl TestBlockChainClient {
queue_size: AtomicUsize::new(0), queue_size: AtomicUsize::new(0),
miner: Arc::new(Miner::with_spec(&spec)), miner: Arc::new(Miner::with_spec(&spec)),
spec: spec, spec: spec,
vm_factory: EvmFactory::new(VMType::Interpreter), vm_factory: EvmFactory::new(VMType::Interpreter, 1024 * 1024),
latest_block_timestamp: RwLock::new(10_000_000), latest_block_timestamp: RwLock::new(10_000_000),
}; };
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
@ -308,7 +308,7 @@ pub fn get_temp_state_db() -> GuardedTempResult<StateDB> {
let temp = RandomTempPath::new(); let temp = RandomTempPath::new();
let db = Database::open(&DatabaseConfig::with_columns(NUM_COLUMNS), temp.as_str()).unwrap(); let db = Database::open(&DatabaseConfig::with_columns(NUM_COLUMNS), temp.as_str()).unwrap();
let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::EarlyMerge, COL_STATE); let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::EarlyMerge, COL_STATE);
let state_db = StateDB::new(journal_db); let state_db = StateDB::new(journal_db, 1024 * 1024);
GuardedTempResult { GuardedTempResult {
_temp: temp, _temp: temp,
result: Some(state_db) result: Some(state_db)
@ -570,6 +570,10 @@ impl BlockChainClient for TestBlockChainClient {
Ok(h) Ok(h)
} }
fn import_block_with_receipts(&self, b: Bytes, _r: Bytes) -> Result<H256, BlockImportError> {
self.import_block(b)
}
fn queue_info(&self) -> QueueInfo { fn queue_info(&self) -> QueueInfo {
QueueInfo { QueueInfo {
verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed), verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed),
@ -595,6 +599,10 @@ impl BlockChainClient for TestBlockChainClient {
genesis_hash: self.genesis_hash.clone(), genesis_hash: self.genesis_hash.clone(),
best_block_hash: self.last_hash.read().clone(), best_block_hash: self.last_hash.read().clone(),
best_block_number: self.blocks.read().len() as BlockNumber - 1, best_block_number: self.blocks.read().len() as BlockNumber - 1,
first_block_hash: None,
first_block_number: None,
ancient_block_hash: None,
ancient_block_number: None,
} }
} }

View File

@ -139,6 +139,9 @@ pub trait BlockChainClient : Sync + Send {
/// Import a block into the blockchain. /// Import a block into the blockchain.
fn import_block(&self, bytes: Bytes) -> Result<H256, BlockImportError>; fn import_block(&self, bytes: Bytes) -> Result<H256, BlockImportError>;
/// Import a block with transaction receipts. Does no sealing and transaction validation.
fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result<H256, BlockImportError>;
/// Get block queue information. /// Get block queue information.
fn queue_info(&self) -> BlockQueueInfo; fn queue_info(&self) -> BlockQueueInfo;

View File

@ -197,7 +197,7 @@ impl Engine for AuthorityRound {
if self.is_step_proposer(step, header.author()) { if self.is_step_proposer(step, header.author()) {
if let Some(ap) = accounts { if let Some(ap) = accounts {
// Account should be permanently unlocked, otherwise sealing will fail. // Account should be permanently unlocked, otherwise sealing will fail.
if let Ok(signature) = ap.sign(*header.author(), header.bare_hash()) { if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) {
return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
} else { } else {
trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable"); trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable");

View File

@ -112,7 +112,7 @@ impl Engine for BasicAuthority {
let header = block.header(); let header = block.header();
let message = header.bare_hash(); let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail // account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = ap.sign(*block.header().author(), message) { if let Ok(signature) = ap.sign(*block.header().author(), None, message) {
return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else { } else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");

View File

@ -127,10 +127,14 @@ pub trait Engine : Sync + Send {
fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) } fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) }
/// Determine the code execution cost of the builtin contract with address `a`. /// Determine the code execution cost of the builtin contract with address `a`.
/// Panics if `is_builtin(a)` is not true. /// Panics if `is_builtin(a)` is not true.
fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.builtins().get(a).unwrap().cost(input.len()) } fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 {
self.builtins().get(a).expect("queried cost of nonexistent builtin").cost(input.len())
}
/// Execution the builtin contract `a` on `input` and return `output`. /// Execution the builtin contract `a` on `input` and return `output`.
/// Panics if `is_builtin(a)` is not true. /// Panics if `is_builtin(a)` is not true.
fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); } fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) {
self.builtins().get(a).expect("attempted to execute nonexistent builtin").execute(input, output);
}
/// Add a channel for communication with Client which can be used for sealing. /// Add a channel for communication with Client which can be used for sealing.
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {} fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}

View File

@ -41,7 +41,7 @@ pub struct EthashParams {
/// Namereg contract address. /// Namereg contract address.
pub registrar: Address, pub registrar: Address,
/// Homestead transition block number. /// Homestead transition block number.
pub frontier_compatibility_mode_limit: u64, pub homestead_transition: u64,
/// DAO hard-fork transition block (X). /// DAO hard-fork transition block (X).
pub dao_hardfork_transition: u64, pub dao_hardfork_transition: u64,
/// DAO hard-fork refund contract address (C). /// DAO hard-fork refund contract address (C).
@ -54,6 +54,8 @@ pub struct EthashParams {
pub difficulty_hardfork_bound_divisor: U256, pub difficulty_hardfork_bound_divisor: U256,
/// Block on which there is no additional difficulty from the exponential bomb. /// Block on which there is no additional difficulty from the exponential bomb.
pub bomb_defuse_transition: u64, pub bomb_defuse_transition: u64,
/// Bad gas transition block number.
pub eip150_transition: u64,
} }
impl From<ethjson::spec::EthashParams> for EthashParams { impl From<ethjson::spec::EthashParams> for EthashParams {
@ -66,13 +68,14 @@ impl From<ethjson::spec::EthashParams> for EthashParams {
duration_limit: p.duration_limit.into(), duration_limit: p.duration_limit.into(),
block_reward: p.block_reward.into(), block_reward: p.block_reward.into(),
registrar: p.registrar.map_or_else(Address::new, Into::into), registrar: p.registrar.map_or_else(Address::new, Into::into),
frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.map_or(0, Into::into), homestead_transition: p.homestead_transition.map_or(0, Into::into),
dao_hardfork_transition: p.dao_hardfork_transition.map_or(0x7fffffffffffffff, Into::into), dao_hardfork_transition: p.dao_hardfork_transition.map_or(0x7fffffffffffffff, Into::into),
dao_hardfork_beneficiary: p.dao_hardfork_beneficiary.map_or_else(Address::new, Into::into), dao_hardfork_beneficiary: p.dao_hardfork_beneficiary.map_or_else(Address::new, Into::into),
dao_hardfork_accounts: p.dao_hardfork_accounts.unwrap_or_else(Vec::new).into_iter().map(Into::into).collect(), dao_hardfork_accounts: p.dao_hardfork_accounts.unwrap_or_else(Vec::new).into_iter().map(Into::into).collect(),
difficulty_hardfork_transition: p.difficulty_hardfork_transition.map_or(0x7fffffffffffffff, Into::into), difficulty_hardfork_transition: p.difficulty_hardfork_transition.map_or(0x7fffffffffffffff, Into::into),
difficulty_hardfork_bound_divisor: p.difficulty_hardfork_bound_divisor.map_or(p.difficulty_bound_divisor.into(), Into::into), difficulty_hardfork_bound_divisor: p.difficulty_hardfork_bound_divisor.map_or(p.difficulty_bound_divisor.into(), Into::into),
bomb_defuse_transition: p.bomb_defuse_transition.map_or(0x7fffffffffffffff, Into::into), bomb_defuse_transition: p.bomb_defuse_transition.map_or(0x7fffffffffffffff, Into::into),
eip150_transition: p.eip150_transition.map_or(0, Into::into),
} }
} }
} }
@ -117,12 +120,14 @@ impl Engine for Ethash {
} }
fn schedule(&self, env_info: &EnvInfo) -> Schedule { fn schedule(&self, env_info: &EnvInfo) -> Schedule {
trace!(target: "client", "Creating schedule. fCML={}", self.ethash_params.frontier_compatibility_mode_limit); trace!(target: "client", "Creating schedule. fCML={}, bGCML={}", self.ethash_params.homestead_transition, self.ethash_params.eip150_transition);
if env_info.number < self.ethash_params.frontier_compatibility_mode_limit { if env_info.number < self.ethash_params.homestead_transition {
Schedule::new_frontier() Schedule::new_frontier()
} else { } else if env_info.number < self.ethash_params.eip150_transition {
Schedule::new_homestead() Schedule::new_homestead()
} else {
Schedule::new_homestead_gas_fix()
} }
} }
@ -264,7 +269,7 @@ impl Engine for Ethash {
} }
fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> { fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> {
if header.number() >= self.ethash_params.frontier_compatibility_mode_limit { if header.number() >= self.ethash_params.homestead_transition {
try!(t.check_low_s()); try!(t.check_low_s());
} }
Ok(()) Ok(())
@ -290,7 +295,7 @@ impl Ethash {
false => self.ethash_params.difficulty_bound_divisor, false => self.ethash_params.difficulty_bound_divisor,
}; };
let duration_limit = self.ethash_params.duration_limit; let duration_limit = self.ethash_params.duration_limit;
let frontier_limit = self.ethash_params.frontier_compatibility_mode_limit; let frontier_limit = self.ethash_params.homestead_transition;
let mut target = if header.number() < frontier_limit { let mut target = if header.number() < frontier_limit {
if header.timestamp() >= parent.timestamp() + duration_limit { if header.timestamp() >= parent.timestamp() + duration_limit {

View File

@ -51,8 +51,11 @@ pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/fro
/// Create a new Homestead chain spec as though it never changed from Frontier. /// Create a new Homestead chain spec as though it never changed from Frontier.
pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) } pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) }
/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier.
pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) }
/// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8. /// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8.
pub fn new_daohardfork_test() -> Spec { load(include_bytes!("../../res/ethereum/daohardfork_test.json")) } pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) }
/// Create a new Frontier main net chain spec without genesis accounts. /// Create a new Frontier main net chain spec without genesis accounts.
pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) }

View File

@ -118,11 +118,12 @@ impl Factory {
} }
} }
/// Create new instance of specific `VMType` factory /// Create new instance of specific `VMType` factory, with a size in bytes
pub fn new(evm: VMType) -> Self { /// for caching jump destinations.
pub fn new(evm: VMType, cache_size: usize) -> Self {
Factory { Factory {
evm: evm, evm: evm,
evm_cache: Arc::new(SharedCache::default()), evm_cache: Arc::new(SharedCache::new(cache_size)),
} }
} }
@ -164,22 +165,22 @@ macro_rules! evm_test(
#[ignore] #[ignore]
#[cfg(feature = "jit")] #[cfg(feature = "jit")]
fn $name_jit() { fn $name_jit() {
$name_test(Factory::new(VMType::Jit)); $name_test(Factory::new(VMType::Jit, 1024 * 32));
} }
#[test] #[test]
fn $name_int() { fn $name_int() {
$name_test(Factory::new(VMType::Interpreter)); $name_test(Factory::new(VMType::Interpreter, 1024 * 32));
} }
}; };
($name_test: ident: $name_jit: ident, $name_int: ident) => { ($name_test: ident: $name_jit: ident, $name_int: ident) => {
#[test] #[test]
#[cfg(feature = "jit")] #[cfg(feature = "jit")]
fn $name_jit() { fn $name_jit() {
$name_test(Factory::new(VMType::Jit)); $name_test(Factory::new(VMType::Jit, 1024 * 32));
} }
#[test] #[test]
fn $name_int() { fn $name_int() {
$name_test(Factory::new(VMType::Interpreter)); $name_test(Factory::new(VMType::Interpreter, 1024 * 32));
} }
} }
); );
@ -193,13 +194,13 @@ macro_rules! evm_test_ignore(
#[cfg(feature = "jit")] #[cfg(feature = "jit")]
#[cfg(feature = "ignored-tests")] #[cfg(feature = "ignored-tests")]
fn $name_jit() { fn $name_jit() {
$name_test(Factory::new(VMType::Jit)); $name_test(Factory::new(VMType::Jit, 1024 * 32));
} }
#[test] #[test]
#[ignore] #[ignore]
#[cfg(feature = "ignored-tests")] #[cfg(feature = "ignored-tests")]
fn $name_int() { fn $name_int() {
$name_test(Factory::new(VMType::Interpreter)); $name_test(Factory::new(VMType::Interpreter, 1024 * 32));
} }
} }
); );

View File

@ -19,6 +19,7 @@ use super::u256_to_address;
use evm::{self, CostType}; use evm::{self, CostType};
use evm::instructions::{self, Instruction, InstructionInfo}; use evm::instructions::{self, Instruction, InstructionInfo};
use evm::interpreter::stack::Stack; use evm::interpreter::stack::Stack;
use evm::schedule::Schedule;
macro_rules! overflowing { macro_rules! overflowing {
($x: expr) => {{ ($x: expr) => {{
@ -31,7 +32,7 @@ macro_rules! overflowing {
#[cfg_attr(feature="dev", allow(enum_variant_names))] #[cfg_attr(feature="dev", allow(enum_variant_names))]
enum InstructionCost<Cost: CostType> { enum InstructionCost<Cost: CostType> {
Gas(Cost), Gas(Cost),
GasMem(Cost, Cost), GasMem(Cost, Cost, Option<Cost>),
GasMemCopy(Cost, Cost, Cost) GasMemCopy(Cost, Cost, Cost)
} }
@ -56,7 +57,37 @@ impl<Gas: CostType> Gasometer<Gas> {
} }
} }
/// How much gas is provided to a CALL/CREATE, given that we need to deduct `needed` for this operation
/// and that we `requested` some.
pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option<evm::Result<Gas>>) -> evm::Result<Gas> {
match schedule.sub_gas_cap_divisor {
Some(cap_divisor) if self.current_gas >= needed => {
let gas_remaining = self.current_gas - needed;
let max_gas_provided = gas_remaining - gas_remaining / Gas::from(cap_divisor);
if let Some(Ok(r)) = requested {
Ok(min(r, max_gas_provided))
} else {
Ok(max_gas_provided)
}
},
_ => {
if let Some(r) = requested {
r
} else if self.current_gas >= needed {
Ok(self.current_gas - needed)
} else {
Ok(0.into())
}
}
}
}
#[cfg_attr(feature="dev", allow(cyclomatic_complexity))] #[cfg_attr(feature="dev", allow(cyclomatic_complexity))]
/// Determine how much gas is used by the given instruction, given the machine's state.
///
/// We guarantee that the final element of the returned tuple (`provided`) will be `Some`
/// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case,
/// it will be the amount of gas that the current context provides to the child context.
pub fn get_gas_cost_mem( pub fn get_gas_cost_mem(
&mut self, &mut self,
ext: &evm::Ext, ext: &evm::Ext,
@ -64,7 +95,7 @@ impl<Gas: CostType> Gasometer<Gas> {
info: &InstructionInfo, info: &InstructionInfo,
stack: &Stack<U256>, stack: &Stack<U256>,
current_mem_size: usize, current_mem_size: usize,
) -> evm::Result<(Gas, Gas, usize)> { ) -> evm::Result<(Gas, Gas, usize, Option<Gas>)> {
let schedule = ext.schedule(); let schedule = ext.schedule();
let tier = instructions::get_tier_idx(info.tier); let tier = instructions::get_tier_idx(info.tier);
let default_gas = Gas::from(schedule.tier_step_gas[tier]); let default_gas = Gas::from(schedule.tier_step_gas[tier]);
@ -90,26 +121,42 @@ impl<Gas: CostType> Gasometer<Gas> {
instructions::SLOAD => { instructions::SLOAD => {
InstructionCost::Gas(Gas::from(schedule.sload_gas)) InstructionCost::Gas(Gas::from(schedule.sload_gas))
}, },
instructions::BALANCE => {
InstructionCost::Gas(Gas::from(schedule.balance_gas))
},
instructions::EXTCODESIZE => {
InstructionCost::Gas(Gas::from(schedule.extcodesize_gas))
},
instructions::SUICIDE => {
let mut gas = Gas::from(schedule.suicide_gas);
let address = u256_to_address(stack.peek(0));
if !ext.exists(&address) {
gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into()));
}
InstructionCost::Gas(gas)
},
instructions::MSTORE | instructions::MLOAD => { instructions::MSTORE | instructions::MLOAD => {
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32))) InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)), None)
}, },
instructions::MSTORE8 => { instructions::MSTORE8 => {
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1))) InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)), None)
}, },
instructions::RETURN => { instructions::RETURN => {
InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
}, },
instructions::SHA3 => { instructions::SHA3 => {
let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31)); let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31));
let words = w >> 5; let words = w >> 5;
let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words); let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words);
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
}, },
instructions::CALLDATACOPY | instructions::CODECOPY => { instructions::CALLDATACOPY | instructions::CODECOPY => {
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2))))
}, },
instructions::EXTCODECOPY => { instructions::EXTCODECOPY => {
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) InstructionCost::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3))))
}, },
instructions::LOG0...instructions::LOG4 => { instructions::LOG0...instructions::LOG4 => {
let no_of_topics = instructions::get_log_topics(instruction); let no_of_topics = instructions::get_log_topics(instruction);
@ -117,10 +164,10 @@ impl<Gas: CostType> Gasometer<Gas> {
let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas))); let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas)));
let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas))); let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
}, },
instructions::CALL | instructions::CALLCODE => { instructions::CALL | instructions::CALLCODE => {
let mut gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas)); let mut gas = Gas::from(schedule.call_gas);
let mem = cmp::max( let mem = cmp::max(
try!(mem_needed(stack.peek(5), stack.peek(6))), try!(mem_needed(stack.peek(5), stack.peek(6))),
try!(mem_needed(stack.peek(3), stack.peek(4))) try!(mem_needed(stack.peek(3), stack.peek(4)))
@ -129,27 +176,49 @@ impl<Gas: CostType> Gasometer<Gas> {
let address = u256_to_address(stack.peek(1)); let address = u256_to_address(stack.peek(1));
if instruction == instructions::CALL && !ext.exists(&address) { if instruction == instructions::CALL && !ext.exists(&address) {
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_new_account_gas))); gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into()));
}; };
if !stack.peek(2).is_zero() { if !stack.peek(2).is_zero() {
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_value_transfer_gas))); gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into()));
}; };
InstructionCost::GasMem(gas,mem) // TODO: refactor to avoid duplicate calculation here and later on.
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
let requested = Gas::from_u256(*stack.peek(0));
let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested)));
gas = overflowing!(gas.overflow_add(provided));
InstructionCost::GasMem(gas, mem, Some(provided))
}, },
instructions::DELEGATECALL => { instructions::DELEGATECALL => {
let gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas)); let mut gas = Gas::from(schedule.call_gas);
let mem = cmp::max( let mem = cmp::max(
try!(mem_needed(stack.peek(4), stack.peek(5))), try!(mem_needed(stack.peek(4), stack.peek(5))),
try!(mem_needed(stack.peek(2), stack.peek(3))) try!(mem_needed(stack.peek(2), stack.peek(3)))
); );
InstructionCost::GasMem(gas, mem)
// TODO: refactor to avoid duplicate calculation here and later on.
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
let requested = Gas::from_u256(*stack.peek(0));
let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested)));
gas = overflowing!(gas.overflow_add(provided));
InstructionCost::GasMem(gas, mem, Some(provided))
}, },
instructions::CREATE => { instructions::CREATE => {
let gas = Gas::from(schedule.create_gas); let mut gas = Gas::from(schedule.create_gas);
let mem = try!(mem_needed(stack.peek(1), stack.peek(2))); let mem = try!(mem_needed(stack.peek(1), stack.peek(2)));
InstructionCost::GasMem(gas, mem)
// TODO: refactor to avoid duplicate calculation here and later on.
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
let provided = try!(self.gas_provided(schedule, cost_so_far, None));
gas = overflowing!(gas.overflow_add(provided));
InstructionCost::GasMem(gas, mem, Some(provided))
}, },
instructions::EXP => { instructions::EXP => {
let expon = stack.peek(1); let expon = stack.peek(1);
@ -157,17 +226,17 @@ impl<Gas: CostType> Gasometer<Gas> {
let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes); let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes);
InstructionCost::Gas(gas) InstructionCost::Gas(gas)
}, },
_ => InstructionCost::Gas(default_gas) _ => InstructionCost::Gas(default_gas),
}; };
match cost { match cost {
InstructionCost::Gas(gas) => { InstructionCost::Gas(gas) => {
Ok((gas, self.current_mem_gas, 0)) Ok((gas, self.current_mem_gas, 0, None))
}, },
InstructionCost::GasMem(gas, mem_size) => { InstructionCost::GasMem(gas, mem_size, provided) => {
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
let gas = overflowing!(gas.overflow_add(mem_gas_cost)); let gas = overflowing!(gas.overflow_add(mem_gas_cost));
Ok((gas, new_mem_gas, new_mem_size)) Ok((gas, new_mem_gas, new_mem_size, provided))
}, },
InstructionCost::GasMemCopy(gas, mem_size, copy) => { InstructionCost::GasMemCopy(gas, mem_size, copy) => {
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
@ -175,7 +244,7 @@ impl<Gas: CostType> Gasometer<Gas> {
let copy_gas = Gas::from(schedule.copy_gas) * copy; let copy_gas = Gas::from(schedule.copy_gas) * copy;
let gas = overflowing!(gas.overflow_add(copy_gas)); let gas = overflowing!(gas.overflow_add(copy_gas));
let gas = overflowing!(gas.overflow_add(mem_gas_cost)); let gas = overflowing!(gas.overflow_add(mem_gas_cost));
Ok((gas, new_mem_gas, new_mem_size)) Ok((gas, new_mem_gas, new_mem_size, None))
} }
} }
} }

View File

@ -81,8 +81,6 @@ impl<'a> CodeReader<'a> {
enum InstructionResult<Gas> { enum InstructionResult<Gas> {
Ok, Ok,
UseAllGas,
GasLeft(Gas),
UnusedGas(Gas), UnusedGas(Gas),
JumpToPosition(U256), JumpToPosition(U256),
// gas left, init_orf, init_size // gas left, init_orf, init_size
@ -104,7 +102,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
let mut informant = informant::EvmInformant::new(ext.depth()); let mut informant = informant::EvmInformant::new(ext.depth());
let code = &params.code.as_ref().unwrap(); let code = &params.code.as_ref().expect("exec always called with code; qed");
let valid_jump_destinations = self.cache.jump_destinations(&params.code_hash, code); let valid_jump_destinations = self.cache.jump_destinations(&params.code_hash, code);
let mut gasometer = Gasometer::<Cost>::new(try!(Cost::from_u256(params.gas))); let mut gasometer = Gasometer::<Cost>::new(try!(Cost::from_u256(params.gas)));
@ -120,7 +118,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
try!(self.verify_instruction(ext, instruction, info, &stack)); try!(self.verify_instruction(ext, instruction, info, &stack));
// Calculate gas cost // Calculate gas cost
let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size())); let (gas_cost, mem_gas, mem_size, provided) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size()));
// TODO: make compile-time removable if too much of a performance hit. // TODO: make compile-time removable if too much of a performance hit.
let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256()); let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256());
@ -138,27 +136,21 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
// Execute instruction // Execute instruction
let result = try!(self.exec_instruction( let result = try!(self.exec_instruction(
gasometer.current_gas, &params, ext, instruction, &mut reader, &mut stack gasometer.current_gas, &params, ext, instruction, &mut reader, &mut stack, provided
)); ));
evm_debug!({ informant.after_instruction(instruction) }); evm_debug!({ informant.after_instruction(instruction) });
if let InstructionResult::UnusedGas(ref gas) = result {
gasometer.current_gas = gasometer.current_gas + *gas;
}
if trace_executed { if trace_executed {
ext.trace_executed(gasometer.current_gas.as_u256(), stack.peek_top(info.ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written); ext.trace_executed(gasometer.current_gas.as_u256(), stack.peek_top(info.ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written);
} }
// Advance // Advance
match result { match result {
InstructionResult::Ok => {},
InstructionResult::UnusedGas(gas) => {
gasometer.current_gas = gasometer.current_gas + gas;
},
InstructionResult::UseAllGas => {
gasometer.current_gas = Cost::from(0);
},
InstructionResult::GasLeft(gas_left) => {
gasometer.current_gas = gas_left;
},
InstructionResult::JumpToPosition(position) => { InstructionResult::JumpToPosition(position) => {
let pos = try!(self.verify_jump(position, &valid_jump_destinations)); let pos = try!(self.verify_jump(position, &valid_jump_destinations));
reader.position = pos; reader.position = pos;
@ -168,6 +160,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
return Ok(GasLeft::NeedsReturn(gas.as_u256(), self.mem.read_slice(off, size))); return Ok(GasLeft::NeedsReturn(gas.as_u256(), self.mem.read_slice(off, size)));
}, },
InstructionResult::StopExecution => break, InstructionResult::StopExecution => break,
_ => {},
} }
} }
informant.done(); informant.done();
@ -250,7 +243,8 @@ impl<Cost: CostType> Interpreter<Cost> {
ext: &mut evm::Ext, ext: &mut evm::Ext,
instruction: Instruction, instruction: Instruction,
code: &mut CodeReader, code: &mut CodeReader,
stack: &mut Stack<U256> stack: &mut Stack<U256>,
provided: Option<Cost>
) -> evm::Result<InstructionResult<Cost>> { ) -> evm::Result<InstructionResult<Cost>> {
match instruction { match instruction {
instructions::JUMP => { instructions::JUMP => {
@ -275,31 +269,32 @@ impl<Cost: CostType> Interpreter<Cost> {
let endowment = stack.pop_back(); let endowment = stack.pop_back();
let init_off = stack.pop_back(); let init_off = stack.pop_back();
let init_size = stack.pop_back(); let init_size = stack.pop_back();
let create_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is `CREATE`; qed");
let contract_code = self.mem.read_slice(init_off, init_size); let contract_code = self.mem.read_slice(init_off, init_size);
let can_create = ext.balance(&params.address) >= endowment && ext.depth() < ext.schedule().max_depth; let can_create = ext.balance(&params.address) >= endowment && ext.depth() < ext.schedule().max_depth;
if !can_create { if !can_create {
stack.push(U256::zero()); stack.push(U256::zero());
return Ok(InstructionResult::Ok); return Ok(InstructionResult::UnusedGas(create_gas));
} }
let create_result = ext.create(&gas.as_u256(), &endowment, contract_code); let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code);
return match create_result { return match create_result {
ContractCreateResult::Created(address, gas_left) => { ContractCreateResult::Created(address, gas_left) => {
stack.push(address_to_u256(address)); stack.push(address_to_u256(address));
Ok(InstructionResult::GasLeft(Cost::from_u256(gas_left).expect("Gas left cannot be greater."))) Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")))
}, },
ContractCreateResult::Failed => { ContractCreateResult::Failed => {
stack.push(U256::zero()); stack.push(U256::zero());
// TODO [todr] Should we just StopExecution here? Ok(InstructionResult::Ok)
Ok(InstructionResult::UseAllGas)
} }
}; };
}, },
instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL => { instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL => {
assert!(ext.schedule().call_value_transfer_gas > ext.schedule().call_stipend, "overflow possible"); assert!(ext.schedule().call_value_transfer_gas > ext.schedule().call_stipend, "overflow possible");
let call_gas = Cost::from_u256(stack.pop_back()).expect("Gas is already validated."); stack.pop_back();
let call_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is one of `CALL`/`CALLCODE`/`DELEGATECALL`; qed");
let code_address = stack.pop_back(); let code_address = stack.pop_back();
let code_address = u256_to_address(&code_address); let code_address = u256_to_address(&code_address);
@ -317,17 +312,17 @@ impl<Cost: CostType> Interpreter<Cost> {
// Add stipend (only CALL|CALLCODE when value > 0) // Add stipend (only CALL|CALLCODE when value > 0)
let call_gas = call_gas + value.map_or_else(|| Cost::from(0), |val| match val.is_zero() { let call_gas = call_gas + value.map_or_else(|| Cost::from(0), |val| match val.is_zero() {
false => Cost::from(ext.schedule().call_stipend), false => Cost::from(ext.schedule().call_stipend),
true => Cost::from(0) true => Cost::from(0),
}); });
// Get sender & receive addresses, check if we have balance // Get sender & receive addresses, check if we have balance
let (sender_address, receive_address, has_balance, call_type) = match instruction { let (sender_address, receive_address, has_balance, call_type) = match instruction {
instructions::CALL => { instructions::CALL => {
let has_balance = ext.balance(&params.address) >= value.unwrap(); let has_balance = ext.balance(&params.address) >= value.expect("value set for all but delegate call; qed");
(&params.address, &code_address, has_balance, CallType::Call) (&params.address, &code_address, has_balance, CallType::Call)
}, },
instructions::CALLCODE => { instructions::CALLCODE => {
let has_balance = ext.balance(&params.address) >= value.unwrap(); let has_balance = ext.balance(&params.address) >= value.expect("value set for all but delegate call; qed");
(&params.address, &params.address, has_balance, CallType::CallCode) (&params.address, &params.address, has_balance, CallType::CallCode)
}, },
instructions::DELEGATECALL => (&params.sender, &params.address, true, CallType::DelegateCall), instructions::DELEGATECALL => (&params.sender, &params.address, true, CallType::DelegateCall),

View File

@ -21,25 +21,66 @@ use util::sha3::*;
use bit_set::BitSet; use bit_set::BitSet;
use super::super::instructions; use super::super::instructions;
const CACHE_CODE_ITEMS: usize = 65536; const INITIAL_CAPACITY: usize = 32;
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
/// GLobal cache for EVM interpreter /// Global cache for EVM interpreter
pub struct SharedCache { pub struct SharedCache {
jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>> jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>>,
max_size: usize,
cur_size: Mutex<usize>,
} }
impl SharedCache { impl SharedCache {
/// Get jump destincations bitmap for a contract. /// Create a jump destinations cache with a maximum size in bytes
/// to cache.
pub fn new(max_size: usize) -> Self {
SharedCache {
jump_destinations: Mutex::new(LruCache::new(INITIAL_CAPACITY)),
max_size: max_size * 8, // dealing with bits here.
cur_size: Mutex::new(0),
}
}
/// Get jump destinations bitmap for a contract.
pub fn jump_destinations(&self, code_hash: &H256, code: &[u8]) -> Arc<BitSet> { pub fn jump_destinations(&self, code_hash: &H256, code: &[u8]) -> Arc<BitSet> {
if code_hash == &SHA3_EMPTY { if code_hash == &SHA3_EMPTY {
return Self::find_jump_destinations(code); return Self::find_jump_destinations(code);
} }
if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) { if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
return d.clone(); return d.clone();
} }
let d = Self::find_jump_destinations(code); let d = Self::find_jump_destinations(code);
self.jump_destinations.lock().insert(code_hash.clone(), d.clone());
{
let mut cur_size = self.cur_size.lock();
*cur_size += d.capacity();
let mut jump_dests = self.jump_destinations.lock();
let cap = jump_dests.capacity();
// grow the cache as necessary; it operates on amount of items
// but we're working based on memory usage.
if jump_dests.len() == cap && *cur_size < self.max_size {
jump_dests.set_capacity(cap * 2);
}
// account for any element displaced from the cache.
if let Some(lru) = jump_dests.insert(code_hash.clone(), d.clone()) {
*cur_size -= lru.capacity();
}
// remove elements until we are below the memory target.
while *cur_size > self.max_size {
match jump_dests.remove_lru() {
Some((_, v)) => *cur_size -= v.capacity(),
_ => break,
}
}
}
d d
} }
@ -57,15 +98,15 @@ impl SharedCache {
} }
position += 1; position += 1;
} }
jump_dests.shrink_to_fit();
Arc::new(jump_dests) Arc::new(jump_dests)
} }
} }
impl Default for SharedCache { impl Default for SharedCache {
fn default() -> SharedCache { fn default() -> Self {
SharedCache { SharedCache::new(DEFAULT_CACHE_SIZE)
jump_destinations: Mutex::new(LruCache::new(CACHE_CODE_ITEMS)),
}
} }
} }

View File

@ -359,7 +359,7 @@ impl evm::Evm for JitEvm {
data.timestamp = ext.env_info().timestamp as i64; data.timestamp = ext.env_info().timestamp as i64;
self.context = Some(unsafe { evmjit::ContextHandle::new(data, schedule, &mut ext_handle) }); self.context = Some(unsafe { evmjit::ContextHandle::new(data, schedule, &mut ext_handle) });
let mut context = self.context.as_mut().unwrap(); let mut context = self.context.as_mut().expect("context handle set on the prior line; qed");
let res = context.exec(); let res = context.exec();
match res { match res {

View File

@ -80,6 +80,19 @@ pub struct Schedule {
pub tx_data_non_zero_gas: usize, pub tx_data_non_zero_gas: usize,
/// Gas price for copying memory /// Gas price for copying memory
pub copy_gas: usize, pub copy_gas: usize,
/// Price of EXTCODESIZE
pub extcodesize_gas: usize,
/// Base price of EXTCODECOPY
pub extcodecopy_base_gas: usize,
/// Price of BALANCE
pub balance_gas: usize,
/// Price of SUICIDE
pub suicide_gas: usize,
/// Amount of additional gas to pay when SUICIDE credits a non-existant account
pub suicide_to_new_account_cost: usize,
/// If Some(x): let limit = GAS * (x - 1) / x; let CALL's gas = min(requested, limit). let CREATE's gas = limit.
/// If None: let CALL's gas = (requested > GAS ? [OOG] : GAS). let CREATE's gas = GAS
pub sub_gas_cap_divisor: Option<usize>,
} }
impl Schedule { impl Schedule {
@ -93,6 +106,49 @@ impl Schedule {
Self::new(true, true, 53000) Self::new(true, true, 53000)
} }
/// Schedule for the Homestead-era of the Ethereum main net.
pub fn new_homestead_gas_fix() -> Schedule {
Schedule{
exceptional_failed_code_deposit: true,
have_delegate_call: true,
stack_limit: 1024,
max_depth: 1024,
tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0],
exp_gas: 10,
exp_byte_gas: 10,
sha3_gas: 30,
sha3_word_gas: 6,
sload_gas: 200,
sstore_set_gas: 20000,
sstore_reset_gas: 5000,
sstore_refund_gas: 15000,
jumpdest_gas: 1,
log_gas: 375,
log_data_gas: 8,
log_topic_gas: 375,
create_gas: 32000,
call_gas: 700,
call_stipend: 2300,
call_value_transfer_gas: 9000,
call_new_account_gas: 25000,
suicide_refund_gas: 24000,
memory_gas: 3,
quad_coeff_div: 512,
create_data_gas: 200,
tx_gas: 21000,
tx_create_gas: 53000,
tx_data_zero_gas: 4,
tx_data_non_zero_gas: 68,
copy_gas: 3,
extcodesize_gas: 700,
extcodecopy_base_gas: 700,
balance_gas: 400,
suicide_gas: 5000,
suicide_to_new_account_cost: 25000,
sub_gas_cap_divisor: Some(64),
}
}
fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule { fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
Schedule{ Schedule{
exceptional_failed_code_deposit: efcd, exceptional_failed_code_deposit: efcd,
@ -126,6 +182,12 @@ impl Schedule {
tx_data_zero_gas: 4, tx_data_zero_gas: 4,
tx_data_non_zero_gas: 68, tx_data_non_zero_gas: 68,
copy_gas: 3, copy_gas: 3,
extcodesize_gas: 20,
extcodecopy_base_gas: 20,
balance_gas: 20,
suicide_gas: 0,
suicide_to_new_account_cost: 0,
sub_gas_cap_divisor: None,
} }
} }
} }

View File

@ -817,7 +817,7 @@ fn test_signextend(factory: super::Factory) {
#[test] // JIT just returns out of gas #[test] // JIT just returns out of gas
fn test_badinstruction_int() { fn test_badinstruction_int() {
let factory = super::Factory::new(VMType::Interpreter); let factory = super::Factory::new(VMType::Interpreter, 1024 * 32);
let code = "af".from_hex().unwrap(); let code = "af".from_hex().unwrap();
let mut params = ActionParams::default(); let mut params = ActionParams::default();

View File

@ -427,8 +427,16 @@ impl<'a> Executive<'a> {
trace!("exec::finalize: t.gas={}, sstore_refunds={}, suicide_refunds={}, refunds_bound={}, gas_left_prerefund={}, refunded={}, gas_left={}, gas_used={}, refund_value={}, fees_value={}\n", trace!("exec::finalize: t.gas={}, sstore_refunds={}, suicide_refunds={}, refunds_bound={}, gas_left_prerefund={}, refunded={}, gas_left={}, gas_used={}, refund_value={}, fees_value={}\n",
t.gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value); t.gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value);
trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, t.sender().unwrap()); let sender = match t.sender() {
self.state.add_balance(&t.sender().unwrap(), &refund_value); Ok(sender) => sender,
Err(e) => {
debug!(target: "executive", "attempted to finalize transaction without sender: {}", e);
return Err(ExecutionError::Internal);
}
};
trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, sender);
self.state.add_balance(&sender, &refund_value);
trace!("exec::finalize: Compensating author: fees_value={}, author={}\n", fees_value, &self.info.author); trace!("exec::finalize: Compensating author: fees_value={}, author={}\n", fees_value, &self.info.author);
self.state.add_balance(&self.info.author, &fees_value); self.state.add_balance(&self.info.author, &fees_value);
@ -598,7 +606,7 @@ mod tests {
#[test] #[test]
// Tracing is not suported in JIT // Tracing is not suported in JIT
fn test_call_to_create() { fn test_call_to_create() {
let factory = Factory::new(VMType::Interpreter); let factory = Factory::new(VMType::Interpreter, 1024 * 32);
// code: // code:
// //
@ -697,7 +705,7 @@ mod tests {
VMOperation { pc: 33, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99985.into(), stack_push: vec_into![29], mem_diff: None, store_diff: None }) }, VMOperation { pc: 33, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99985.into(), stack_push: vec_into![29], mem_diff: None, store_diff: None }) },
VMOperation { pc: 35, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99982.into(), stack_push: vec_into![3], mem_diff: None, store_diff: None }) }, VMOperation { pc: 35, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99982.into(), stack_push: vec_into![3], mem_diff: None, store_diff: None }) },
VMOperation { pc: 37, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![23], mem_diff: None, store_diff: None }) }, VMOperation { pc: 37, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![23], mem_diff: None, store_diff: None }) },
VMOperation { pc: 39, instruction: 240, gas_cost: 32000.into(), executed: Some(VMExecutedOperation { gas_used: 67979.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) }, VMOperation { pc: 39, instruction: 240, gas_cost: 99979.into(), executed: Some(VMExecutedOperation { gas_used: 64755.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) },
VMOperation { pc: 40, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 64752.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) }, VMOperation { pc: 40, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 64752.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 42, instruction: 85, gas_cost: 20000.into(), executed: Some(VMExecutedOperation { gas_used: 44752.into(), stack_push: vec_into![], mem_diff: None, store_diff: Some(StorageDiff { location: 0.into(), value: U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap() }) }) } VMOperation { pc: 42, instruction: 85, gas_cost: 20000.into(), executed: Some(VMExecutedOperation { gas_used: 44752.into(), stack_push: vec_into![], mem_diff: None, store_diff: Some(StorageDiff { location: 0.into(), value: U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap() }) }) }
], ],
@ -724,7 +732,7 @@ mod tests {
#[test] #[test]
fn test_create_contract() { fn test_create_contract() {
// Tracing is not supported in JIT // Tracing is not supported in JIT
let factory = Factory::new(VMType::Interpreter); let factory = Factory::new(VMType::Interpreter, 1024 * 32);
// code: // code:
// //
// 60 10 - push 16 // 60 10 - push 16

View File

@ -199,8 +199,9 @@ impl Header {
match &mut *hash { match &mut *hash {
&mut Some(ref h) => h.clone(), &mut Some(ref h) => h.clone(),
hash @ &mut None => { hash @ &mut None => {
*hash = Some(self.rlp_sha3(Seal::With)); let h = self.rlp_sha3(Seal::With);
hash.as_ref().unwrap().clone() *hash = Some(h.clone());
h
} }
} }
} }
@ -211,8 +212,9 @@ impl Header {
match &mut *hash { match &mut *hash {
&mut Some(ref h) => h.clone(), &mut Some(ref h) => h.clone(),
hash @ &mut None => { hash @ &mut None => {
*hash = Some(self.rlp_sha3(Seal::Without)); let h = self.rlp_sha3(Seal::Without);
hash.as_ref().unwrap().clone() *hash = Some(h.clone());
h
} }
} }
} }

View File

@ -48,7 +48,8 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
let mut spec = match era { let mut spec = match era {
ChainEra::Frontier => ethereum::new_frontier_test(), ChainEra::Frontier => ethereum::new_frontier_test(),
ChainEra::Homestead => ethereum::new_homestead_test(), ChainEra::Homestead => ethereum::new_homestead_test(),
ChainEra::DaoHardfork => ethereum::new_daohardfork_test(), ChainEra::Eip150 => ethereum::new_eip150_test(),
ChainEra::TransitionTest => ethereum::new_transition_test(),
}; };
spec.set_genesis_state(state); spec.set_genesis_state(state);
spec.overwrite_genesis_params(genesis); spec.overwrite_genesis_params(genesis);
@ -116,14 +117,38 @@ mod frontier_era_tests {
declare_test!{BlockchainTests_RandomTests_bl201507071825GO, "BlockchainTests/RandomTests/bl201507071825GO"} declare_test!{BlockchainTests_RandomTests_bl201507071825GO, "BlockchainTests/RandomTests/bl201507071825GO"}
} }
mod daohardfork_tests { mod transition_tests {
use tests::helpers::*; use tests::helpers::*;
use super::json_chain_test; use super::json_chain_test;
fn do_json_test(json_data: &[u8]) -> Vec<String> { fn do_json_test(json_data: &[u8]) -> Vec<String> {
json_chain_test(json_data, ChainEra::DaoHardfork) json_chain_test(json_data, ChainEra::TransitionTest)
} }
declare_test!{BlockchainTests_TestNetwork_bcSimpleTransitionTest, "BlockchainTests/TestNetwork/bcSimpleTransitionTest"} declare_test!{BlockchainTests_TestNetwork_bcSimpleTransitionTest, "BlockchainTests/TestNetwork/bcSimpleTransitionTest"}
declare_test!{BlockchainTests_TestNetwork_bcTheDaoTest, "BlockchainTests/TestNetwork/bcTheDaoTest"} declare_test!{BlockchainTests_TestNetwork_bcTheDaoTest, "BlockchainTests/TestNetwork/bcTheDaoTest"}
declare_test!{BlockchainTests_TestNetwork_bcEIP150Test, "BlockchainTests/TestNetwork/bcEIP150Test"}
}
mod eip150_blockchain_tests {
use tests::helpers::*;
use super::json_chain_test;
fn do_json_test(json_data: &[u8]) -> Vec<String> {
json_chain_test(json_data, ChainEra::Eip150)
}
declare_test!{BlockchainTests_EIP150_bcBlockGasLimitTest, "BlockchainTests/EIP150/bcBlockGasLimitTest"}
declare_test!{BlockchainTests_EIP150_bcForkStressTest, "BlockchainTests/EIP150/bcForkStressTest"}
declare_test!{BlockchainTests_EIP150_bcGasPricerTest, "BlockchainTests/EIP150/bcGasPricerTest"}
declare_test!{BlockchainTests_EIP150_bcInvalidHeaderTest, "BlockchainTests/EIP150/bcInvalidHeaderTest"}
declare_test!{BlockchainTests_EIP150_bcInvalidRLPTest, "BlockchainTests/EIP150/bcInvalidRLPTest"}
declare_test!{BlockchainTests_EIP150_bcMultiChainTest, "BlockchainTests/EIP150/bcMultiChainTest"}
declare_test!{BlockchainTests_EIP150_bcRPC_API_Test, "BlockchainTests/EIP150/bcRPC_API_Test"}
declare_test!{BlockchainTests_EIP150_bcStateTest, "BlockchainTests/EIP150/bcStateTest"}
declare_test!{BlockchainTests_EIP150_bcTotalDifficultyTest, "BlockchainTests/EIP150/bcTotalDifficultyTest"}
declare_test!{BlockchainTests_EIP150_bcUncleHeaderValiditiy, "BlockchainTests/EIP150/bcUncleHeaderValiditiy"}
declare_test!{BlockchainTests_EIP150_bcUncleTest, "BlockchainTests/EIP150/bcUncleTest"}
declare_test!{BlockchainTests_EIP150_bcValidBlockTest, "BlockchainTests/EIP150/bcValidBlockTest"}
declare_test!{BlockchainTests_EIP150_bcWalletTest, "BlockchainTests/EIP150/bcWalletTest"}
} }

View File

@ -0,0 +1,43 @@
// 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 super::test_common::*;
use tests::helpers::*;
use super::state::json_chain_test;
fn do_json_test(json_data: &[u8]) -> Vec<String> {
json_chain_test(json_data, ChainEra::Eip150)
}
declare_test!{StateTests_EIP150_stEIPSpecificTest, "StateTests/EIP150/stEIPSpecificTest"}
declare_test!{StateTests_EIP150_stEIPsingleCodeGasPrices, "StateTests/EIP150/stEIPsingleCodeGasPrices"}
declare_test!{StateTests_EIP150_stMemExpandingEIPCalls, "StateTests/EIP150/stMemExpandingEIPCalls"}
declare_test!{StateTests_EIP150_stCallCodes, "StateTests/EIP150/Homestead/stCallCodes"}
declare_test!{StateTests_EIP150_stCallCreateCallCodeTest, "StateTests/EIP150/Homestead/stCallCreateCallCodeTest"}
declare_test!{StateTests_EIP150_stDelegatecallTest, "StateTests/EIP150/Homestead/stDelegatecallTest"}
declare_test!{StateTests_EIP150_stInitCodeTest, "StateTests/EIP150/Homestead/stInitCodeTest"}
declare_test!{StateTests_EIP150_stLogTests, "StateTests/EIP150/Homestead/stLogTests"}
declare_test!{heavy => StateTests_EIP150_stMemoryStressTest, "StateTests/EIP150/Homestead/stMemoryStressTest"}
declare_test!{heavy => StateTests_EIP150_stMemoryTest, "StateTests/EIP150/Homestead/stMemoryTest"}
declare_test!{StateTests_EIP150_stPreCompiledContracts, "StateTests/EIP150/Homestead/stPreCompiledContracts"}
declare_test!{heavy => StateTests_EIP150_stQuadraticComplexityTest, "StateTests/EIP150/Homestead/stQuadraticComplexityTest"}
declare_test!{StateTests_EIP150_stRecursiveCreate, "StateTests/EIP150/Homestead/stRecursiveCreate"}
declare_test!{StateTests_EIP150_stRefundTest, "StateTests/EIP150/Homestead/stRefundTest"}
declare_test!{StateTests_EIP150_stSpecialTest, "StateTests/EIP150/Homestead/stSpecialTest"}
declare_test!{StateTests_EIP150_stSystemOperationsTest, "StateTests/EIP150/Homestead/stSystemOperationsTest"}
declare_test!{StateTests_EIP150_stTransactionTest, "StateTests/EIP150/Homestead/stTransactionTest"}
declare_test!{StateTests_EIP150_stWalletTest, "StateTests/EIP150/Homestead/stWalletTest"}

View File

@ -191,7 +191,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec<String> {
state.populate_from(From::from(vm.pre_state.clone())); state.populate_from(From::from(vm.pre_state.clone()));
let info = From::from(vm.env); let info = From::from(vm.env);
let engine = TestEngine::new(1); let engine = TestEngine::new(1);
let vm_factory = Factory::new(vm_type.clone()); let vm_factory = Factory::new(vm_type.clone(), 1024 * 32);
let params = ActionParams::from(vm.transaction); let params = ActionParams::from(vm.transaction);
let mut substate = Substate::new(); let mut substate = Substate::new();

View File

@ -37,6 +37,6 @@ declare_test!{BlockchainTests_Homestead_bcUncleTest, "BlockchainTests/Homestead/
declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"} declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"}
declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"} declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"}
declare_test!{BlockchainTests_Homestead_bcShanghaiLove, "BlockchainTests/Homestead/bcShanghaiLove"} declare_test!{BlockchainTests_Homestead_bcShanghaiLove, "BlockchainTests/Homestead/bcShanghaiLove"}
// Uncomment once the test is correct. // TODO [ToDr] uncomment as soon as eip150 tests are merged to develop branch of ethereum/tests
// declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"} // declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"}
declare_test!{BlockchainTests_Homestead_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"} declare_test!{BlockchainTests_Homestead_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"}

View File

@ -23,4 +23,5 @@ mod state;
mod chain; mod chain;
mod homestead_state; mod homestead_state;
mod homestead_chain; mod homestead_chain;
mod eip150_state;
mod trie; mod trie;

View File

@ -20,10 +20,6 @@ use pod_state::{self, PodState};
use ethereum; use ethereum;
use ethjson; use ethjson;
fn do_json_test(json_data: &[u8]) -> Vec<String> {
json_chain_test(json_data, ChainEra::Frontier)
}
pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> { pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
init_log(); init_log();
let tests = ethjson::state::Test::load(json_data).unwrap(); let tests = ethjson::state::Test::load(json_data).unwrap();
@ -31,7 +27,8 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
let engine = match era { let engine = match era {
ChainEra::Frontier => ethereum::new_mainnet_like().engine, ChainEra::Frontier => ethereum::new_mainnet_like().engine,
ChainEra::Homestead => ethereum::new_homestead_test().engine, ChainEra::Homestead => ethereum::new_homestead_test().engine,
ChainEra::DaoHardfork => ethereum::new_daohardfork_test().engine, ChainEra::Eip150 => ethereum::new_eip150_test().engine,
ChainEra::TransitionTest => ethereum::new_transition_test().engine,
}; };
for (name, test) in tests.into_iter() { for (name, test) in tests.into_iter() {
@ -93,6 +90,14 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
failed failed
} }
mod frontier_tests {
use super::json_chain_test;
use tests::helpers::ChainEra;
fn do_json_test(json_data: &[u8]) -> Vec<String> {
json_chain_test(json_data, ChainEra::Frontier)
}
declare_test!{StateTests_stBlockHashTest, "StateTests/stBlockHashTest"} declare_test!{StateTests_stBlockHashTest, "StateTests/stBlockHashTest"}
declare_test!{StateTests_stCallCodes, "StateTests/stCallCodes"} declare_test!{StateTests_stCallCodes, "StateTests/stCallCodes"}
declare_test!{StateTests_stCallCreateCallCodeTest, "StateTests/stCallCreateCallCodeTest"} declare_test!{StateTests_stCallCreateCallCodeTest, "StateTests/stCallCreateCallCodeTest"}
@ -112,7 +117,6 @@ declare_test!{StateTests_stTransactionTest, "StateTests/stTransactionTest"}
declare_test!{StateTests_stTransitionTest, "StateTests/stTransitionTest"} declare_test!{StateTests_stTransitionTest, "StateTests/stTransitionTest"}
declare_test!{StateTests_stWalletTest, "StateTests/stWalletTest"} declare_test!{StateTests_stWalletTest, "StateTests/stWalletTest"}
declare_test!{StateTests_RandomTests_st201503121803PYTHON, "StateTests/RandomTests/st201503121803PYTHON"} declare_test!{StateTests_RandomTests_st201503121803PYTHON, "StateTests/RandomTests/st201503121803PYTHON"}
declare_test!{StateTests_RandomTests_st201503121806PYTHON, "StateTests/RandomTests/st201503121806PYTHON"} declare_test!{StateTests_RandomTests_st201503121806PYTHON, "StateTests/RandomTests/st201503121806PYTHON"}
declare_test!{StateTests_RandomTests_st201503121848GO, "StateTests/RandomTests/st201503121848GO"} declare_test!{StateTests_RandomTests_st201503121848GO, "StateTests/RandomTests/st201503121848GO"}
@ -755,3 +759,4 @@ declare_test!{StateTests_RandomTests_st201506091836GO, "StateTests/RandomTests/s
declare_test!{StateTests_RandomTests_st201506092032GO, "StateTests/RandomTests/st201506092032GO"} declare_test!{StateTests_RandomTests_st201506092032GO, "StateTests/RandomTests/st201506092032GO"}
declare_test!{StateTests_RandomTests_st201506101359JS, "StateTests/RandomTests/st201506101359JS"} declare_test!{StateTests_RandomTests_st201506101359JS, "StateTests/RandomTests/st201506101359JS"}
declare_test!{StateTests_RandomTests_st201507030359GO, "StateTests/RandomTests/st201507030359GO"} declare_test!{StateTests_RandomTests_st201507030359GO, "StateTests/RandomTests/st201507030359GO"}
}

View File

@ -30,7 +30,7 @@ use transaction::{Action, SignedTransaction};
use receipt::{Receipt, RichReceipt}; use receipt::{Receipt, RichReceipt};
use spec::Spec; use spec::Spec;
use engines::Engine; use engines::Engine;
use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionOrigin}; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
use miner::work_notify::WorkPoster; use miner::work_notify::WorkPoster;
use client::TransactionImportResult; use client::TransactionImportResult;
use miner::price_info::PriceInfo; use miner::price_info::PriceInfo;
@ -76,6 +76,8 @@ pub struct MinerOptions {
pub tx_gas_limit: U256, pub tx_gas_limit: U256,
/// Maximum size of the transaction queue. /// Maximum size of the transaction queue.
pub tx_queue_size: usize, pub tx_queue_size: usize,
/// Strategy to use for prioritizing transactions in the queue.
pub tx_queue_strategy: PrioritizationStrategy,
/// Whether we should fallback to providing all the queue's transactions or just pending. /// Whether we should fallback to providing all the queue's transactions or just pending.
pub pending_set: PendingSet, pub pending_set: PendingSet,
/// How many historical work packages can we store before running out? /// How many historical work packages can we store before running out?
@ -94,12 +96,13 @@ impl Default for MinerOptions {
reseal_on_external_tx: false, reseal_on_external_tx: false,
reseal_on_own_tx: true, reseal_on_own_tx: true,
tx_gas_limit: !U256::zero(), tx_gas_limit: !U256::zero(),
tx_queue_size: 2048, tx_queue_size: 1024,
tx_queue_gas_limit: GasLimit::Auto,
tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice,
pending_set: PendingSet::AlwaysQueue, pending_set: PendingSet::AlwaysQueue,
reseal_min_period: Duration::from_secs(2), reseal_min_period: Duration::from_secs(2),
work_queue_size: 20, work_queue_size: 20,
enable_resubmission: true, enable_resubmission: true,
tx_queue_gas_limit: GasLimit::Auto,
} }
} }
} }
@ -133,7 +136,7 @@ impl GasPriceCalibrator {
let gas_per_tx: f32 = 21000.0; let gas_per_tx: f32 = 21000.0;
let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx; let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx;
info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas))); info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas)));
set_price(U256::from_dec_str(&format!("{:.0}", wei_per_gas)).unwrap()); set_price(U256::from(wei_per_gas as u64));
}) { }) {
self.next_calibration = Instant::now() + self.options.recalibration_period; self.next_calibration = Instant::now() + self.options.recalibration_period;
} else { } else {
@ -212,7 +215,9 @@ impl Miner {
GasLimit::Fixed(ref limit) => *limit, GasLimit::Fixed(ref limit) => *limit,
_ => !U256::zero(), _ => !U256::zero(),
}; };
let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, gas_limit, options.tx_gas_limit))); let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(
options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit
)));
Miner { Miner {
transaction_queue: txq, transaction_queue: txq,
next_allowed_reseal: Mutex::new(Instant::now()), next_allowed_reseal: Mutex::new(Instant::now()),
@ -282,6 +287,7 @@ impl Miner {
trace!(target: "miner", "prepare_block: done recalibration."); trace!(target: "miner", "prepare_block: done recalibration.");
} }
let _timer = PerfTimer::new("prepare_block");
let (transactions, mut open_block, original_work_hash) = { let (transactions, mut open_block, original_work_hash) = {
let transactions = {self.transaction_queue.lock().top_transactions()}; let transactions = {self.transaction_queue.lock().top_transactions()};
let mut sealing_work = self.sealing_work.lock(); let mut sealing_work = self.sealing_work.lock();
@ -468,8 +474,8 @@ impl Miner {
let mut queue = self.transaction_queue.lock(); let mut queue = self.transaction_queue.lock();
queue.set_gas_limit(gas_limit); queue.set_gas_limit(gas_limit);
if let GasLimit::Auto = self.options.tx_queue_gas_limit { if let GasLimit::Auto = self.options.tx_queue_gas_limit {
// Set total tx queue gas limit to be 2x the block gas limit. // Set total tx queue gas limit to be 20x the block gas limit.
queue.set_total_gas_limit(gas_limit << 1); queue.set_total_gas_limit(gas_limit * 20.into());
} }
} }
@ -747,7 +753,7 @@ impl MinerService for Miner {
let mut transaction_queue = self.transaction_queue.lock(); let mut transaction_queue = self.transaction_queue.lock();
let import = self.add_transactions_to_queue( let import = self.add_transactions_to_queue(
chain, vec![transaction], TransactionOrigin::Local, &mut transaction_queue chain, vec![transaction], TransactionOrigin::Local, &mut transaction_queue
).pop().unwrap(); ).pop().expect("one result returned per added transaction; one added => one result; qed");
match import { match import {
Ok(ref res) => { Ok(ref res) => {
@ -869,7 +875,11 @@ impl MinerService for Miner {
gas_used: receipt.gas_used - prev_gas, gas_used: receipt.gas_used - prev_gas,
contract_address: match tx.action { contract_address: match tx.action {
Action::Call(_) => None, Action::Call(_) => None,
Action::Create => Some(contract_address(&tx.sender().unwrap(), &tx.nonce)), Action::Create => {
let sender = tx.sender()
.expect("transactions in pending block have already been checked for valid sender; qed");
Some(contract_address(&sender, &tx.nonce))
}
}, },
logs: receipt.logs.clone(), logs: receipt.logs.clone(),
} }
@ -1035,7 +1045,7 @@ impl MinerService for Miner {
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use super::super::MinerService; use super::super::{MinerService, PrioritizationStrategy};
use super::*; use super::*;
use util::*; use util::*;
use ethkey::{Generator, Random}; use ethkey::{Generator, Random};
@ -1089,6 +1099,7 @@ mod tests {
tx_gas_limit: !U256::zero(), tx_gas_limit: !U256::zero(),
tx_queue_size: 1024, tx_queue_size: 1024,
tx_queue_gas_limit: GasLimit::None, tx_queue_gas_limit: GasLimit::None,
tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice,
pending_set: PendingSet::AlwaysSealing, pending_set: PendingSet::AlwaysSealing,
work_queue_size: 5, work_queue_size: 5,
enable_resubmission: true, enable_resubmission: true,

View File

@ -47,7 +47,7 @@ mod transaction_queue;
mod work_notify; mod work_notify;
mod price_info; mod price_info;
pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionOrigin}; pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
pub use self::external::{ExternalMiner, ExternalMinerService}; pub use self::external::{ExternalMiner, ExternalMinerService};
pub use client::TransactionImportResult; pub use client::TransactionImportResult;

View File

@ -52,7 +52,8 @@ impl<F: Fn(PriceInfo) + Sync + Send + 'static> Handler<HttpStream> for SetPriceH
.and_then(|json| json.find_path(&["result", "ethusd"]) .and_then(|json| json.find_path(&["result", "ethusd"])
.and_then(|obj| match *obj { .and_then(|obj| match *obj {
Json::String(ref s) => Some((self.set_price)(PriceInfo { Json::String(ref s) => Some((self.set_price)(PriceInfo {
ethusd: FromStr::from_str(s).unwrap() ethusd: FromStr::from_str(s)
.expect("Etherscan API will always return properly formatted price; qed")
})), })),
_ => None, _ => None,
})); }));
@ -67,7 +68,11 @@ impl PriceInfo {
let client = try!(Client::new().map_err(|_| ())); let client = try!(Client::new().map_err(|_| ()));
thread::spawn(move || { thread::spawn(move || {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let _ = client.request(FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice").unwrap(), SetPriceHandler { let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice")
.expect("string known to be a valid URL; qed");
let _ = client.request(
url,
SetPriceHandler {
set_price: set_price, set_price: set_price,
channel: tx, channel: tx,
}).ok().and_then(|_| rx.recv().ok()); }).ok().and_then(|_| rx.recv().ok());

View File

@ -49,7 +49,7 @@
//! balance: U256::from(1_000_000), //! balance: U256::from(1_000_000),
//! }; //! };
//! //!
//! let mut txq = TransactionQueue::new(); //! let mut txq = TransactionQueue::default();
//! txq.add(st2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); //! txq.add(st2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
//! txq.add(st1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); //! txq.add(st1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
//! //!
@ -130,11 +130,20 @@ struct TransactionOrder {
/// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5) /// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5)
/// High nonce_height = Low priority (processed later) /// High nonce_height = Low priority (processed later)
nonce_height: U256, nonce_height: U256,
/// Gas specified in the transaction.
gas: U256,
/// Gas Price of the transaction. /// Gas Price of the transaction.
/// Low gas price = Low priority (processed later) /// Low gas price = Low priority (processed later)
gas_price: U256, gas_price: U256,
/// Gas usage priority factor. Usage depends on strategy.
/// Represents the linear increment in required gas price for heavy transactions.
///
/// High gas limit + Low gas price = Low priority
/// High gas limit + High gas price = High priority
gas_factor: U256,
/// Gas (limit) of the transaction. Usage depends on strategy.
/// Low gas limit = High priority (processed earlier)
gas: U256,
/// Transaction ordering strategy
strategy: PrioritizationStrategy,
/// Hash to identify associated transaction /// Hash to identify associated transaction
hash: H256, hash: H256,
/// Origin of the transaction /// Origin of the transaction
@ -145,11 +154,15 @@ struct TransactionOrder {
impl TransactionOrder { impl TransactionOrder {
fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self {
fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256, min_gas_price: U256, strategy: PrioritizationStrategy) -> Self {
let factor = (tx.transaction.gas >> 15) * min_gas_price;
TransactionOrder { TransactionOrder {
nonce_height: tx.nonce() - base_nonce, nonce_height: tx.nonce() - base_nonce,
gas: tx.transaction.gas.clone(),
gas_price: tx.transaction.gas_price, gas_price: tx.transaction.gas_price,
gas: tx.transaction.gas,
gas_factor: factor,
strategy: strategy,
hash: tx.hash(), hash: tx.hash(),
origin: tx.origin, origin: tx.origin,
penalties: 0, penalties: 0,
@ -197,11 +210,28 @@ impl Ord for TransactionOrder {
return self.origin.cmp(&b.origin); return self.origin.cmp(&b.origin);
} }
match self.strategy {
PrioritizationStrategy::GasAndGasPrice => {
if self.gas != b.gas {
return self.gas.cmp(&b.gas);
}
},
PrioritizationStrategy::GasFactorAndGasPrice => {
// avoiding overflows
// (gp1 - g1) > (gp2 - g2) <=>
// (gp1 + g2) > (gp2 + g1)
let f_a = self.gas_price + b.gas_factor;
let f_b = b.gas_price + self.gas_factor;
if f_a != f_b {
return f_b.cmp(&f_a);
}
},
PrioritizationStrategy::GasPriceOnly => {},
}
// Then compare gas_prices // Then compare gas_prices
let a_gas = self.gas_price; if self.gas_price != b.gas_price {
let b_gas = b.gas_price; return b.gas_price.cmp(&self.gas_price);
if a_gas != b_gas {
return b_gas.cmp(&a_gas);
} }
// Compare hashes // Compare hashes
@ -326,14 +356,14 @@ impl TransactionSet {
let to_drop : Vec<(Address, U256)> = { let to_drop : Vec<(Address, U256)> = {
self.by_priority self.by_priority
.iter() .iter()
.skip_while(|order| { .filter(|order| {
count = count + 1; count = count + 1;
let r = gas.overflowing_add(order.gas); let r = gas.overflowing_add(order.gas);
if r.1 { return false } if r.1 { return false }
gas = r.0; gas = r.0;
// Own and retracted transactions are allowed to go above the gas limit, bot not above the count limit. // Own and retracted transactions are allowed to go above all limits.
(gas <= self.gas_limit || order.origin == TransactionOrigin::Local || order.origin == TransactionOrigin::RetractedBlock) && order.origin != TransactionOrigin::Local && order.origin != TransactionOrigin::RetractedBlock &&
count <= self.limit (gas > self.gas_limit || count > self.limit)
}) })
.map(|order| by_hash.get(&order.hash) .map(|order| by_hash.get(&order.hash)
.expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`.")) .expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`."))
@ -345,6 +375,7 @@ impl TransactionSet {
.fold(HashMap::new(), |mut removed, (sender, nonce)| { .fold(HashMap::new(), |mut removed, (sender, nonce)| {
let order = self.drop(&sender, &nonce) let order = self.drop(&sender, &nonce)
.expect("Transaction has just been found in `by_priority`; so it is in `by_address` also."); .expect("Transaction has just been found in `by_priority`; so it is in `by_address` also.");
trace!(target: "txqueue", "Dropped out of limit transaction: {:?}", order.hash);
by_hash.remove(&order.hash) by_hash.remove(&order.hash)
.expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed"); .expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed");
@ -414,8 +445,32 @@ pub struct AccountDetails {
/// Transactions with `gas > (gas_limit + gas_limit * Factor(in percents))` are not imported to the queue. /// Transactions with `gas > (gas_limit + gas_limit * Factor(in percents))` are not imported to the queue.
const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) % const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) %
/// Describes the strategy used to prioritize transactions in the queue.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PrioritizationStrategy {
/// Use only gas price. Disregards the actual computation cost of the transaction.
/// i.e. Higher gas price = Higher priority
GasPriceOnly,
/// Use gas limit and then gas price.
/// i.e. Higher gas limit = Lower priority
GasAndGasPrice,
/// Calculate and use priority based on gas and gas price.
/// PRIORITY = GAS_PRICE - GAS/2^15 * MIN_GAS_PRICE
///
/// Rationale:
/// Heavy transactions are paying linear cost (GAS * GAS_PRICE)
/// while the computation might be more expensive.
///
/// i.e.
/// 1M gas tx with `gas_price=30*min` has the same priority
/// as 32k gas tx with `gas_price=min`
GasFactorAndGasPrice,
}
/// `TransactionQueue` implementation /// `TransactionQueue` implementation
pub struct TransactionQueue { pub struct TransactionQueue {
/// Prioritization strategy for this queue
strategy: PrioritizationStrategy,
/// Gas Price threshold for transactions that can be imported to this queue (defaults to 0) /// Gas Price threshold for transactions that can be imported to this queue (defaults to 0)
minimal_gas_price: U256, minimal_gas_price: U256,
/// The maximum amount of gas any individual transaction may use. /// The maximum amount of gas any individual transaction may use.
@ -434,18 +489,18 @@ pub struct TransactionQueue {
impl Default for TransactionQueue { impl Default for TransactionQueue {
fn default() -> Self { fn default() -> Self {
TransactionQueue::new() TransactionQueue::new(PrioritizationStrategy::GasPriceOnly)
} }
} }
impl TransactionQueue { impl TransactionQueue {
/// Creates new instance of this Queue /// Creates new instance of this Queue
pub fn new() -> Self { pub fn new(strategy: PrioritizationStrategy) -> Self {
Self::with_limits(1024, !U256::zero(), !U256::zero()) Self::with_limits(strategy, 1024, !U256::zero(), !U256::zero())
} }
/// Create new instance of this Queue with specified limits /// Create new instance of this Queue with specified limits
pub fn with_limits(limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self { pub fn with_limits(strategy: PrioritizationStrategy, limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self {
let current = TransactionSet { let current = TransactionSet {
by_priority: BTreeSet::new(), by_priority: BTreeSet::new(),
by_address: Table::new(), by_address: Table::new(),
@ -463,6 +518,7 @@ impl TransactionQueue {
}; };
TransactionQueue { TransactionQueue {
strategy: strategy,
minimal_gas_price: U256::zero(), minimal_gas_price: U256::zero(),
tx_gas_limit: tx_gas_limit, tx_gas_limit: tx_gas_limit,
gas_limit: !U256::zero(), gas_limit: !U256::zero(),
@ -644,7 +700,7 @@ impl TransactionQueue {
None => vec![], None => vec![],
}; };
for k in nonces_from_sender { for k in nonces_from_sender {
let order = self.current.drop(&sender, &k).unwrap(); let order = self.current.drop(&sender, &k).expect("transaction known to be in self.current; qed");
self.current.insert(sender, k, order.penalize()); self.current.insert(sender, k, order.penalize());
} }
// Same thing for future // Same thing for future
@ -653,7 +709,7 @@ impl TransactionQueue {
None => vec![], None => vec![],
}; };
for k in nonces_from_sender { for k in nonces_from_sender {
let order = self.future.drop(&sender, &k).unwrap(); let order = self.future.drop(&sender, &k).expect("transaction known to be in self.future; qed");
self.future.insert(sender, k, order.penalize()); self.future.insert(sender, k, order.penalize());
} }
} }
@ -678,6 +734,8 @@ impl TransactionQueue {
let nonce = transaction.nonce(); let nonce = transaction.nonce();
let current_nonce = fetch_account(&sender).nonce; let current_nonce = fetch_account(&sender).nonce;
trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash());
// Remove from future // Remove from future
let order = self.future.drop(&sender, &nonce); let order = self.future.drop(&sender, &nonce);
if order.is_some() { if order.is_some() {
@ -841,6 +899,7 @@ impl TransactionQueue {
return Err(TransactionError::AlreadyImported); return Err(TransactionError::AlreadyImported);
} }
let min_gas_price = (self.minimal_gas_price, self.strategy);
let address = tx.sender(); let address = tx.sender();
let nonce = tx.nonce(); let nonce = tx.nonce();
let hash = tx.hash(); let hash = tx.hash();
@ -878,7 +937,7 @@ impl TransactionQueue {
if nonce > next_nonce { if nonce > next_nonce {
// We have a gap - put to future. // We have a gap - put to future.
// Insert transaction (or replace old one with lower gas price) // Insert transaction (or replace old one with lower gas price)
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.future, &mut self.by_hash))); try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash)));
// Enforce limit in Future // Enforce limit in Future
let removed = self.future.enforce_limit(&mut self.by_hash); let removed = self.future.enforce_limit(&mut self.by_hash);
// Return an error if this transaction was not imported because of limit. // Return an error if this transaction was not imported because of limit.
@ -894,7 +953,7 @@ impl TransactionQueue {
self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce);
// Replace transaction if any // Replace transaction if any
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.current, &mut self.by_hash))); try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash)));
// Keep track of highest nonce stored in current // Keep track of highest nonce stored in current
let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n)); let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n));
self.last_nonces.insert(address, new_max); self.last_nonces.insert(address, new_max);
@ -931,8 +990,8 @@ impl TransactionQueue {
/// ///
/// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher /// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher
/// gas_price) /// gas_price)
fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, set: &mut TransactionSet, by_hash: &mut HashMap<H256, VerifiedTransaction>) -> bool { fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, min_gas_price: (U256, PrioritizationStrategy), set: &mut TransactionSet, by_hash: &mut HashMap<H256, VerifiedTransaction>) -> bool {
let order = TransactionOrder::for_transaction(&tx, base_nonce); let order = TransactionOrder::for_transaction(&tx, base_nonce, min_gas_price.0, min_gas_price.1);
let hash = tx.hash(); let hash = tx.hash();
let address = tx.sender(); let address = tx.sender();
let nonce = tx.nonce(); let nonce = tx.nonce();
@ -953,12 +1012,14 @@ impl TransactionQueue {
let old_fee = old.gas_price; let old_fee = old.gas_price;
let new_fee = order.gas_price; let new_fee = order.gas_price;
if old_fee.cmp(&new_fee) == Ordering::Greater { if old_fee.cmp(&new_fee) == Ordering::Greater {
trace!(target: "txqueue", "Didn't insert transaction because gas price was too low: {:?} ({:?} stays in the queue)", order.hash, old.hash);
// Put back old transaction since it has greater priority (higher gas_price) // Put back old transaction since it has greater priority (higher gas_price)
set.insert(address, nonce, old); set.insert(address, nonce, old);
// and remove new one // and remove new one
by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`.");
false false
} else { } else {
trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash);
// Make sure we remove old transaction entirely // Make sure we remove old transaction entirely
by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`.");
true true
@ -1010,12 +1071,12 @@ mod test {
fn default_gas_val() -> U256 { 100_000.into() } fn default_gas_val() -> U256 { 100_000.into() }
fn default_gas_price() -> U256 { 1.into() } fn default_gas_price() -> U256 { 1.into() }
fn new_unsigned_tx(nonce: U256, gas_price: U256) -> Transaction { fn new_unsigned_tx(nonce: U256, gas: U256, gas_price: U256) -> Transaction {
Transaction { Transaction {
action: Action::Create, action: Action::Create,
value: U256::from(100), value: U256::from(100),
data: "3331600055".from_hex().unwrap(), data: "3331600055".from_hex().unwrap(),
gas: default_gas_val(), gas: gas,
gas_price: gas_price, gas_price: gas_price,
nonce: nonce nonce: nonce
} }
@ -1023,7 +1084,12 @@ mod test {
fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction { fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction {
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
new_unsigned_tx(nonce, gas_price).sign(keypair.secret()) new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret())
}
fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction {
let keypair = Random.generate().unwrap();
new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret())
} }
fn new_tx_default() -> SignedTransaction { fn new_tx_default() -> SignedTransaction {
@ -1038,8 +1104,8 @@ mod test {
} }
fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) {
let tx1 = new_unsigned_tx(nonce, gas_price); let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price);
let tx2 = new_unsigned_tx(nonce + nonce_increment, gas_price + gas_price_increment); let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment);
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
let secret = &keypair.secret(); let secret = &keypair.secret();
@ -1049,8 +1115,8 @@ mod test {
/// Returns two consecutive transactions, both with increased gas price /// Returns two consecutive transactions, both with increased gas price
fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) {
let gas = default_gas_price() + gas_price_increment; let gas = default_gas_price() + gas_price_increment;
let tx1 = new_unsigned_tx(default_nonce(), gas); let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas);
let tx2 = new_unsigned_tx(default_nonce() + 1.into(), gas); let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas);
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
let secret = &keypair.secret(); let secret = &keypair.secret();
@ -1077,17 +1143,21 @@ mod test {
assert_eq!(TransactionOrigin::External.cmp(&TransactionOrigin::RetractedBlock), Ordering::Greater); assert_eq!(TransactionOrigin::External.cmp(&TransactionOrigin::RetractedBlock), Ordering::Greater);
} }
fn transaction_order(tx: &VerifiedTransaction, nonce: U256) -> TransactionOrder {
TransactionOrder::for_transaction(tx, nonce, 0.into(), PrioritizationStrategy::GasPriceOnly)
}
#[test] #[test]
fn should_return_correct_nonces_when_dropped_because_of_limit() { fn should_return_correct_nonces_when_dropped_because_of_limit() {
// given // given
let mut txq = TransactionQueue::with_limits(2, !U256::zero(), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 2, !U256::zero(), !U256::zero());
let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into());
let sender = tx1.sender().unwrap(); let sender = tx1.sender().unwrap();
let nonce = tx1.nonce; let nonce = tx1.nonce;
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
assert_eq!(txq.status().pending, 2); assert_eq!(txq.status().pending, 2);
assert_eq!(txq.last_nonce(&sender), Some(nonce + U256::one())); assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into()));
// when // when
let tx = new_tx(123.into(), 1.into()); let tx = new_tx(123.into(), 1.into());
@ -1133,9 +1203,9 @@ mod test {
x x
}; };
// Insert both transactions // Insert both transactions
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero()); let order1 = transaction_order(&tx1, U256::zero());
set.insert(tx1.sender(), tx1.nonce(), order1.clone()); set.insert(tx1.sender(), tx1.nonce(), order1.clone());
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero()); let order2 = transaction_order(&tx2, U256::zero());
set.insert(tx2.sender(), tx2.nonce(), order2.clone()); set.insert(tx2.sender(), tx2.nonce(), order2.clone());
assert_eq!(set.by_priority.len(), 2); assert_eq!(set.by_priority.len(), 2);
assert_eq!(set.by_address.len(), 2); assert_eq!(set.by_address.len(), 2);
@ -1176,7 +1246,7 @@ mod test {
x x
}; };
// Insert both transactions // Insert both transactions
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero()); let order1 = transaction_order(&tx1, U256::zero());
set.insert(tx1.sender(), tx1.nonce(), order1.clone()); set.insert(tx1.sender(), tx1.nonce(), order1.clone());
assert_eq!(set.by_priority.len(), 1); assert_eq!(set.by_priority.len(), 1);
assert_eq!(set.by_address.len(), 1); assert_eq!(set.by_address.len(), 1);
@ -1184,7 +1254,7 @@ mod test {
assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into()); assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into());
assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1); assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1);
// Two different orders (imagine nonce changed in the meantime) // Two different orders (imagine nonce changed in the meantime)
let order2 = TransactionOrder::for_transaction(&tx2, U256::one()); let order2 = transaction_order(&tx2, U256::one());
set.insert(tx2.sender(), tx2.nonce(), order2.clone()); set.insert(tx2.sender(), tx2.nonce(), order2.clone());
assert_eq!(set.by_priority.len(), 1); assert_eq!(set.by_priority.len(), 1);
assert_eq!(set.by_address.len(), 1); assert_eq!(set.by_address.len(), 1);
@ -1213,10 +1283,10 @@ mod test {
}; };
let tx = new_tx_default(); let tx = new_tx_default();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap();
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero()); let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none()); assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none());
let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External).unwrap(); let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External).unwrap();
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero()); let order2 = TransactionOrder::for_transaction(&tx2, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some()); assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some());
} }
@ -1233,7 +1303,7 @@ mod test {
assert_eq!(set.gas_price_entry_limit(), 0.into()); assert_eq!(set.gas_price_entry_limit(), 0.into());
let tx = new_tx_default(); let tx = new_tx_default();
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap();
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero()); let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none()); assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none());
assert_eq!(set.gas_price_entry_limit(), 2.into()); assert_eq!(set.gas_price_entry_limit(), 2.into());
} }
@ -1241,7 +1311,7 @@ mod test {
#[test] #[test]
fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { fn should_handle_same_transaction_imported_twice_with_different_state_nonces() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_similar_tx_pair(); let (tx, tx2) = new_similar_tx_pair();
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
!U256::zero() }; !U256::zero() };
@ -1266,7 +1336,7 @@ mod test {
#[test] #[test]
fn should_move_all_transactions_from_future() { fn should_move_all_transactions_from_future() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 1.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 1.into());
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
!U256::zero() }; !U256::zero() };
@ -1292,7 +1362,7 @@ mod test {
#[test] #[test]
fn should_import_tx() { fn should_import_tx() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
// when // when
@ -1304,10 +1374,77 @@ mod test {
assert_eq!(stats.pending, 1); assert_eq!(stats.pending, 1);
} }
#[test]
fn should_order_by_gas() {
// given
let mut txq = TransactionQueue::new(PrioritizationStrategy::GasAndGasPrice);
let tx1 = new_tx_with_gas(50000.into(), 40.into());
let tx2 = new_tx_with_gas(40000.into(), 30.into());
let tx3 = new_tx_with_gas(30000.into(), 10.into());
let tx4 = new_tx_with_gas(50000.into(), 20.into());
txq.set_minimal_gas_price(15.into());
// when
let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External);
let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External);
let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External);
let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External);
// then
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
assert_eq!(res2.unwrap(), TransactionImportResult::Current);
assert_eq!(unwrap_tx_err(res3), TransactionError::InsufficientGasPrice {
minimal: U256::from(15),
got: U256::from(10),
});
assert_eq!(res4.unwrap(), TransactionImportResult::Current);
let stats = txq.status();
assert_eq!(stats.pending, 3);
assert_eq!(txq.top_transactions()[0].gas, 40000.into());
assert_eq!(txq.top_transactions()[1].gas, 50000.into());
assert_eq!(txq.top_transactions()[2].gas, 50000.into());
assert_eq!(txq.top_transactions()[1].gas_price, 40.into());
assert_eq!(txq.top_transactions()[2].gas_price, 20.into());
}
#[test]
fn should_order_by_gas_factor() {
// given
let mut txq = TransactionQueue::new(PrioritizationStrategy::GasFactorAndGasPrice);
let tx1 = new_tx_with_gas(150_000.into(), 40.into());
let tx2 = new_tx_with_gas(40_000.into(), 16.into());
let tx3 = new_tx_with_gas(30_000.into(), 15.into());
let tx4 = new_tx_with_gas(150_000.into(), 62.into());
txq.set_minimal_gas_price(15.into());
// when
let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External);
let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External);
let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External);
let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External);
// then
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
assert_eq!(res2.unwrap(), TransactionImportResult::Current);
assert_eq!(res3.unwrap(), TransactionImportResult::Current);
assert_eq!(res4.unwrap(), TransactionImportResult::Current);
let stats = txq.status();
assert_eq!(stats.pending, 4);
assert_eq!(txq.top_transactions()[0].gas, 30_000.into());
assert_eq!(txq.top_transactions()[1].gas, 150_000.into());
assert_eq!(txq.top_transactions()[2].gas, 40_000.into());
assert_eq!(txq.top_transactions()[3].gas, 150_000.into());
assert_eq!(txq.top_transactions()[0].gas_price, 15.into());
assert_eq!(txq.top_transactions()[1].gas_price, 62.into());
assert_eq!(txq.top_transactions()[2].gas_price, 16.into());
assert_eq!(txq.top_transactions()[3].gas_price, 40.into());
}
#[test] #[test]
fn gas_limit_should_never_overflow() { fn gas_limit_should_never_overflow() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
txq.set_gas_limit(U256::zero()); txq.set_gas_limit(U256::zero());
assert_eq!(txq.gas_limit, U256::zero()); assert_eq!(txq.gas_limit, U256::zero());
@ -1321,7 +1458,7 @@ mod test {
#[test] #[test]
fn should_not_import_transaction_above_gas_limit() { fn should_not_import_transaction_above_gas_limit() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
let gas = tx.gas; let gas = tx.gas;
let limit = gas / U256::from(2); let limit = gas / U256::from(2);
@ -1344,7 +1481,7 @@ mod test {
#[test] #[test]
fn should_drop_transactions_from_senders_without_balance() { fn should_drop_transactions_from_senders_without_balance() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
let account = |a: &Address| AccountDetails { let account = |a: &Address| AccountDetails {
nonce: default_account_details(a).nonce, nonce: default_account_details(a).nonce,
@ -1367,7 +1504,7 @@ mod test {
#[test] #[test]
fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { fn should_not_import_transaction_below_min_gas_price_threshold_if_external() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
txq.set_minimal_gas_price(tx.gas_price + U256::one()); txq.set_minimal_gas_price(tx.gas_price + U256::one());
@ -1387,7 +1524,7 @@ mod test {
#[test] #[test]
fn should_import_transaction_below_min_gas_price_threshold_if_local() { fn should_import_transaction_below_min_gas_price_threshold_if_local() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
txq.set_minimal_gas_price(tx.gas_price + U256::one()); txq.set_minimal_gas_price(tx.gas_price + U256::one());
@ -1406,8 +1543,8 @@ mod test {
use rlp::{self, RlpStream, Stream}; use rlp::{self, RlpStream, Stream};
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_unsigned_tx(123.into(), 1.into()); let tx = new_unsigned_tx(123.into(), 100.into(), 1.into());
let stx = { let stx = {
let mut s = RlpStream::new_list(9); let mut s = RlpStream::new_list(9);
s.append(&tx.nonce); s.append(&tx.nonce);
@ -1431,7 +1568,7 @@ mod test {
#[test] #[test]
fn should_import_txs_from_same_sender() { fn should_import_txs_from_same_sender() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
@ -1449,7 +1586,7 @@ mod test {
#[test] #[test]
fn should_prioritize_local_transactions_within_same_nonce_height() { fn should_prioritize_local_transactions_within_same_nonce_height() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
// the second one has same nonce but higher `gas_price` // the second one has same nonce but higher `gas_price`
let (_, tx2) = new_similar_tx_pair(); let (_, tx2) = new_similar_tx_pair();
@ -1470,7 +1607,7 @@ mod test {
#[test] #[test]
fn should_prioritize_reimported_transactions_within_same_nonce_height() { fn should_prioritize_reimported_transactions_within_same_nonce_height() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
// the second one has same nonce but higher `gas_price` // the second one has same nonce but higher `gas_price`
let (_, tx2) = new_similar_tx_pair(); let (_, tx2) = new_similar_tx_pair();
@ -1491,7 +1628,7 @@ mod test {
#[test] #[test]
fn should_not_prioritize_local_transactions_with_different_nonce_height() { fn should_not_prioritize_local_transactions_with_different_nonce_height() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when // when
@ -1509,7 +1646,7 @@ mod test {
fn should_penalize_transactions_from_sender_in_future() { fn should_penalize_transactions_from_sender_in_future() {
// given // given
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() }; let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() };
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
// txa, txb - slightly bigger gas price to have consistent ordering // txa, txb - slightly bigger gas price to have consistent ordering
let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
@ -1538,7 +1675,7 @@ mod test {
#[test] #[test]
fn should_penalize_transactions_from_sender() { fn should_penalize_transactions_from_sender() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
// txa, txb - slightly bigger gas price to have consistent ordering // txa, txb - slightly bigger gas price to have consistent ordering
let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
@ -1571,7 +1708,7 @@ mod test {
#[test] #[test]
fn should_return_pending_hashes() { fn should_return_pending_hashes() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
@ -1589,7 +1726,7 @@ mod test {
#[test] #[test]
fn should_put_transaction_to_futures_if_gap_detected() { fn should_put_transaction_to_futures_if_gap_detected() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(2.into(), 0.into());
@ -1615,7 +1752,7 @@ mod test {
!U256::zero() }; !U256::zero() };
let next2_nonce = default_nonce() + U256::from(3); let next2_nonce = default_nonce() + U256::from(3);
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External).unwrap();
@ -1634,12 +1771,12 @@ mod test {
#[test] #[test]
fn should_move_transactions_if_gap_filled() { fn should_move_transactions_if_gap_filled() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let kp = Random.generate().unwrap(); let kp = Random.generate().unwrap();
let secret = kp.secret(); let secret = kp.secret();
let tx = new_unsigned_tx(123.into(), 1.into()).sign(secret); let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret);
let tx1 = new_unsigned_tx(124.into(), 1.into()).sign(secret); let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret);
let tx2 = new_unsigned_tx(125.into(), 1.into()).sign(secret); let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret);
txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap();
assert_eq!(txq.status().pending, 1); assert_eq!(txq.status().pending, 1);
@ -1661,7 +1798,7 @@ mod test {
#[test] #[test]
fn should_remove_transaction() { fn should_remove_transaction() {
// given // given
let mut txq2 = TransactionQueue::new(); let mut txq2 = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(3.into(), 0.into());
txq2.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq2.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq2.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq2.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
@ -1682,7 +1819,7 @@ mod test {
#[test] #[test]
fn should_move_transactions_to_future_if_gap_introduced() { fn should_move_transactions_to_future_if_gap_introduced() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let tx3 = new_tx_default(); let tx3 = new_tx_default();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
@ -1703,7 +1840,7 @@ mod test {
#[test] #[test]
fn should_clear_queue() { fn should_clear_queue() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// add // add
@ -1723,7 +1860,7 @@ mod test {
#[test] #[test]
fn should_drop_old_transactions_when_hitting_the_limit() { fn should_drop_old_transactions_when_hitting_the_limit() {
// given // given
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let sender = tx.sender().unwrap(); let sender = tx.sender().unwrap();
let nonce = tx.nonce; let nonce = tx.nonce;
@ -1744,7 +1881,7 @@ mod test {
#[test] #[test]
fn should_limit_future_transactions() { fn should_limit_future_transactions() {
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
txq.current.set_limit(10); txq.current.set_limit(10);
let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into());
let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into());
@ -1763,23 +1900,28 @@ mod test {
#[test] #[test]
fn should_limit_by_gas() { fn should_limit_by_gas() {
let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero());
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).ok(); txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).ok(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).ok(); txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).ok(); // limited by gas
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap_err();
assert_eq!(txq.status().pending, 2); assert_eq!(txq.status().pending, 2);
} }
#[test] #[test]
fn should_keep_own_transactions_above_gas_limit() { fn should_keep_own_transactions_above_gas_limit() {
let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero());
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2));
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
txq.add(tx5.clone(), &default_account_details, TransactionOrigin::External).unwrap();
// Not accepted because of limit
txq.add(tx6.clone(), &default_account_details, TransactionOrigin::External).unwrap_err();
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
assert_eq!(txq.status().pending, 4); assert_eq!(txq.status().pending, 4);
@ -1787,7 +1929,7 @@ mod test {
#[test] #[test]
fn should_drop_transactions_with_old_nonces() { fn should_drop_transactions_with_old_nonces() {
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
let last_nonce = tx.nonce + U256::one(); let last_nonce = tx.nonce + U256::one();
let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() }; let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() };
@ -1807,7 +1949,7 @@ mod test {
// given // given
let nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce + U256::one(), let nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce + U256::one(),
balance: !U256::zero() }; balance: !U256::zero() };
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
assert_eq!(txq.status().future, 1); assert_eq!(txq.status().future, 1);
@ -1826,7 +1968,7 @@ mod test {
#[test] #[test]
fn should_accept_same_transaction_twice_if_removed() { fn should_accept_same_transaction_twice_if_removed() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
@ -1847,7 +1989,7 @@ mod test {
#[test] #[test]
fn should_not_move_to_future_if_state_nonce_is_higher() { fn should_not_move_to_future_if_state_nonce_is_higher() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
let tx3 = new_tx_default(); let tx3 = new_tx_default();
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
@ -1870,9 +2012,9 @@ mod test {
fn should_replace_same_transaction_when_has_higher_fee() { fn should_replace_same_transaction_when_has_higher_fee() {
init_log(); init_log();
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
let tx = new_unsigned_tx(123.into(), 1.into()).sign(keypair.secret()); let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret());
let tx2 = { let tx2 = {
let mut tx2 = (*tx).clone(); let mut tx2 = (*tx).clone();
tx2.gas_price = U256::from(200); tx2.gas_price = U256::from(200);
@ -1893,9 +2035,9 @@ mod test {
#[test] #[test]
fn should_replace_same_transaction_when_importing_to_futures() { fn should_replace_same_transaction_when_importing_to_futures() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
let tx0 = new_unsigned_tx(123.into(), 1.into()).sign(keypair.secret()); let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret());
let tx1 = { let tx1 = {
let mut tx1 = (*tx0).clone(); let mut tx1 = (*tx0).clone();
tx1.nonce = U256::from(124); tx1.nonce = U256::from(124);
@ -1927,7 +2069,7 @@ mod test {
!U256::zero() }; !U256::zero() };
let next_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce + U256::one(), balance: let next_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce + U256::one(), balance:
!U256::zero() }; !U256::zero() };
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
txq.add(tx1.clone(), &previous_nonce, TransactionOrigin::External).unwrap(); txq.add(tx1.clone(), &previous_nonce, TransactionOrigin::External).unwrap();
txq.add(tx2, &previous_nonce, TransactionOrigin::External).unwrap(); txq.add(tx2, &previous_nonce, TransactionOrigin::External).unwrap();
@ -1945,7 +2087,7 @@ mod test {
#[test] #[test]
fn should_return_none_when_transaction_from_given_address_does_not_exist() { fn should_return_none_when_transaction_from_given_address_does_not_exist() {
// given // given
let txq = TransactionQueue::new(); let txq = TransactionQueue::default();
// then // then
assert_eq!(txq.last_nonce(&Address::default()), None); assert_eq!(txq.last_nonce(&Address::default()), None);
@ -1954,7 +2096,7 @@ mod test {
#[test] #[test]
fn should_return_correct_nonce_when_transactions_from_given_address_exist() { fn should_return_correct_nonce_when_transactions_from_given_address_exist() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let tx = new_tx_default(); let tx = new_tx_default();
let from = tx.sender().unwrap(); let from = tx.sender().unwrap();
let nonce = tx.nonce; let nonce = tx.nonce;
@ -1970,7 +2112,7 @@ mod test {
#[test] #[test]
fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() }; let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() };
@ -1988,7 +2130,7 @@ mod test {
#[test] #[test]
fn should_return_valid_last_nonce_after_remove_all() { fn should_return_valid_last_nonce_after_remove_all() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into());
let sender = tx1.sender().unwrap(); let sender = tx1.sender().unwrap();
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
@ -2012,7 +2154,7 @@ mod test {
#[test] #[test]
fn should_return_true_if_there_is_local_transaction_pending() { fn should_return_true_if_there_is_local_transaction_pending() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
assert_eq!(txq.has_local_pending_transactions(), false); assert_eq!(txq.has_local_pending_transactions(), false);
@ -2028,7 +2170,7 @@ mod test {
#[test] #[test]
fn should_keep_right_order_in_future() { fn should_keep_right_order_in_future() {
// given // given
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance: let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance:
default_account_details(a).balance }; default_account_details(a).balance };
@ -2045,15 +2187,16 @@ mod test {
#[test] #[test]
fn should_return_correct_last_nonce() { fn should_return_correct_last_nonce() {
// given // given
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::default();
let (tx1, tx2, tx2_2, tx3) = { let (tx1, tx2, tx2_2, tx3) = {
let keypair = Random.generate().unwrap(); let keypair = Random.generate().unwrap();
let secret = &keypair.secret(); let secret = &keypair.secret();
let nonce = 123.into(); let nonce = 123.into();
let tx = new_unsigned_tx(nonce, 1.into()); let gas = default_gas_val();
let tx2 = new_unsigned_tx(nonce + 1.into(), 1.into()); let tx = new_unsigned_tx(nonce, gas, 1.into());
let tx2_2 = new_unsigned_tx(nonce + 1.into(), 5.into()); let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into());
let tx3 = new_unsigned_tx(nonce + 2.into(), 1.into()); let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into());
let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into());
(tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret)) (tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret))

View File

@ -89,7 +89,7 @@ impl ClientService {
db_config.set_cache(::db::COL_STATE, size); db_config.set_cache(::db::COL_STATE, size);
} }
db_config.compaction = config.db_compaction.compaction_profile(); db_config.compaction = config.db_compaction.compaction_profile(&client_path);
db_config.wal = config.db_wal; db_config.wal = config.db_wal;
let pruning = config.pruning; let pruning = config.pruning;

View File

@ -19,12 +19,20 @@
use account_db::{AccountDB, AccountDBMut}; use account_db::{AccountDB, AccountDBMut};
use snapshot::Error; use snapshot::Error;
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY}; use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP};
use util::trie::{TrieDB, Trie}; use util::trie::{TrieDB, Trie};
use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View}; use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
// An empty account -- these are replaced with RLP null data for a space optimization.
const ACC_EMPTY: Account = Account {
nonce: U256([0, 0, 0, 0]),
balance: U256([0, 0, 0, 0]),
storage_root: SHA3_NULL_RLP,
code_hash: SHA3_EMPTY,
};
// whether an encoded account has code and how it is referred to. // whether an encoded account has code and how it is referred to.
#[repr(u8)] #[repr(u8)]
enum CodeState { enum CodeState {
@ -88,6 +96,10 @@ impl Account {
// walk the account's storage trie, returning an RLP item containing the // walk the account's storage trie, returning an RLP item containing the
// account properties and the storage. // account properties and the storage.
pub fn to_fat_rlp(&self, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> { pub fn to_fat_rlp(&self, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
if self == &ACC_EMPTY {
return Ok(::rlp::NULL_RLP.to_vec());
}
let db = try!(TrieDB::new(acct_db, &self.storage_root)); let db = try!(TrieDB::new(acct_db, &self.storage_root));
let mut pairs = Vec::new(); let mut pairs = Vec::new();
@ -142,6 +154,11 @@ impl Account {
) -> Result<(Self, Option<Bytes>), Error> { ) -> Result<(Self, Option<Bytes>), Error> {
use util::{TrieDBMut, TrieMut}; use util::{TrieDBMut, TrieMut};
// check for special case of empty account.
if rlp.is_empty() {
return Ok((ACC_EMPTY, None));
}
let nonce = try!(rlp.val_at(0)); let nonce = try!(rlp.val_at(0));
let balance = try!(rlp.val_at(1)); let balance = try!(rlp.val_at(1));
let code_state: CodeState = { let code_state: CodeState = {
@ -214,7 +231,7 @@ mod tests {
use std::collections::{HashSet, HashMap}; use std::collections::{HashSet, HashMap};
use super::Account; use super::{ACC_EMPTY, Account};
#[test] #[test]
fn encoding_basic() { fn encoding_basic() {
@ -310,4 +327,14 @@ mod tests {
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec())); assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
assert_eq!(acc, account1); assert_eq!(acc, account1);
} }
#[test]
fn encoding_empty_acc() {
let mut db = get_temp_state_db();
let mut used_code = HashSet::new();
let code_map = HashMap::new();
assert_eq!(ACC_EMPTY.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP), &code_map).unwrap(), (ACC_EMPTY, None));
}
} }

View File

@ -29,7 +29,7 @@ use engines::Engine;
use ids::BlockID; use ids::BlockID;
use views::BlockView; use views::BlockView;
use util::{Bytes, Hashable, HashDB, snappy}; use util::{Bytes, Hashable, HashDB, snappy, U256, Uint};
use util::memorydb::MemoryDB; use util::memorydb::MemoryDB;
use util::Mutex; use util::Mutex;
use util::hash::{FixedHash, H256}; use util::hash::{FixedHash, H256};
@ -44,6 +44,7 @@ use self::block::AbridgedBlock;
use self::io::SnapshotWriter; use self::io::SnapshotWriter;
use super::state_db::StateDB; use super::state_db::StateDB;
use super::state::Account as StateAccount;
use crossbeam::{scope, ScopedJoinHandle}; use crossbeam::{scope, ScopedJoinHandle};
use rand::{Rng, OsRng}; use rand::{Rng, OsRng};
@ -409,6 +410,7 @@ impl StateRebuilder {
/// Feed an uncompressed state chunk into the rebuilder. /// Feed an uncompressed state chunk into the rebuilder.
pub fn feed(&mut self, chunk: &[u8]) -> Result<(), ::error::Error> { pub fn feed(&mut self, chunk: &[u8]) -> Result<(), ::error::Error> {
let rlp = UntrustedRlp::new(chunk); let rlp = UntrustedRlp::new(chunk);
let empty_rlp = StateAccount::new_basic(U256::zero(), U256::zero()).rlp();
let account_fat_rlps: Vec<_> = rlp.iter().map(|r| r.as_raw()).collect(); let account_fat_rlps: Vec<_> = rlp.iter().map(|r| r.as_raw()).collect();
let mut pairs = Vec::with_capacity(rlp.item_count()); let mut pairs = Vec::with_capacity(rlp.item_count());
@ -476,7 +478,9 @@ impl StateRebuilder {
}; };
for (hash, thin_rlp) in pairs { for (hash, thin_rlp) in pairs {
if &thin_rlp[..] != &empty_rlp[..] {
bloom.set(&*hash); bloom.set(&*hash);
}
try!(account_trie.insert(&hash, &thin_rlp)); try!(account_trie.insert(&hash, &thin_rlp));
} }
} }
@ -564,6 +568,7 @@ const POW_VERIFY_RATE: f32 = 0.02;
/// After all chunks have been submitted, we "glue" the chunks together. /// After all chunks have been submitted, we "glue" the chunks together.
pub struct BlockRebuilder { pub struct BlockRebuilder {
chain: BlockChain, chain: BlockChain,
db: Arc<Database>,
rng: OsRng, rng: OsRng,
disconnected: Vec<(u64, H256)>, disconnected: Vec<(u64, H256)>,
best_number: u64, best_number: u64,
@ -571,9 +576,10 @@ pub struct BlockRebuilder {
impl BlockRebuilder { impl BlockRebuilder {
/// Create a new BlockRebuilder. /// Create a new BlockRebuilder.
pub fn new(chain: BlockChain, best_number: u64) -> Result<Self, ::error::Error> { pub fn new(chain: BlockChain, db: Arc<Database>, best_number: u64) -> Result<Self, ::error::Error> {
Ok(BlockRebuilder { Ok(BlockRebuilder {
chain: chain, chain: chain,
db: db,
rng: try!(OsRng::new()), rng: try!(OsRng::new()),
disconnected: Vec::new(), disconnected: Vec::new(),
best_number: best_number, best_number: best_number,
@ -616,15 +622,17 @@ impl BlockRebuilder {
} }
let is_best = cur_number == self.best_number; let is_best = cur_number == self.best_number;
let mut batch = self.db.transaction();
// special-case the first block in each chunk. // special-case the first block in each chunk.
if idx == 3 { if idx == 3 {
if self.chain.insert_snapshot_block(&block_bytes, receipts, Some(parent_total_difficulty), is_best) { if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
self.disconnected.push((cur_number, block.header.hash())); self.disconnected.push((cur_number, block.header.hash()));
} }
} else { } else {
self.chain.insert_snapshot_block(&block_bytes, receipts, None, is_best); self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
} }
self.db.write(batch).expect("Error writing to the DB");
self.chain.commit(); self.chain.commit();
parent_hash = BlockView::new(&block_bytes).hash(); parent_hash = BlockView::new(&block_bytes).hash();

View File

@ -98,7 +98,7 @@ impl Restoration {
.map_err(UtilError::SimpleString))); .map_err(UtilError::SimpleString)));
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
let blocks = try!(BlockRebuilder::new(chain, manifest.block_number)); let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), manifest.block_number));
let root = manifest.state_root.clone(); let root = manifest.state_root.clone();
Ok(Restoration { Ok(Restoration {
@ -346,7 +346,7 @@ impl Service {
self.taking_snapshot.store(false, Ordering::SeqCst); self.taking_snapshot.store(false, Ordering::SeqCst);
if let Err(e) = res { if let Err(e) = res {
if client.chain_info().best_block_number >= num + ::client::HISTORY { if client.chain_info().best_block_number >= num + client.pruning_history() {
// "Cancelled" is mincing words a bit -- what really happened // "Cancelled" is mincing words a bit -- what really happened
// is that the state we were snapshotting got pruned out // is that the state we were snapshotting got pruned out
// before we could finish. // before we could finish.
@ -415,9 +415,14 @@ impl Service {
guard: Guard::new(rest_dir), guard: Guard::new(rest_dir),
}; };
let state_chunks = params.manifest.state_hashes.len();
let block_chunks = params.manifest.block_hashes.len();
*res = Some(try!(Restoration::new(params))); *res = Some(try!(Restoration::new(params)));
*self.status.lock() = RestorationStatus::Ongoing { *self.status.lock() = RestorationStatus::Ongoing {
state_chunks: state_chunks as u32,
block_chunks: block_chunks as u32,
state_chunks_done: self.state_chunks.load(Ordering::SeqCst) as u32, state_chunks_done: self.state_chunks.load(Ordering::SeqCst) as u32,
block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32, block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32,
}; };
@ -535,7 +540,7 @@ impl SnapshotService for Service {
fn status(&self) -> RestorationStatus { fn status(&self) -> RestorationStatus {
let mut cur_status = self.status.lock(); let mut cur_status = self.status.lock();
if let RestorationStatus::Ongoing { ref mut state_chunks_done, ref mut block_chunks_done } = *cur_status { if let RestorationStatus::Ongoing { ref mut state_chunks_done, ref mut block_chunks_done, .. } = *cur_status {
*state_chunks_done = self.state_chunks.load(Ordering::SeqCst) as u32; *state_chunks_done = self.state_chunks.load(Ordering::SeqCst) as u32;
*block_chunks_done = self.block_chunks.load(Ordering::SeqCst) as u32; *block_chunks_done = self.block_chunks.load(Ordering::SeqCst) as u32;
} }

View File

@ -69,7 +69,7 @@ fn chunk_and_restore(amount: u64) {
// restore it. // restore it.
let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap());
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
let mut rebuilder = BlockRebuilder::new(new_chain, amount).unwrap(); let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), amount).unwrap();
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
let engine = ::engines::NullEngine::new(Default::default(), Default::default()); let engine = ::engines::NullEngine::new(Default::default(), Default::default());
for chunk_hash in &reader.manifest().block_hashes { for chunk_hash in &reader.manifest().block_hashes {

View File

@ -30,7 +30,7 @@ use std::sync::Arc;
trait Oracle: Send + Sync { trait Oracle: Send + Sync {
fn to_number(&self, hash: H256) -> Option<u64>; fn to_number(&self, hash: H256) -> Option<u64>;
fn is_major_syncing(&self) -> bool; fn is_major_importing(&self) -> bool;
} }
struct StandardOracle<F> where F: 'static + Send + Sync + Fn() -> bool { struct StandardOracle<F> where F: 'static + Send + Sync + Fn() -> bool {
@ -45,10 +45,8 @@ impl<F> Oracle for StandardOracle<F>
self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number()) self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number())
} }
fn is_major_syncing(&self) -> bool { fn is_major_importing(&self) -> bool {
let queue_info = self.client.queue_info(); (self.sync_status)()
(self.sync_status)() || queue_info.unverified_queue_size + queue_info.verified_queue_size > 3
} }
} }
@ -110,7 +108,7 @@ impl ChainNotify for Watcher {
_: Vec<H256>, _: Vec<H256>,
_duration: u64) _duration: u64)
{ {
if self.oracle.is_major_syncing() { return } if self.oracle.is_major_importing() { return }
trace!(target: "snapshot_watcher", "{} imported", imported.len()); trace!(target: "snapshot_watcher", "{} imported", imported.len());
@ -145,7 +143,7 @@ mod tests {
self.0.get(&hash).cloned() self.0.get(&hash).cloned()
} }
fn is_major_syncing(&self) -> bool { false } fn is_major_importing(&self) -> bool { false }
} }
struct TestBroadcast(Option<u64>); struct TestBroadcast(Option<u64>);

View File

@ -151,7 +151,8 @@ impl Spec {
if self.state_root_memo.read().is_none() { if self.state_root_memo.read().is_none() {
*self.state_root_memo.write() = Some(self.genesis_state.root()); *self.state_root_memo.write() = Some(self.genesis_state.root());
} }
self.state_root_memo.read().as_ref().unwrap().clone() self.state_root_memo.read().as_ref().cloned()
.expect("state root memo ensured to be set at this point; qed")
} }
/// Get the known knodes of the network in enode format. /// Get the known knodes of the network in enode format.

View File

@ -288,6 +288,15 @@ impl Account {
/// Determine whether there are any un-`commit()`-ed storage-setting operations. /// Determine whether there are any un-`commit()`-ed storage-setting operations.
pub fn storage_is_clean(&self) -> bool { self.storage_changes.is_empty() } pub fn storage_is_clean(&self) -> bool { self.storage_changes.is_empty() }
/// Check if account has zero nonce, balance, no code and no storage.
pub fn is_empty(&self) -> bool {
self.storage_changes.is_empty() &&
self.balance.is_zero() &&
self.nonce.is_zero() &&
self.storage_root == SHA3_NULL_RLP &&
self.code_hash == SHA3_EMPTY
}
#[cfg(test)] #[cfg(test)]
/// return the storage root associated with this account or None if it has been altered via the overlay. /// return the storage root associated with this account or None if it has been altered via the overlay.
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} } pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }

View File

@ -239,15 +239,15 @@ impl State {
/// Create a recoverable snaphot of this state. /// Create a recoverable snaphot of this state.
pub fn snapshot(&mut self) { pub fn snapshot(&mut self) {
self.snapshots.borrow_mut().push(HashMap::new()); self.snapshots.get_mut().push(HashMap::new());
} }
/// Merge last snapshot with previous. /// Merge last snapshot with previous.
pub fn discard_snapshot(&mut self) { pub fn discard_snapshot(&mut self) {
// merge with previous snapshot // merge with previous snapshot
let last = self.snapshots.borrow_mut().pop(); let last = self.snapshots.get_mut().pop();
if let Some(mut snapshot) = last { if let Some(mut snapshot) = last {
if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() { if let Some(ref mut prev) = self.snapshots.get_mut().last_mut() {
if prev.is_empty() { if prev.is_empty() {
**prev = snapshot; **prev = snapshot;
} else { } else {
@ -261,11 +261,11 @@ impl State {
/// Revert to the last snapshot and discard it. /// Revert to the last snapshot and discard it.
pub fn revert_to_snapshot(&mut self) { pub fn revert_to_snapshot(&mut self) {
if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() { if let Some(mut snapshot) = self.snapshots.get_mut().pop() {
for (k, v) in snapshot.drain() { for (k, v) in snapshot.drain() {
match v { match v {
Some(v) => { Some(v) => {
match self.cache.borrow_mut().entry(k) { match self.cache.get_mut().entry(k) {
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
// Merge snapshotted changes back into the main account // Merge snapshotted changes back into the main account
// storage preserving the cache. // storage preserving the cache.
@ -277,7 +277,7 @@ impl State {
} }
}, },
None => { None => {
match self.cache.borrow_mut().entry(k) { match self.cache.get_mut().entry(k) {
Entry::Occupied(e) => { Entry::Occupied(e) => {
if e.get().is_dirty() { if e.get().is_dirty() {
e.remove(); e.remove();
@ -340,18 +340,20 @@ impl State {
/// Determine whether an account exists. /// Determine whether an account exists.
pub fn exists(&self, a: &Address) -> bool { pub fn exists(&self, a: &Address) -> bool {
self.ensure_cached(a, RequireCache::None, |a| a.is_some()) // Bloom filter does not contain empty accounts, so it is important here to
// check if account exists in the database directly before EIP-158 is in effect.
self.ensure_cached(a, RequireCache::None, false, |a| a.is_some())
} }
/// Get the balance of account `a`. /// Get the balance of account `a`.
pub fn balance(&self, a: &Address) -> U256 { pub fn balance(&self, a: &Address) -> U256 {
self.ensure_cached(a, RequireCache::None, self.ensure_cached(a, RequireCache::None, true,
|a| a.as_ref().map_or(U256::zero(), |account| *account.balance())) |a| a.as_ref().map_or(U256::zero(), |account| *account.balance()))
} }
/// Get the nonce of account `a`. /// Get the nonce of account `a`.
pub fn nonce(&self, a: &Address) -> U256 { pub fn nonce(&self, a: &Address) -> U256 {
self.ensure_cached(a, RequireCache::None, self.ensure_cached(a, RequireCache::None, true,
|a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce())) |a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce()))
} }
@ -415,18 +417,18 @@ impl State {
/// Get accounts' code. /// Get accounts' code.
pub fn code(&self, a: &Address) -> Option<Arc<Bytes>> { pub fn code(&self, a: &Address) -> Option<Arc<Bytes>> {
self.ensure_cached(a, RequireCache::Code, self.ensure_cached(a, RequireCache::Code, true,
|a| a.as_ref().map_or(None, |a| a.code().clone())) |a| a.as_ref().map_or(None, |a| a.code().clone()))
} }
pub fn code_hash(&self, a: &Address) -> H256 { pub fn code_hash(&self, a: &Address) -> H256 {
self.ensure_cached(a, RequireCache::None, self.ensure_cached(a, RequireCache::None, true,
|a| a.as_ref().map_or(SHA3_EMPTY, |a| a.code_hash())) |a| a.as_ref().map_or(SHA3_EMPTY, |a| a.code_hash()))
} }
/// Get accounts' code size. /// Get accounts' code size.
pub fn code_size(&self, a: &Address) -> Option<usize> { pub fn code_size(&self, a: &Address) -> Option<usize> {
self.ensure_cached(a, RequireCache::CodeSize, self.ensure_cached(a, RequireCache::CodeSize, true,
|a| a.as_ref().and_then(|a| a.code_size())) |a| a.as_ref().and_then(|a| a.code_size()))
} }
@ -505,7 +507,9 @@ impl State {
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
match a.account { match a.account {
Some(ref mut account) => { Some(ref mut account) => {
if !account.is_empty() {
db.note_account_bloom(&address); db.note_account_bloom(&address);
}
let addr_hash = account.address_hash(address); let addr_hash = account.address_hash(address);
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
account.commit_storage(&factories.trie, account_db.as_hashdb_mut()); account.commit_storage(&factories.trie, account_db.as_hashdb_mut());
@ -516,7 +520,7 @@ impl State {
} }
{ {
let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root).unwrap(); let mut trie = try!(factories.trie.from_existing(db.as_hashdb_mut(), root));
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
a.state = AccountState::Committed; a.state = AccountState::Committed;
match a.account { match a.account {
@ -559,7 +563,6 @@ impl State {
pub fn populate_from(&mut self, accounts: PodState) { pub fn populate_from(&mut self, accounts: PodState) {
assert!(self.snapshots.borrow().is_empty()); assert!(self.snapshots.borrow().is_empty());
for (add, acc) in accounts.drain().into_iter() { for (add, acc) in accounts.drain().into_iter() {
self.db.note_account_bloom(&add);
self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc)))); self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc))));
} }
} }
@ -578,15 +581,15 @@ impl State {
} }
fn query_pod(&mut self, query: &PodState) { fn query_pod(&mut self, query: &PodState) {
for (address, pod_account) in query.get() { for (address, pod_account) in query.get().into_iter()
self.ensure_cached(address, RequireCache::Code, |a| { .filter(|&(ref a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some()))
if a.is_some() { {
// needs to be split into two parts for the refcell code here
// to work.
for key in pod_account.storage.keys() { for key in pod_account.storage.keys() {
self.storage_at(address, key); self.storage_at(address, key);
} }
} }
});
}
} }
/// Returns a `StateDiff` describing the difference from `orig` to `self`. /// Returns a `StateDiff` describing the difference from `orig` to `self`.
@ -613,7 +616,7 @@ impl State {
/// Check caches for required data /// Check caches for required data
/// First searches for account in the local, then the shared cache. /// First searches for account in the local, then the shared cache.
/// Populates local cache if nothing found. /// Populates local cache if nothing found.
fn ensure_cached<F, U>(&self, a: &Address, require: RequireCache, f: F) -> U fn ensure_cached<F, U>(&self, a: &Address, require: RequireCache, check_bloom: bool, f: F) -> U
where F: Fn(Option<&Account>) -> U { where F: Fn(Option<&Account>) -> U {
// check local cache first // check local cache first
if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) {
@ -636,7 +639,7 @@ impl State {
Some(r) => r, Some(r) => r,
None => { None => {
// first check bloom if it is not in database for sure // first check bloom if it is not in database for sure
if !self.db.check_account_bloom(a) { return f(None); } if check_bloom && !self.db.check_account_bloom(a) { return f(None); }
// not found in the global cache, get from the DB and insert into local // not found in the global cache, get from the DB and insert into local
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
@ -688,14 +691,15 @@ impl State {
} }
self.note_cache(a); self.note_cache(a);
match &mut self.cache.borrow_mut().get_mut(a).unwrap().account { // at this point the entry is guaranteed to be in the cache.
RefMut::map(self.cache.borrow_mut(), |c| {
let mut entry = c.get_mut(a).expect("entry known to exist in the cache; qed");
match &mut entry.account {
&mut Some(ref mut acc) => not_default(acc), &mut Some(ref mut acc) => not_default(acc),
slot => *slot = Some(default()), slot => *slot = Some(default()),
} }
// at this point the account is guaranteed to be in the cache.
RefMut::map(self.cache.borrow_mut(), |c| {
let mut entry = c.get_mut(a).unwrap();
// set the dirty flag after changing account data. // set the dirty flag after changing account data.
entry.state = AccountState::Dirty; entry.state = AccountState::Dirty;
match entry.account { match entry.account {
@ -1667,6 +1671,21 @@ fn remove() {
assert_eq!(state.nonce(&a), U256::from(0u64)); assert_eq!(state.nonce(&a), U256::from(0u64));
} }
#[test]
fn empty_account_exists() {
let a = Address::zero();
let path = RandomTempPath::new();
let db = get_temp_state_db_in(path.as_path());
let (root, db) = {
let mut state = State::new(db, U256::from(0), Default::default());
state.add_balance(&a, &U256::default()); // create an empty account
state.commit().unwrap();
state.drop()
};
let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap();
assert!(state.exists(&a));
}
#[test] #[test]
fn remove_from_database() { fn remove_from_database() {
let a = Address::zero(); let a = Address::zero();
@ -1797,4 +1816,20 @@ fn create_empty() {
assert_eq!(state.root().hex(), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); assert_eq!(state.root().hex(), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421");
} }
#[test]
fn should_not_panic_on_state_diff_with_storage() {
let state = get_temp_state();
let mut state = state.reference().clone();
let a: Address = 0xa.into();
state.init_code(&a, b"abcdefg".to_vec());
state.add_balance(&a, &256.into());
state.set_storage(&a, 0xb.into(), 0xc.into());
let mut new_state = state.clone();
new_state.set_storage(&a, 0xb.into(), 0xd.into());
new_state.diff_from(state);
}
} }

View File

@ -26,17 +26,18 @@ use bloom_journal::{Bloom, BloomJournal};
use db::COL_ACCOUNT_BLOOM; use db::COL_ACCOUNT_BLOOM;
use byteorder::{LittleEndian, ByteOrder}; use byteorder::{LittleEndian, ByteOrder};
const STATE_CACHE_ITEMS: usize = 256000;
const STATE_CACHE_BLOCKS: usize = 8;
pub const ACCOUNT_BLOOM_SPACE: usize = 1048576; pub const ACCOUNT_BLOOM_SPACE: usize = 1048576;
pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000; pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000;
pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count";
const STATE_CACHE_BLOCKS: usize = 12;
/// Shared canonical state cache. /// Shared canonical state cache.
struct AccountCache { struct AccountCache {
/// DB Account cache. `None` indicates that account is known to be missing. /// DB Account cache. `None` indicates that account is known to be missing.
// When changing the type of the values here, be sure to update `mem_used` and
// `new`.
accounts: LruCache<Address, Option<Account>>, accounts: LruCache<Address, Option<Account>>,
/// Information on the modifications in recently committed blocks; specifically which addresses /// Information on the modifications in recently committed blocks; specifically which addresses
/// changed in which block. Ordered by block number. /// changed in which block. Ordered by block number.
@ -92,6 +93,7 @@ pub struct StateDB {
local_cache: Vec<CacheQueueItem>, local_cache: Vec<CacheQueueItem>,
/// Shared account bloom. Does not handle chain reorganizations. /// Shared account bloom. Does not handle chain reorganizations.
account_bloom: Arc<Mutex<Bloom>>, account_bloom: Arc<Mutex<Bloom>>,
cache_size: usize,
/// Hash of the block on top of which this instance was created or /// Hash of the block on top of which this instance was created or
/// `None` if cache is disabled /// `None` if cache is disabled
parent_hash: Option<H256>, parent_hash: Option<H256>,
@ -102,16 +104,41 @@ pub struct StateDB {
} }
impl StateDB { impl StateDB {
/// Create a new instance wrapping `JournalDB` and the maximum allowed size
/// of the LRU cache in bytes. Actual used memory may (read: will) be higher due to bookkeeping.
// TODO: make the cache size actually accurate by moving the account storage cache
// into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`.
pub fn new(db: Box<JournalDB>, cache_size: usize) -> StateDB {
let bloom = Self::load_bloom(db.backing());
let cache_items = cache_size / ::std::mem::size_of::<Option<Account>>();
StateDB {
db: db,
account_cache: Arc::new(Mutex::new(AccountCache {
accounts: LruCache::new(cache_items),
modifications: VecDeque::new(),
})),
local_cache: Vec::new(),
account_bloom: Arc::new(Mutex::new(bloom)),
cache_size: cache_size,
parent_hash: None,
commit_hash: None,
commit_number: None,
}
}
/// Loads accounts bloom from the database /// Loads accounts bloom from the database
/// This bloom is used to handle request for the non-existant account fast /// This bloom is used to handle request for the non-existant account fast
pub fn load_bloom(db: &Database) -> Bloom { pub fn load_bloom(db: &Database) -> Bloom {
let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY) let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY)
.expect("Low-level database error"); .expect("Low-level database error");
if hash_count_entry.is_none() { let hash_count_bytes = match hash_count_entry {
return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET); Some(bytes) => bytes,
} None => return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET),
let hash_count_bytes = hash_count_entry.unwrap(); };
assert_eq!(hash_count_bytes.len(), 1); assert_eq!(hash_count_bytes.len(), 1);
let hash_count = hash_count_bytes[0]; let hash_count = hash_count_bytes[0];
@ -129,23 +156,6 @@ impl StateDB {
bloom bloom
} }
/// Create a new instance wrapping `JournalDB`
pub fn new(db: Box<JournalDB>) -> StateDB {
let bloom = Self::load_bloom(db.backing());
StateDB {
db: db,
account_cache: Arc::new(Mutex::new(AccountCache {
accounts: LruCache::new(STATE_CACHE_ITEMS),
modifications: VecDeque::new(),
})),
local_cache: Vec::new(),
account_bloom: Arc::new(Mutex::new(bloom)),
parent_hash: None,
commit_hash: None,
commit_number: None,
}
}
pub fn check_account_bloom(&self, address: &Address) -> bool { pub fn check_account_bloom(&self, address: &Address) -> bool {
trace!(target: "account_bloom", "Check account bloom: {:?}", address); trace!(target: "account_bloom", "Check account bloom: {:?}", address);
let bloom = self.account_bloom.lock(); let bloom = self.account_bloom.lock();
@ -172,19 +182,24 @@ impl StateDB {
Ok(()) Ok(())
} }
/// Commit all recent insert operations and canonical historical commits' removals from the /// Journal all recent operations under the given era and ID.
/// old era to the backing database, reverting any non-canonical historical commit's inserts. pub fn journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result<u32, UtilError> {
pub fn commit(&mut self, batch: &mut DBTransaction, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
{ {
let mut bloom_lock = self.account_bloom.lock(); let mut bloom_lock = self.account_bloom.lock();
try!(Self::commit_bloom(batch, bloom_lock.drain_journal())); try!(Self::commit_bloom(batch, bloom_lock.drain_journal()));
} }
let records = try!(self.db.commit(batch, now, id, end)); let records = try!(self.db.journal_under(batch, now, id));
self.commit_hash = Some(id.clone()); self.commit_hash = Some(id.clone());
self.commit_number = Some(now); self.commit_number = Some(now);
Ok(records) Ok(records)
} }
/// Mark a given candidate from an ancient era as canonical, enacting its removals from the
/// backing database and reverting any non-canonical historical commit's insertions.
pub fn mark_canonical(&mut self, batch: &mut DBTransaction, end_era: u64, canon_id: &H256) -> Result<u32, UtilError> {
self.db.mark_canonical(batch, end_era, canon_id)
}
/// Propagate local cache into the global cache and synchonize /// Propagate local cache into the global cache and synchonize
/// the global cache with the best block state. /// the global cache with the best block state.
/// This function updates the global cache by removing entries /// This function updates the global cache by removing entries
@ -298,6 +313,7 @@ impl StateDB {
account_cache: self.account_cache.clone(), account_cache: self.account_cache.clone(),
local_cache: Vec::new(), local_cache: Vec::new(),
account_bloom: self.account_bloom.clone(), account_bloom: self.account_bloom.clone(),
cache_size: self.cache_size,
parent_hash: None, parent_hash: None,
commit_hash: None, commit_hash: None,
commit_number: None, commit_number: None,
@ -311,6 +327,7 @@ impl StateDB {
account_cache: self.account_cache.clone(), account_cache: self.account_cache.clone(),
local_cache: Vec::new(), local_cache: Vec::new(),
account_bloom: self.account_bloom.clone(), account_bloom: self.account_bloom.clone(),
cache_size: self.cache_size,
parent_hash: Some(parent.clone()), parent_hash: Some(parent.clone()),
commit_hash: None, commit_hash: None,
commit_number: None, commit_number: None,
@ -324,7 +341,8 @@ impl StateDB {
/// Heap size used. /// Heap size used.
pub fn mem_used(&self) -> usize { pub fn mem_used(&self) -> usize {
self.db.mem_used() //TODO: + self.account_cache.lock().heap_size_of_children() // TODO: account for LRU-cache overhead; this is a close approximation.
self.db.mem_used() + self.account_cache.lock().accounts.len() * ::std::mem::size_of::<Option<Account>>()
} }
/// Returns underlying `JournalDB`. /// Returns underlying `JournalDB`.
@ -365,6 +383,11 @@ impl StateDB {
cache.accounts.get_mut(a).map(|c| f(c.as_mut())) cache.accounts.get_mut(a).map(|c| f(c.as_mut()))
} }
/// Query how much memory is set aside for the accounts cache (in bytes).
pub fn cache_size(&self) -> usize {
self.cache_size
}
/// Check if the account can be returned from cache by matching current block parent hash against canonical /// Check if the account can be returned from cache by matching current block parent hash against canonical
/// state and filtering out account modified in later blocks. /// state and filtering out account modified in later blocks.
fn is_allowed(addr: &Address, parent_hash: &Option<H256>, modifications: &VecDeque<BlockChanges>) -> bool { fn is_allowed(addr: &Address, parent_hash: &Option<H256>, modifications: &VecDeque<BlockChanges>) -> bool {
@ -430,30 +453,30 @@ fn state_db_smoke() {
// balance [ 5 5 4 3 2 2 ] // balance [ 5 5 4 3 2 2 ]
let mut s = state_db.boxed_clone_canon(&root_parent); let mut s = state_db.boxed_clone_canon(&root_parent);
s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false); s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false);
s.commit(&mut batch, 0, &h0, None).unwrap(); s.journal_under(&mut batch, 0, &h0).unwrap();
s.sync_cache(&[], &[], true); s.sync_cache(&[], &[], true);
let mut s = state_db.boxed_clone_canon(&h0); let mut s = state_db.boxed_clone_canon(&h0);
s.commit(&mut batch, 1, &h1a, None).unwrap(); s.journal_under(&mut batch, 1, &h1a).unwrap();
s.sync_cache(&[], &[], true); s.sync_cache(&[], &[], true);
let mut s = state_db.boxed_clone_canon(&h0); let mut s = state_db.boxed_clone_canon(&h0);
s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true); s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true);
s.commit(&mut batch, 1, &h1b, None).unwrap(); s.journal_under(&mut batch, 1, &h1b).unwrap();
s.sync_cache(&[], &[], false); s.sync_cache(&[], &[], false);
let mut s = state_db.boxed_clone_canon(&h1b); let mut s = state_db.boxed_clone_canon(&h1b);
s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true); s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true);
s.commit(&mut batch, 2, &h2b, None).unwrap(); s.journal_under(&mut batch, 2, &h2b).unwrap();
s.sync_cache(&[], &[], false); s.sync_cache(&[], &[], false);
let mut s = state_db.boxed_clone_canon(&h1a); let mut s = state_db.boxed_clone_canon(&h1a);
s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true); s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true);
s.commit(&mut batch, 2, &h2a, None).unwrap(); s.journal_under(&mut batch, 2, &h2a).unwrap();
s.sync_cache(&[], &[], true); s.sync_cache(&[], &[], true);
let mut s = state_db.boxed_clone_canon(&h2a); let mut s = state_db.boxed_clone_canon(&h2a);
s.commit(&mut batch, 3, &h3a, None).unwrap(); s.journal_under(&mut batch, 3, &h3a).unwrap();
s.sync_cache(&[], &[], true); s.sync_cache(&[], &[], true);
let s = state_db.boxed_clone_canon(&h3a); let s = state_db.boxed_clone_canon(&h3a);
@ -471,7 +494,7 @@ fn state_db_smoke() {
// reorg to 3b // reorg to 3b
// blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ] // blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ]
let mut s = state_db.boxed_clone_canon(&h2b); let mut s = state_db.boxed_clone_canon(&h2b);
s.commit(&mut batch, 3, &h3b, None).unwrap(); s.journal_under(&mut batch, 3, &h3b).unwrap();
s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true); s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true);
let s = state_db.boxed_clone_canon(&h3a); let s = state_db.boxed_clone_canon(&h3a);
assert!(s.get_cached_account(&address).is_none()); assert!(s.get_cached_account(&address).is_none());

View File

@ -24,6 +24,7 @@ use common::*;
use devtools::*; use devtools::*;
use miner::Miner; use miner::Miner;
use rlp::{Rlp, View}; use rlp::{Rlp, View};
use spec::Spec;
#[test] #[test]
fn imports_from_empty() { fn imports_from_empty() {
@ -222,7 +223,7 @@ fn can_handle_long_fork() {
push_blocks_to_client(client, 49, 1201, 800); push_blocks_to_client(client, 49, 1201, 800);
push_blocks_to_client(client, 53, 1201, 600); push_blocks_to_client(client, 53, 1201, 600);
for _ in 0..40 { for _ in 0..400 {
client.import_verified_blocks(); client.import_verified_blocks();
} }
assert_eq!(2000, client.chain_info().best_block_number); assert_eq!(2000, client.chain_info().best_block_number);
@ -238,3 +239,27 @@ fn can_mine() {
assert_eq!(*b.block().header().parent_hash(), BlockView::new(&dummy_blocks[0]).header_view().sha3()); assert_eq!(*b.block().header().parent_hash(), BlockView::new(&dummy_blocks[0]).header_view().sha3());
} }
#[test]
fn change_history_size() {
let dir = RandomTempPath::new();
let test_spec = Spec::new_null();
let mut config = ClientConfig::default();
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
config.history = 2;
let address = Address::random();
{
let client = Client::new(ClientConfig::default(), &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
for _ in 0..20 {
let mut b = client.prepare_open_block(Address::default(), (3141562.into(), 31415620.into()), vec![]);
b.block_mut().fields_mut().state.add_balance(&address, &5.into());
b.block_mut().fields_mut().state.commit().unwrap();
let b = b.close_and_lock().seal(&*test_spec.engine, vec![]).unwrap();
client.import_sealed_block(b).unwrap(); // account change is in the journal overlay
}
}
let mut config = ClientConfig::default();
config.history = 10;
let client = Client::new(config, &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
assert_eq!(client.state().balance(&address), 100.into());
}

View File

@ -22,7 +22,6 @@ use spec::*;
use state_db::StateDB; use state_db::StateDB;
use block::{OpenBlock, Drain}; use block::{OpenBlock, Drain};
use blockchain::{BlockChain, Config as BlockChainConfig}; use blockchain::{BlockChain, Config as BlockChainConfig};
use state::State;
use evm::Schedule; use evm::Schedule;
use engines::Engine; use engines::Engine;
use ethereum; use ethereum;
@ -35,19 +34,20 @@ use db::COL_STATE;
pub enum ChainEra { pub enum ChainEra {
Frontier, Frontier,
Homestead, Homestead,
DaoHardfork, Eip150,
TransitionTest,
} }
pub struct TestEngine { pub struct TestEngine {
engine: Arc<Engine>, engine: Arc<Engine>,
max_depth: usize max_depth: usize,
} }
impl TestEngine { impl TestEngine {
pub fn new(max_depth: usize) -> TestEngine { pub fn new(max_depth: usize) -> TestEngine {
TestEngine { TestEngine {
engine: ethereum::new_frontier_test().engine, engine: ethereum::new_frontier_test().engine,
max_depth: max_depth max_depth: max_depth,
} }
} }
} }
@ -347,7 +347,7 @@ pub fn get_temp_state() -> GuardedTempResult<State> {
pub fn get_temp_state_db_in(path: &Path) -> StateDB { pub fn get_temp_state_db_in(path: &Path) -> StateDB {
let db = new_db(path.to_str().expect("Only valid utf8 paths for tests.")); let db = new_db(path.to_str().expect("Only valid utf8 paths for tests."));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, COL_STATE); let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, COL_STATE);
StateDB::new(journal_db) StateDB::new(journal_db, 5 * 1024 * 1024)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -129,7 +129,7 @@ impl<T> TraceDB<T> where T: DatabaseExtras {
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self { pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
let mut batch = DBTransaction::new(&tracesdb); let mut batch = DBTransaction::new(&tracesdb);
batch.put(db::COL_TRACE, b"version", TRACE_DB_VER); batch.put(db::COL_TRACE, b"version", TRACE_DB_VER);
tracesdb.write(batch).unwrap(); tracesdb.write(batch).expect("failed to update version");
TraceDB { TraceDB {
traces: RwLock::new(HashMap::new()), traces: RwLock::new(HashMap::new()),
@ -275,16 +275,6 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
.map(Into::into) .map(Into::into)
.collect(); .collect();
// insert new block traces into the cache and the database
{
let mut traces = self.traces.write();
// it's important to use overwrite here,
// cause this value might be queried by hash later
batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite);
// note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection
self.note_used(CacheID::Trace(request.block_hash.clone()));
}
let chain = BloomGroupChain::new(self.bloom_config, self); let chain = BloomGroupChain::new(self.bloom_config, self);
let trace_blooms = chain.replace(&replaced_range, enacted_blooms); let trace_blooms = chain.replace(&replaced_range, enacted_blooms);
let blooms_to_insert = trace_blooms.into_iter() let blooms_to_insert = trace_blooms.into_iter()
@ -299,6 +289,16 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
self.note_used(CacheID::Bloom(key)); self.note_used(CacheID::Bloom(key));
} }
} }
// insert new block traces into the cache and the database
{
let mut traces = self.traces.write();
// it's important to use overwrite here,
// cause this value might be queried by hash later
batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite);
// note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection
self.note_used(CacheID::Trace(request.block_hash.clone()));
}
} }
fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> { fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> {
@ -504,6 +504,28 @@ mod tests {
} }
} }
fn create_noncanon_import_request(block_number: BlockNumber, block_hash: H256) -> ImportRequest {
ImportRequest {
traces: FlatBlockTraces::from(vec![FlatTransactionTraces::from(vec![FlatTrace {
trace_address: Default::default(),
subtraces: 0,
action: Action::Call(Call {
from: 1.into(),
to: 2.into(),
value: 3.into(),
gas: 4.into(),
input: vec![],
call_type: CallType::Call,
}),
result: Res::FailedCall(TraceError::OutOfGas),
}])]),
block_hash: block_hash.clone(),
block_number: block_number,
enacted: vec![],
retracted: 0,
}
}
fn create_simple_localized_trace(block_number: BlockNumber, block_hash: H256, tx_hash: H256) -> LocalizedTrace { fn create_simple_localized_trace(block_number: BlockNumber, block_hash: H256, tx_hash: H256) -> LocalizedTrace {
LocalizedTrace { LocalizedTrace {
action: Action::Call(Call { action: Action::Call(Call {
@ -524,6 +546,34 @@ mod tests {
} }
} }
#[test]
fn test_import_non_canon_traces() {
let temp = RandomTempPath::new();
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
let mut config = Config::default();
config.enabled = true;
let block_0 = H256::from(0xa1);
let block_1 = H256::from(0xa2);
let tx_0 = H256::from(0xff);
let tx_1 = H256::from(0xaf);
let mut extras = Extras::default();
extras.block_hashes.insert(0, block_0.clone());
extras.block_hashes.insert(1, block_1.clone());
extras.transaction_hashes.insert(0, vec![tx_0.clone()]);
extras.transaction_hashes.insert(1, vec![tx_1.clone()]);
let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras));
// import block 0
let request = create_noncanon_import_request(0, block_0.clone());
let mut batch = DBTransaction::new(&db);
tracedb.import(&mut batch, request);
db.write(batch).unwrap();
assert!(tracedb.traces(&block_0).is_some(), "Traces should be available even if block is non-canon.");
}
#[test] #[test]
fn test_import() { fn test_import() {

View File

@ -31,5 +31,13 @@ pub struct BlockChainInfo {
/// Best blockchain block hash. /// Best blockchain block hash.
pub best_block_hash: H256, pub best_block_hash: H256,
/// Best blockchain block number. /// Best blockchain block number.
pub best_block_number: BlockNumber pub best_block_number: BlockNumber,
/// Best ancient block hash.
pub ancient_block_hash: Option<H256>,
/// Best ancient block number.
pub ancient_block_number: Option<BlockNumber>,
/// First block on the best sequence.
pub first_block_hash: Option<H256>,
/// Number of the first block on the best sequence.
pub first_block_number: Option<BlockNumber>,
} }

View File

@ -23,6 +23,10 @@ pub enum RestorationStatus {
Inactive, Inactive,
/// Ongoing restoration. /// Ongoing restoration.
Ongoing { Ongoing {
/// Total number of state chunks.
state_chunks: u32,
/// Total number of block chunks.
block_chunks: u32,
/// Number of state chunks completed. /// Number of state chunks completed.
state_chunks_done: u32, state_chunks_done: u32,
/// Number of block chunks completed. /// Number of block chunks completed.

View File

@ -108,7 +108,7 @@ impl Filter {
/// Returns true if given trace matches the filter. /// Returns true if given trace matches the filter.
pub fn matches(&self, trace: &FlatTrace) -> bool { pub fn matches(&self, trace: &FlatTrace) -> bool {
let action = match trace.action { match trace.action {
Action::Call(ref call) => { Action::Call(ref call) => {
let from_matches = self.from_address.matches(&call.from); let from_matches = self.from_address.matches(&call.from);
let to_matches = self.to_address.matches(&call.to); let to_matches = self.to_address.matches(&call.to);
@ -116,7 +116,12 @@ impl Filter {
} }
Action::Create(ref create) => { Action::Create(ref create) => {
let from_matches = self.from_address.matches(&create.from); let from_matches = self.from_address.matches(&create.from);
let to_matches = self.to_address.matches_all();
let to_matches = match trace.result {
Res::Create(ref create_result) => self.to_address.matches(&create_result.address),
_ => false
};
from_matches && to_matches from_matches && to_matches
}, },
Action::Suicide(ref suicide) => { Action::Suicide(ref suicide) => {
@ -124,11 +129,6 @@ impl Filter {
let to_matches = self.to_address.matches(&suicide.refund_address); let to_matches = self.to_address.matches(&suicide.refund_address);
from_matches && to_matches from_matches && to_matches
} }
};
action || match trace.result {
Res::Create(ref create) => self.to_address.matches(&create.address),
_ => false
} }
} }
} }

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