Merge remote-tracking branch 'parity/master' into auth-round-no-mocknet
This commit is contained in:
commit
aa05823afe
@ -121,7 +121,6 @@ linux-armv7:
|
||||
stage: build
|
||||
image: ethcore/rust-armv7:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
@ -150,7 +149,6 @@ linux-arm:
|
||||
stage: build
|
||||
image: ethcore/rust-arm:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
@ -179,7 +177,6 @@ linux-armv6:
|
||||
stage: build
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
@ -208,7 +205,6 @@ linux-aarch64:
|
||||
stage: build
|
||||
image: ethcore/rust-aarch64:latest
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
@ -266,13 +262,14 @@ windows:
|
||||
- set RUST_BACKTRACE=1
|
||||
- set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings
|
||||
- rustup default stable-x86_64-pc-windows-msvc
|
||||
- git submodule update --init
|
||||
- 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/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||
- cd nsis
|
||||
- makensis.exe installer.nsi
|
||||
- cp installer.exe InstallParity.exe
|
||||
- copy installer.exe InstallParity.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
|
||||
- md5sums InstallParity.exe > InstallParity.exe.md5
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
@ -299,8 +296,9 @@ windows:
|
||||
paths:
|
||||
- target/release/parity.exe
|
||||
- target/release/parity.pdb
|
||||
- nsis/installer.exe
|
||||
- nsis/InstallParity.exe
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
allow_failure: true
|
||||
test-linux:
|
||||
stage: test
|
||||
before_script:
|
||||
@ -312,3 +310,46 @@ test-linux:
|
||||
- rust-test
|
||||
dependencies:
|
||||
- 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
3
.gitmodules
vendored
@ -2,3 +2,6 @@
|
||||
path = ethcore/res/ethereum/tests
|
||||
url = https://github.com/ethereum/tests.git
|
||||
branch = develop
|
||||
[submodule "js/build"]
|
||||
path = js/build
|
||||
url = https://github.com/ethcore/js-precompiled
|
||||
|
132
Cargo.lock
generated
132
Cargo.lock
generated
@ -264,7 +264,7 @@ name = "ethash"
|
||||
version = "1.4.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"sha3 0.1.0",
|
||||
]
|
||||
@ -295,7 +295,7 @@ dependencies = [
|
||||
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
@ -319,6 +319,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ethcore-bloom-journal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethcore-dapps"
|
||||
@ -332,14 +335,15 @@ dependencies = [
|
||||
"fetch 0.1.0",
|
||||
"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-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)",
|
||||
"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)",
|
||||
"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-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
|
||||
"parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
|
||||
"parity-ui 1.4.0",
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
]
|
||||
|
||||
@ -453,7 +457,7 @@ dependencies = [
|
||||
"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)",
|
||||
"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)",
|
||||
"rlp 0.1.0",
|
||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -482,7 +486,7 @@ dependencies = [
|
||||
"fetch 0.1.0",
|
||||
"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-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)",
|
||||
"rlp 0.1.0",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -505,7 +509,8 @@ dependencies = [
|
||||
"ethcore-util 1.4.0",
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
@ -547,8 +552,9 @@ dependencies = [
|
||||
"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)",
|
||||
"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)",
|
||||
"regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlp 0.1.0",
|
||||
"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)",
|
||||
@ -633,7 +639,7 @@ dependencies = [
|
||||
"ethcore-util 1.4.0",
|
||||
"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)",
|
||||
"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)",
|
||||
"rlp 0.1.0",
|
||||
"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"
|
||||
dependencies = [
|
||||
"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_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)",
|
||||
@ -847,8 +853,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc-http-server"
|
||||
version = "6.1.0"
|
||||
source = "git+https://github.com/ethcore/jsonrpc-http-server.git#2766c6708f66f6f4e667211461d220b49c0d9fdf"
|
||||
version = "6.1.1"
|
||||
source = "git+https://github.com/ethcore/jsonrpc-http-server.git#ee72e4778583daf901b5692468fc622f46abecb6"
|
||||
dependencies = [
|
||||
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
|
||||
"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]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.0.9"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -898,9 +904,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
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 = [
|
||||
"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]]
|
||||
@ -1164,10 +1170,28 @@ name = "odds"
|
||||
version = "0.2.12"
|
||||
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]]
|
||||
name = "parity-dapps"
|
||||
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 = [
|
||||
"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)",
|
||||
@ -1181,38 +1205,21 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-dapps-home"
|
||||
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 = [
|
||||
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-dapps-signer"
|
||||
name = "parity-ui"
|
||||
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-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)",
|
||||
"parity-dapps-glue 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.2.6"
|
||||
version = "0.2.8"
|
||||
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)",
|
||||
@ -1221,6 +1228,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "phf"
|
||||
version = "0.7.14"
|
||||
@ -1406,7 +1434,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rocksdb"
|
||||
version = "0.4.5"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
|
||||
dependencies = [
|
||||
"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)",
|
||||
@ -1415,7 +1443,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rocksdb-sys"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
|
||||
dependencies = [
|
||||
"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)",
|
||||
@ -1544,6 +1572,11 @@ dependencies = [
|
||||
"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]]
|
||||
name = "slab"
|
||||
version = "0.1.3"
|
||||
@ -1851,7 +1884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "ws"
|
||||
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 = [
|
||||
"bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)",
|
||||
"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-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-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 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 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 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 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"
|
||||
@ -1982,12 +2015,12 @@ dependencies = [
|
||||
"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 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-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 parity-dapps-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>"
|
||||
"checksum parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "<none>"
|
||||
"checksum parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e0fd1be2c3cf5fef20a6d18fec252c4f3c87c14fc3039002eb7d4ed91e436826"
|
||||
"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6"
|
||||
"checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125"
|
||||
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
||||
"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_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_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 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.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"
|
||||
|
@ -58,7 +58,7 @@ version = "0.9"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
default = ["ui", "use-precompiled-js", "ipc"]
|
||||
default = ["ui", "use-precompiled-js"]
|
||||
ui = ["dapps", "ethcore-signer/ui"]
|
||||
use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"]
|
||||
dapps = ["ethcore-dapps"]
|
||||
|
@ -27,11 +27,14 @@ ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-rpc = { path = "../rpc" }
|
||||
ethcore-util = { path = "../util" }
|
||||
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" }
|
||||
# List of apps
|
||||
parity-dapps-status = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
|
||||
parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }
|
||||
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true }
|
||||
### /DEPRECATED
|
||||
|
||||
mime_guess = { version = "1.6.1" }
|
||||
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 }
|
||||
|
||||
[features]
|
||||
default = ["serde_codegen", "extra-dapps"]
|
||||
extra-dapps = ["parity-dapps-wallet"]
|
||||
default = ["serde_codegen"]
|
||||
nightly = ["serde_macros"]
|
||||
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
|
||||
|
||||
use-precompiled-js = [
|
||||
"parity-dapps-status/use-precompiled-js",
|
||||
"parity-ui/use-precompiled-js",
|
||||
"parity-dapps-home/use-precompiled-js",
|
||||
"parity-dapps-wallet/use-precompiled-js"
|
||||
]
|
||||
|
31
dapps/js-glue/Cargo.toml
Normal file
31
dapps/js-glue/Cargo.toml
Normal 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
65
dapps/js-glue/README.md
Normal 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
45
dapps/js-glue/build.rs
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#[cfg(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();
|
||||
}
|
66
dapps/js-glue/src/build.rs
Normal file
66
dapps/js-glue/src/build.rs
Normal 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() {}
|
||||
}
|
189
dapps/js-glue/src/codegen.rs
Normal file
189
dapps/js-glue/src/codegen.rs
Normal 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
89
dapps/js-glue/src/js.rs
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![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
39
dapps/js-glue/src/lib.rs
Normal 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");
|
46
dapps/js-glue/src/lib.rs.in
Normal file
46
dapps/js-glue/src/lib.rs.in
Normal 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;
|
||||
}
|
@ -19,12 +19,16 @@ use serde_json;
|
||||
use endpoint::Handler;
|
||||
use handlers::{ContentHandler, EchoHandler};
|
||||
|
||||
pub fn as_json<T : Serialize>(val: &T) -> Box<Handler> {
|
||||
Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
|
||||
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn as_json_error<T : Serialize>(val: &T) -> Box<Handler> {
|
||||
Box::new(ContentHandler::not_found(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
|
||||
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
Box::new(ContentHandler::not_found(json, mime!(Application/Json)))
|
||||
}
|
||||
|
||||
pub fn ping_response(local_domain: &str) -> Box<Handler> {
|
||||
|
@ -47,15 +47,17 @@ impl ContentCache {
|
||||
}
|
||||
|
||||
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 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
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 {
|
||||
ContentStatus::Fetching(ref fetch) => {
|
||||
trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
|
||||
@ -73,7 +75,6 @@ impl ContentCache {
|
||||
}
|
||||
|
||||
removed.push(entry);
|
||||
len -= 1;
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
// fallback to resolver
|
||||
if let Ok(content_id) = content_id.from_hex() {
|
||||
// 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;
|
||||
}
|
||||
// else try to resolve the app_id
|
||||
@ -99,7 +99,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
let mut cache = self.cache.lock();
|
||||
let content_id = path.app_id.clone();
|
||||
|
||||
if self.sync.is_major_syncing() {
|
||||
if self.sync.is_major_importing() {
|
||||
return Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Sync In Progress",
|
||||
|
@ -17,7 +17,8 @@
|
||||
use endpoint::{Endpoints, Endpoint};
|
||||
use page::PageEndpoint;
|
||||
use proxypac::ProxyPac;
|
||||
use parity_dapps::WebApp;
|
||||
use parity_dapps::{self, WebApp};
|
||||
use parity_dapps_glue::WebApp as NewWebApp;
|
||||
|
||||
mod cache;
|
||||
mod fs;
|
||||
@ -25,8 +26,8 @@ pub mod urlhint;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
|
||||
extern crate parity_dapps_status;
|
||||
extern crate parity_dapps_home;
|
||||
extern crate parity_ui;
|
||||
|
||||
pub const DAPPS_DOMAIN : &'static str = ".parity";
|
||||
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()))
|
||||
}
|
||||
|
||||
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
|
||||
let mut pages = fs::local_endpoints(dapps_path);
|
||||
// Home page needs to be safe embed
|
||||
// because we use Cross-Origin LocalStorage.
|
||||
// TODO [ToDr] Account naming should be moved to parity.
|
||||
pages.insert("home".into(), Box::new(
|
||||
PageEndpoint::new_safe_to_embed(parity_dapps_home::App::default())
|
||||
));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed());
|
||||
insert::<parity_dapps_status::App>(&mut pages, "parity");
|
||||
insert::<parity_dapps_status::App>(&mut pages, "status");
|
||||
|
||||
// Optional dapps
|
||||
wallet_page(&mut pages);
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
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
|
||||
}
|
||||
|
||||
#[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) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ impl URLHintContract {
|
||||
}
|
||||
|
||||
let mut it = vec.into_iter();
|
||||
let account_slash_repo = it.next().unwrap();
|
||||
let commit = it.next().unwrap();
|
||||
let owner = it.next().unwrap();
|
||||
let account_slash_repo = it.next().expect("element 0 of 3-len vector known to exist; qed");
|
||||
let commit = it.next().expect("element 1 of 3-len vector known to exist; qed");
|
||||
let owner = it.next().expect("element 2 of 3-len vector known to exist qed");
|
||||
|
||||
match (account_slash_repo, commit, owner) {
|
||||
(Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => {
|
||||
|
@ -19,57 +19,60 @@
|
||||
use std::io::Write;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::mime::Mime;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
use util::version;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContentHandler {
|
||||
code: StatusCode,
|
||||
content: String,
|
||||
mimetype: String,
|
||||
mimetype: Mime,
|
||||
write_pos: usize,
|
||||
safe_to_embed_at_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
pub fn ok(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code: StatusCode::Ok,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
pub fn ok(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::Ok, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn not_found(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
code: StatusCode::NotFound,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
pub fn not_found(content: String, mimetype: Mime) -> Self {
|
||||
Self::new(StatusCode::NotFound, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String) -> Self {
|
||||
Self::new(code, content, "text/html".into())
|
||||
pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at)
|
||||
}
|
||||
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> 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!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
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 {
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ impl server::Handler<HttpStream> for EchoHandler {
|
||||
|
||||
// Don't even read the payload if origin is forbidden!
|
||||
if let Cors::Forbidden = self.cors {
|
||||
self.handler = Some(ContentHandler::ok(String::new(), "text/plain".into()));
|
||||
self.handler = Some(ContentHandler::ok(String::new(), mime!(Text/Plain)));
|
||||
Next::write()
|
||||
} else {
|
||||
Next::read()
|
||||
@ -92,7 +92,7 @@ impl server::Handler<HttpStream> for EchoHandler {
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
match decoder.read_to_string(&mut self.content) {
|
||||
Ok(0) => {
|
||||
self.handler = Some(ContentHandler::ok(self.content.clone(), "application/json".into()));
|
||||
self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
|
||||
Next::write()
|
||||
},
|
||||
Ok(_) => Next::read(),
|
||||
@ -114,11 +114,15 @@ impl server::Handler<HttpStream> for EchoHandler {
|
||||
]));
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,24 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
|
||||
use url::Url;
|
||||
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> {
|
||||
match *req.uri() {
|
||||
uri::RequestUri::AbsoluteUri(ref url) => {
|
||||
|
@ -43,10 +43,8 @@
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate url as url_lib;
|
||||
extern crate hyper;
|
||||
extern crate url as url_lib;
|
||||
extern crate unicase;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
@ -57,14 +55,24 @@ extern crate jsonrpc_core;
|
||||
extern crate jsonrpc_http_server;
|
||||
extern crate mime_guess;
|
||||
extern crate rustc_serialize;
|
||||
extern crate parity_dapps;
|
||||
extern crate ethcore_rpc;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate linked_hash_map;
|
||||
extern crate fetch;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate mime;
|
||||
|
||||
#[cfg(test)]
|
||||
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 apps;
|
||||
mod page;
|
||||
@ -92,11 +100,11 @@ static DAPPS_DOMAIN : &'static str = ".parity";
|
||||
/// Indicates sync status
|
||||
pub trait SyncStatus: Send + Sync {
|
||||
/// 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 {
|
||||
fn is_major_syncing(&self) -> bool { self() }
|
||||
fn is_major_importing(&self) -> bool { self() }
|
||||
}
|
||||
|
||||
/// Webapps HTTP+RPC server build.
|
||||
@ -105,6 +113,7 @@ pub struct ServerBuilder {
|
||||
handler: Arc<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
signer_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl Extendable for ServerBuilder {
|
||||
@ -121,6 +130,7 @@ impl ServerBuilder {
|
||||
handler: Arc::new(IoHandler::new()),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(|| false),
|
||||
signer_port: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +139,11 @@ impl ServerBuilder {
|
||||
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,
|
||||
/// 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> {
|
||||
@ -138,6 +153,7 @@ impl ServerBuilder {
|
||||
NoAuth,
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
@ -152,6 +168,7 @@ impl ServerBuilder {
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
@ -186,13 +203,14 @@ impl Server {
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: String,
|
||||
signer_port: Option<u16>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status));
|
||||
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 mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
|
||||
|
@ -25,7 +25,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Prefix to strip from the path (when `None` deducted from `app_id`)
|
||||
pub prefix: Option<String>,
|
||||
/// Safe to be loaded in frame by other origin. (use wisely!)
|
||||
safe_to_embed: bool,
|
||||
safe_to_embed_at_port: Option<u16>,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed: false,
|
||||
safe_to_embed_at_port: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed: false,
|
||||
safe_to_embed_at_port: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
/// Creates new `PageEndpoint` which can be safely used in iframe
|
||||
/// even from different origin. It might be dangerous (clickjacking).
|
||||
/// Use wisely!
|
||||
pub fn new_safe_to_embed(app: T) -> Self {
|
||||
pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed: true,
|
||||
safe_to_embed_at_port: port,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -79,8 +79,8 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: Default::default(),
|
||||
safe_to_embed: self.safe_to_embed,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
|
||||
safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
use hyper::{Decoder, Encoder, Next};
|
||||
use endpoint::EndpointPath;
|
||||
use handlers::ContentHandler;
|
||||
use handlers::{ContentHandler, add_security_headers};
|
||||
|
||||
/// Represents a file that can be sent to client.
|
||||
/// Implementation should keep track of bytes already sent internally.
|
||||
@ -57,13 +57,14 @@ pub enum ServedFile<T: Dapp> {
|
||||
Error(ContentHandler),
|
||||
}
|
||||
|
||||
impl<T: Dapp> Default for ServedFile<T> {
|
||||
fn default() -> Self {
|
||||
ServedFile::Error(ContentHandler::error(
|
||||
impl<T: Dapp> ServedFile<T> {
|
||||
pub fn new(embeddable_at: Option<u16>) -> Self {
|
||||
ServedFile::Error(ContentHandler::error_embeddable(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested dapp resource was not found.",
|
||||
None
|
||||
None,
|
||||
embeddable_at,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -81,7 +82,7 @@ pub struct PageHandler<T: Dapp> {
|
||||
/// Requested path.
|
||||
pub path: EndpointPath,
|
||||
/// 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> {
|
||||
@ -115,7 +116,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
self.app.file(&self.extract_path(url.path()))
|
||||
},
|
||||
_ => 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()
|
||||
}
|
||||
|
||||
@ -127,10 +128,14 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
match self.file {
|
||||
ServedFile::File(ref f) => {
|
||||
res.set_status(StatusCode::Ok);
|
||||
res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
|
||||
if !self.safe_to_embed {
|
||||
res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
|
||||
|
||||
match f.content_type().parse() {
|
||||
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()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
@ -212,8 +217,8 @@ fn should_extract_path_with_appid() {
|
||||
port: 8080,
|
||||
using_dapps_domains: true,
|
||||
},
|
||||
file: Default::default(),
|
||||
safe_to_embed: true,
|
||||
file: ServedFile::new(None),
|
||||
safe_to_embed_at_port: None,
|
||||
};
|
||||
|
||||
// when
|
||||
|
@ -61,16 +61,16 @@ impl Endpoint for LocalPageEndpoint {
|
||||
app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: Default::default(),
|
||||
safe_to_embed: false,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: None,
|
||||
})
|
||||
} else {
|
||||
Box::new(handler::PageHandler {
|
||||
app: LocalDapp { path: self.path.clone() },
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: Default::default(),
|
||||
safe_to_embed: false,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ function FindProxyForURL(url, host) {{
|
||||
}}
|
||||
"#,
|
||||
DAPPS_DOMAIN, path.host, path.port);
|
||||
Box::new(ContentHandler::ok(content, "application/javascript".to_owned()))
|
||||
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,11 +81,15 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
self.handler = match endpoint {
|
||||
// First check special endpoints
|
||||
(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
|
||||
(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
|
||||
(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
|
||||
_ => {
|
||||
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>>,
|
||||
) -> 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 {
|
||||
control: Some(control),
|
||||
main_page: main_page,
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve, serve_with_registrar, request};
|
||||
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
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.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_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -58,6 +59,7 @@ fn should_serve_apps() {
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json");
|
||||
assert!(response.body.contains("Parity Home Screen"), response.body);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -80,6 +82,7 @@ fn should_handle_ping() {
|
||||
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.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
||||
@ -101,5 +104,6 @@ fn should_try_to_resolve_dapp() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_with_auth, request};
|
||||
use tests::helpers::{serve_with_auth, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_require_authorization() {
|
||||
@ -76,4 +76,5 @@ fn should_allow_on_valid_auth() {
|
||||
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve_with_registrar, request};
|
||||
use tests::helpers::{serve_with_registrar, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_resolve_dapp() {
|
||||
@ -34,5 +34,6 @@ fn should_resolve_dapp() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert_eq!(registrar.calls.lock().len(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
@ -92,3 +92,7 @@ pub fn serve() -> Server {
|
||||
pub fn request(server: Server, request: &str) -> http_client::Response {
|
||||
http_client::request(server.addr(), request)
|
||||
}
|
||||
|
||||
pub fn assert_security_headers(headers: &[String]) {
|
||||
http_client::assert_security_headers_present(headers)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve, request};
|
||||
use tests::helpers::{serve, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home() {
|
||||
@ -74,6 +74,7 @@ fn should_display_404_on_invalid_dapp() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert!(response.body.contains("href=\"/home/"));
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -94,6 +95,7 @@ fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
|
||||
assert!(response.body.contains("href=\"http://home.parity/"));
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -160,6 +162,7 @@ fn should_serve_proxy_pac() {
|
||||
// then
|
||||
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_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -181,5 +184,6 @@ fn should_serve_utils() {
|
||||
// then
|
||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||
assert_eq!(response.body.contains("function(){"), true);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
19
dapps/ui/Cargo.toml
Normal file
19
dapps/ui/Cargo.toml
Normal 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
22
dapps/ui/build.rs
Normal 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
22
dapps/ui/src/lib.rs
Normal 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
55
dapps/ui/src/lib.rs.in
Normal 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"));
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
39
docker/ubuntu-stable/Dockerfile
Normal file
39
docker/ubuntu-stable/Dockerfile
Normal 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"]
|
@ -9,4 +9,4 @@ authors = ["arkpar <arkadiy@ethcore.io"]
|
||||
log = "0.3"
|
||||
sha3 = { path = "../util/sha3" }
|
||||
primal = "0.2.3"
|
||||
parking_lot = "0.2.6"
|
||||
parking_lot = "0.3"
|
||||
|
@ -37,7 +37,7 @@ ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
rand = "0.3"
|
||||
lru-cache = "0.0.7"
|
||||
lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
byteorder = "0.5"
|
||||
|
||||
|
@ -10,7 +10,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30"
|
||||
"homesteadTransition": "0x118c30",
|
||||
"eip150Transition": "0x2625a0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
43
ethcore/res/ethereum/eip150_test.json
Normal file
43
ethcore/res/ethereum/eip150_test.json
Normal 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 } } } }
|
||||
}
|
||||
}
|
@ -11,10 +11,11 @@
|
||||
"durationLimit": "0x3C",
|
||||
"blockReward": "0x6f05b59d3b200000",
|
||||
"registrar" : "0x6c221ca53705f3497ec90ca7b84c59ae7382fc21",
|
||||
"frontierCompatibilityModeLimit": "0x30d40",
|
||||
"homesteadTransition": "0x30d40",
|
||||
"difficultyHardforkTransition": "0x59d9",
|
||||
"difficultyHardforkBoundDivisor": "0x0200",
|
||||
"bombDefuseTransition": "0x30d40"
|
||||
"bombDefuseTransition": "0x30d40",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -8,8 +8,8 @@
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0x259518"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -163,7 +164,8 @@
|
||||
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
||||
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50: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": {
|
||||
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
@ -9,7 +9,7 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0xffffffffffffffff"
|
||||
"homesteadTransition": "0x7fffffffffffffff",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x0"
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
|
||||
"frontierCompatibilityModeLimit": "0x789b0"
|
||||
"homesteadTransition": "0x789b0",
|
||||
"eip150Transition": "0x1b34d8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -17,7 +18,9 @@
|
||||
"accountStartNonce": "0x0100000",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x2"
|
||||
"networkID" : "0x2",
|
||||
"forkBlock": "0x1b34d8",
|
||||
"forkCanonHash": "0xf376243aeff1f256d970714c3de9fd78fa4e63cf63e32a51fe1169e375d98145"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
|
@ -8,7 +8,9 @@
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x08",
|
||||
"blockReward": "0x14D1120D7B160000",
|
||||
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050"
|
||||
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050",
|
||||
"homesteadTransition": "0x7fffffffffffffff",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 8f07dbc8294a32db5ebe8098925fcefc2eab3e71
|
||||
Subproject commit 97066e40ccd061f727deb5cd860e4d9135aa2551
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "DAO hard-fork consensus test",
|
||||
"name": "EIP150.1b hard-fork consensus test",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
@ -9,7 +9,7 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x5",
|
||||
"homesteadTransition": "0x5",
|
||||
"daoHardforkTransition": "0x8",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0xa"
|
||||
}
|
||||
}
|
||||
},
|
@ -23,7 +23,7 @@ use std::time::{Instant, Duration};
|
||||
use util::{Mutex, RwLock};
|
||||
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
|
||||
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;
|
||||
pub use ethstore::ethkey::Signature;
|
||||
|
||||
@ -176,15 +176,23 @@ impl AccountProvider {
|
||||
AccountProvider {
|
||||
unlocked: Mutex::new(HashMap::new()),
|
||||
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.
|
||||
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));
|
||||
Ok(address)
|
||||
Ok((address, public))
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
@ -280,6 +288,21 @@ impl AccountProvider {
|
||||
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.
|
||||
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Perm)
|
||||
@ -301,51 +324,16 @@ impl AccountProvider {
|
||||
unlocked.get(&account).is_some()
|
||||
}
|
||||
|
||||
/// Signs the message. Account must be unlocked.
|
||||
pub fn sign(&self, account: Address, message: Message) -> Result<Signature, Error> {
|
||||
let data = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
data
|
||||
};
|
||||
|
||||
let signature = try!(self.sstore.sign(&account, &data.password, &message));
|
||||
Ok(signature)
|
||||
/// Signs the message. If password is not provided the account must be unlocked.
|
||||
pub fn sign(&self, account: Address, password: Option<String>, message: Message) -> Result<Signature, Error> {
|
||||
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
||||
Ok(try!(self.sstore.sign(&account, &password, &message)))
|
||||
}
|
||||
|
||||
/// Decrypts a message. Account must be unlocked.
|
||||
pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let data = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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)
|
||||
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||
pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
||||
Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message)))
|
||||
}
|
||||
|
||||
/// 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.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
||||
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(), Default::default()).is_err());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -397,11 +385,11 @@ mod tests {
|
||||
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(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), 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.sign(kp.address(), None, Default::default()).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(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -411,8 +399,8 @@ mod tests {
|
||||
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(), "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));
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_err());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ impl<'x> OpenBlock<'x> {
|
||||
let t = outcome.trace;
|
||||
self.block.traces.as_mut().map(|traces| traces.push(t));
|
||||
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))
|
||||
}
|
||||
@ -404,6 +404,10 @@ impl<'x> OpenBlock<'x> {
|
||||
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> {
|
||||
|
@ -29,3 +29,12 @@ pub struct BestBlock {
|
||||
/// Best block uncompressed 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,
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ use log_entry::{LogEntry, LocalizedLogEntry};
|
||||
use receipt::Receipt;
|
||||
use blooms::{Bloom, BloomGroup};
|
||||
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 blockchain::update::ExtrasUpdate;
|
||||
use blockchain::{CacheSize, ImportRoute, Config};
|
||||
@ -43,16 +44,24 @@ pub trait BlockProvider {
|
||||
/// (though not necessarily a part of the canon chain).
|
||||
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
|
||||
/// succeed.
|
||||
fn first_block(&self) -> H256;
|
||||
fn first_block(&self) -> Option<H256>;
|
||||
|
||||
/// Get the number of the first block.
|
||||
fn first_block_number(&self) -> BlockNumber {
|
||||
self.block_number(&self.first_block()).expect("First block always stored; qed")
|
||||
fn first_block_number(&self) -> Option<BlockNumber> {
|
||||
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
|
||||
fn block(&self, hash: &H256) -> Option<Bytes>;
|
||||
|
||||
@ -123,7 +132,8 @@ pub trait BlockProvider {
|
||||
|
||||
/// Returns the header of the genesis block.
|
||||
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.
|
||||
@ -160,9 +170,14 @@ impl bc::group::BloomGroupDatabase for BlockChain {
|
||||
pub struct BlockChain {
|
||||
// All locks must be captured in the order declared here.
|
||||
blooms_config: bc::Config,
|
||||
first_block: H256,
|
||||
|
||||
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_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)
|
||||
}
|
||||
|
||||
fn first_block(&self) -> H256 {
|
||||
self.first_block
|
||||
fn first_block(&self) -> Option<H256> {
|
||||
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
|
||||
@ -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, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_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());
|
||||
|
||||
let receipts_len = receipts.len();
|
||||
@ -397,8 +423,9 @@ impl BlockChain {
|
||||
levels: LOG_BLOOMS_LEVELS,
|
||||
elements_per_index: LOG_BLOOMS_ELEMENTS_PER_INDEX,
|
||||
},
|
||||
first_block: H256::zero(),
|
||||
first_block: None,
|
||||
best_block: RwLock::new(BestBlock::default()),
|
||||
best_ancient_block: RwLock::new(None),
|
||||
block_headers: RwLock::new(HashMap::new()),
|
||||
block_bodies: 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.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?");
|
||||
hash
|
||||
}
|
||||
@ -452,32 +478,45 @@ impl BlockChain {
|
||||
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 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.
|
||||
if raw_first.is_empty() {
|
||||
let (mut f, mut hash) = (best_block_number, best_block_hash);
|
||||
let mut l = 0;
|
||||
match raw_first {
|
||||
None => {
|
||||
let (mut f, mut hash) = (best_block_number, best_block_hash);
|
||||
let mut l = best_ancient_number.unwrap_or(0);
|
||||
|
||||
loop {
|
||||
if l >= f { break; }
|
||||
loop {
|
||||
if l >= f { break; }
|
||||
|
||||
let step = (f - l) >> 1;
|
||||
let m = l + step;
|
||||
let step = (f - l) >> 1;
|
||||
let m = l + step;
|
||||
|
||||
match bc.block_hash(m) {
|
||||
Some(h) => { f = m; hash = h },
|
||||
None => { l = m + 1 },
|
||||
match bc.block_hash(m) {
|
||||
Some(h) => { f = m; hash = h },
|
||||
None => { l = m + 1 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut batch = db.transaction();
|
||||
batch.put(db::COL_EXTRA, b"first", &hash);
|
||||
db.write(batch).expect("Low level database error.");
|
||||
|
||||
bc.first_block = hash;
|
||||
} else {
|
||||
bc.first_block = H256::from_slice(&raw_first);
|
||||
if hash != bc.genesis_hash() {
|
||||
trace!("First block calculated: {:?}", hash);
|
||||
let mut batch = db.transaction();
|
||||
batch.put(db::COL_EXTRA, b"first", &hash);
|
||||
db.write(batch).expect("Low level database error.");
|
||||
bc.first_block = Some(hash);
|
||||
}
|
||||
},
|
||||
Some(raw_first) => {
|
||||
bc.first_block = Some(H256::from_slice(&raw_first));
|
||||
},
|
||||
}
|
||||
|
||||
// and write them
|
||||
@ -488,6 +527,14 @@ impl BlockChain {
|
||||
hash: best_block_hash,
|
||||
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
|
||||
@ -641,11 +688,12 @@ impl BlockChain {
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// 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 header = block.header_view();
|
||||
let hash = header.sha3();
|
||||
@ -656,8 +704,6 @@ impl BlockChain {
|
||||
|
||||
assert!(self.pending_best_block.read().is_none());
|
||||
|
||||
let mut batch = self.db.transaction();
|
||||
|
||||
let block_rlp = UntrustedRlp::new(bytes);
|
||||
let compressed_header = block_rlp.at(0).unwrap().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 {
|
||||
// parent known to be in chain.
|
||||
let info = BlockInfo {
|
||||
hash: hash,
|
||||
hash: hash.clone(),
|
||||
number: header.number(),
|
||||
total_difficulty: parent_details.total_difficulty + header.difficulty(),
|
||||
location: BlockLocation::CanonChain,
|
||||
};
|
||||
|
||||
self.prepare_update(&mut batch, ExtrasUpdate {
|
||||
self.prepare_update(batch, ExtrasUpdate {
|
||||
block_hashes: self.prepare_block_hashes_update(bytes, &info),
|
||||
block_details: self.prepare_block_details_update(bytes, &info),
|
||||
block_receipts: self.prepare_block_receipts_update(receipts, &info),
|
||||
@ -686,7 +732,21 @@ impl BlockChain {
|
||||
info: info,
|
||||
block: bytes
|
||||
}, 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
|
||||
} else {
|
||||
@ -711,7 +771,7 @@ impl BlockChain {
|
||||
let mut update = HashMap::new();
|
||||
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_details: update,
|
||||
block_receipts: self.prepare_block_receipts_update(receipts, &info),
|
||||
@ -720,8 +780,6 @@ impl BlockChain {
|
||||
info: info,
|
||||
block: bytes,
|
||||
}, is_best);
|
||||
self.db.write(batch).unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -1207,6 +1265,24 @@ impl BlockChain {
|
||||
body.append_raw(block_rlp.at(2).as_raw(), 1);
|
||||
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)]
|
||||
|
@ -55,18 +55,23 @@ impl<T> CacheManager<T> where T: Eq + Hash {
|
||||
}
|
||||
|
||||
for _ in 0..COLLECTION_QUEUE_SIZE {
|
||||
let current_size = notify_unused(self.cache_usage.pop_back().unwrap());
|
||||
self.cache_usage.push_front(Default::default());
|
||||
if current_size < self.max_cache_size {
|
||||
break;
|
||||
if let Some(back) = self.cache_usage.pop_back() {
|
||||
let current_size = notify_unused(back);
|
||||
self.cache_usage.push_front(Default::default());
|
||||
if current_size < self.max_cache_size {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let cache = self.cache_usage.pop_back().unwrap();
|
||||
self.cache_usage.push_front(cache);
|
||||
if let Some(cache) = self.cache_usage.pop_back() {
|
||||
self.cache_usage.push_front(cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ use util::kvdb::*;
|
||||
|
||||
// other
|
||||
use io::*;
|
||||
use views::{HeaderView, BodyView};
|
||||
use views::{HeaderView, BodyView, BlockView};
|
||||
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
|
||||
use header::BlockNumber;
|
||||
use state::State;
|
||||
@ -46,7 +46,7 @@ use transaction::{LocalizedTransaction, SignedTransaction, Action};
|
||||
use blockchain::extras::TransactionAddress;
|
||||
use types::filter::Filter;
|
||||
use log_entry::LocalizedLogEntry;
|
||||
use verification::queue::{BlockQueue, QueueInfo as BlockQueueInfo};
|
||||
use verification::queue::BlockQueue;
|
||||
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
|
||||
use client::{
|
||||
BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient,
|
||||
@ -71,9 +71,11 @@ use state_db::StateDB;
|
||||
pub use types::blockchain_info::BlockChainInfo;
|
||||
pub use types::block_status::BlockStatus;
|
||||
pub use blockchain::CacheSize as BlockChainCacheSize;
|
||||
pub use verification::queue::QueueInfo as BlockQueueInfo;
|
||||
|
||||
const MAX_TX_QUEUE_SIZE: usize = 4096;
|
||||
const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2;
|
||||
const MIN_HISTORY_SIZE: u64 = 8;
|
||||
|
||||
impl fmt::Display for BlockChainInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
@ -141,12 +143,9 @@ pub struct Client {
|
||||
queue_transactions: AtomicUsize,
|
||||
last_hashes: RwLock<VecDeque<H256>>,
|
||||
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 {
|
||||
/// Create a new client with given spec and DB path and custom verifier.
|
||||
pub fn new(
|
||||
@ -170,13 +169,35 @@ impl Client {
|
||||
};
|
||||
|
||||
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)) {
|
||||
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));
|
||||
}
|
||||
|
||||
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())) {
|
||||
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 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),
|
||||
accountdb: Default::default(),
|
||||
};
|
||||
@ -217,6 +238,7 @@ impl Client {
|
||||
queue_transactions: AtomicUsize::new(0),
|
||||
last_hashes: RwLock::new(VecDeque::new()),
|
||||
factories: factories,
|
||||
history: history,
|
||||
};
|
||||
Ok(Arc::new(client))
|
||||
}
|
||||
@ -275,7 +297,7 @@ impl Client {
|
||||
let chain = self.chain.read();
|
||||
// Check the block isn't so old we won't be able to enact it.
|
||||
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);
|
||||
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
|
||||
pub fn import_verified_blocks(&self) -> usize {
|
||||
let max_blocks_to_import = 64;
|
||||
let (imported_blocks, import_results, invalid_blocks, imported, duration) = {
|
||||
let max_blocks_to_import = 4;
|
||||
let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = {
|
||||
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
|
||||
let mut invalid_blocks = HashSet::new();
|
||||
let mut import_results = Vec::with_capacity(max_blocks_to_import);
|
||||
|
||||
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 start = precise_time_ns();
|
||||
let blocks = self.block_queue.drain(max_blocks_to_import);
|
||||
|
||||
for block in blocks {
|
||||
let header = &block.header;
|
||||
@ -373,23 +398,19 @@ impl Client {
|
||||
let imported = imported_blocks.len();
|
||||
let invalid_blocks = invalid_blocks.into_iter().collect::<Vec<H256>>();
|
||||
|
||||
{
|
||||
if !invalid_blocks.is_empty() {
|
||||
self.block_queue.mark_as_bad(&invalid_blocks);
|
||||
}
|
||||
if !imported_blocks.is_empty() {
|
||||
self.block_queue.mark_as_good(&imported_blocks);
|
||||
}
|
||||
if !invalid_blocks.is_empty() {
|
||||
self.block_queue.mark_as_bad(&invalid_blocks);
|
||||
}
|
||||
let is_empty = self.block_queue.mark_as_good(&imported_blocks);
|
||||
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);
|
||||
|
||||
if self.queue_info().is_empty() {
|
||||
if is_empty {
|
||||
self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted);
|
||||
}
|
||||
|
||||
@ -410,17 +431,33 @@ impl Client {
|
||||
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 {
|
||||
let number = block.header().number();
|
||||
let parent = block.header().parent_hash().clone();
|
||||
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
|
||||
let receipts = block.receipts().to_owned();
|
||||
@ -436,7 +473,17 @@ impl Client {
|
||||
// already-imported block of the same number.
|
||||
// TODO: Prove it with a test.
|
||||
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);
|
||||
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
||||
@ -446,6 +493,7 @@ impl Client {
|
||||
enacted: route.enacted.clone(),
|
||||
retracted: route.retracted.len()
|
||||
});
|
||||
|
||||
let is_canon = route.enacted.last().map_or(false, |h| h == hash);
|
||||
state.sync_cache(&route.enacted, &route.retracted, is_canon);
|
||||
// Final commit to the DB
|
||||
@ -501,7 +549,7 @@ impl Client {
|
||||
let db = self.state_db.lock().boxed_clone();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -606,20 +654,23 @@ impl Client {
|
||||
let best_block_number = self.chain_info().best_block_number;
|
||||
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());
|
||||
}
|
||||
|
||||
let history = ::std::cmp::min(self.history, 1000);
|
||||
|
||||
let start_hash = match at {
|
||||
BlockID::Latest => {
|
||||
let start_num = if best_block_number > 1000 {
|
||||
best_block_number - 1000
|
||||
} else {
|
||||
0
|
||||
let start_num = match db.earliest_era() {
|
||||
Some(era) => ::std::cmp::max(era, best_block_number - history),
|
||||
None => best_block_number - history,
|
||||
};
|
||||
|
||||
self.block_hash(BlockID::Number(start_num))
|
||||
.expect("blocks within HISTORY are always stored.")
|
||||
match self.block_hash(BlockID::Number(start_num)) {
|
||||
Some(h) => h,
|
||||
None => return Err(snapshot::Error::InvalidStartingBlock(at).into()),
|
||||
}
|
||||
}
|
||||
_ => match self.block_hash(at) {
|
||||
Some(hash) => hash,
|
||||
@ -632,6 +683,11 @@ impl Client {
|
||||
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> {
|
||||
match id {
|
||||
BlockID::Hash(hash) => Some(hash),
|
||||
@ -688,7 +744,8 @@ impl snapshot::DatabaseRestore for Client {
|
||||
let db = self.db.write();
|
||||
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()));
|
||||
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
|
||||
Ok(())
|
||||
@ -974,6 +1031,20 @@ impl BlockChainClient for Client {
|
||||
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 {
|
||||
self.block_queue.queue_info()
|
||||
}
|
||||
@ -983,14 +1054,7 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
let chain = self.chain.read();
|
||||
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())
|
||||
}
|
||||
self.chain.read().chain_info()
|
||||
}
|
||||
|
||||
fn additional_params(&self) -> BTreeMap<String, String> {
|
||||
@ -1120,21 +1184,22 @@ impl MiningBlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let _timer = PerfTimer::new("import_sealed_block");
|
||||
let start = precise_time_ns();
|
||||
|
||||
let h = block.header().hash();
|
||||
let number = block.header().number();
|
||||
|
||||
let block_data = block.rlp_bytes();
|
||||
let route = self.commit_block(block, &h, &block_data);
|
||||
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
||||
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
||||
let start = precise_time_ns();
|
||||
let route = {
|
||||
// scope for self.import_lock
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let _timer = PerfTimer::new("import_sealed_block");
|
||||
|
||||
let number = block.header().number();
|
||||
let block_data = block.rlp_bytes();
|
||||
let route = self.commit_block(block, &h, &block_data);
|
||||
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
||||
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
||||
route
|
||||
};
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(&[route]);
|
||||
self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted);
|
||||
|
||||
self.notify(|notify| {
|
||||
notify.new_blocks(
|
||||
vec![h.clone()],
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::path::Path;
|
||||
pub use std::time::Duration;
|
||||
pub use blockchain::Config as BlockChainConfig;
|
||||
pub use trace::Config as TraceConfig;
|
||||
@ -26,23 +27,26 @@ use util::{journaldb, CompactionProfile};
|
||||
/// Client state db compaction profile
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DatabaseCompactionProfile {
|
||||
/// Default compaction profile
|
||||
Default,
|
||||
/// Try to determine compaction profile automatically
|
||||
Auto,
|
||||
/// SSD compaction profile
|
||||
SSD,
|
||||
/// HDD or other slow storage io compaction profile
|
||||
HDD,
|
||||
}
|
||||
|
||||
impl Default for DatabaseCompactionProfile {
|
||||
fn default() -> Self {
|
||||
DatabaseCompactionProfile::Default
|
||||
DatabaseCompactionProfile::Auto
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseCompactionProfile {
|
||||
/// Returns corresponding compaction profile.
|
||||
pub fn compaction_profile(&self) -> CompactionProfile {
|
||||
pub fn compaction_profile(&self, db_path: &Path) -> CompactionProfile {
|
||||
match *self {
|
||||
DatabaseCompactionProfile::Default => Default::default(),
|
||||
DatabaseCompactionProfile::Auto => CompactionProfile::auto(db_path),
|
||||
DatabaseCompactionProfile::SSD => CompactionProfile::ssd(),
|
||||
DatabaseCompactionProfile::HDD => CompactionProfile::hdd(),
|
||||
}
|
||||
}
|
||||
@ -53,9 +57,10 @@ impl FromStr for DatabaseCompactionProfile {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ssd" | "default" => Ok(DatabaseCompactionProfile::Default),
|
||||
"auto" => Ok(DatabaseCompactionProfile::Auto),
|
||||
"ssd" => Ok(DatabaseCompactionProfile::SSD),
|
||||
"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,
|
||||
/// The name of the client instance.
|
||||
pub name: String,
|
||||
/// State db cache-size if not default
|
||||
/// RocksDB state column cache-size if not default
|
||||
pub db_cache_size: Option<usize>,
|
||||
/// State db compaction profile
|
||||
pub db_compaction: DatabaseCompactionProfile,
|
||||
@ -106,6 +111,12 @@ pub struct ClientConfig {
|
||||
pub mode: Mode,
|
||||
/// Type of block verifier used by client.
|
||||
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)]
|
||||
@ -114,13 +125,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_default_compaction_profile() {
|
||||
assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Default);
|
||||
assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Auto);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing_compaction_profile() {
|
||||
assert_eq!(DatabaseCompactionProfile::Default, "ssd".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::Default, "default".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::Auto, "auto".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::SSD, "ssd".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::HDD, "hdd".parse().unwrap());
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ impl TestBlockChainClient {
|
||||
queue_size: AtomicUsize::new(0),
|
||||
miner: Arc::new(Miner::with_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),
|
||||
};
|
||||
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 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 state_db = StateDB::new(journal_db);
|
||||
let state_db = StateDB::new(journal_db, 1024 * 1024);
|
||||
GuardedTempResult {
|
||||
_temp: temp,
|
||||
result: Some(state_db)
|
||||
@ -570,6 +570,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn import_block_with_receipts(&self, b: Bytes, _r: Bytes) -> Result<H256, BlockImportError> {
|
||||
self.import_block(b)
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> QueueInfo {
|
||||
QueueInfo {
|
||||
verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed),
|
||||
@ -595,6 +599,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
genesis_hash: self.genesis_hash.clone(),
|
||||
best_block_hash: self.last_hash.read().clone(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +139,9 @@ pub trait BlockChainClient : Sync + Send {
|
||||
/// Import a block into the blockchain.
|
||||
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.
|
||||
fn queue_info(&self) -> BlockQueueInfo;
|
||||
|
||||
|
@ -197,7 +197,7 @@ impl Engine for AuthorityRound {
|
||||
if self.is_step_proposer(step, header.author()) {
|
||||
if let Some(ap) = accounts {
|
||||
// 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()]);
|
||||
} else {
|
||||
trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable");
|
||||
|
@ -112,7 +112,7 @@ impl Engine for BasicAuthority {
|
||||
let header = block.header();
|
||||
let message = header.bare_hash();
|
||||
// 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()]);
|
||||
} else {
|
||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
||||
|
@ -127,10 +127,14 @@ pub trait Engine : Sync + Send {
|
||||
fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) }
|
||||
/// Determine the code execution cost of the builtin contract with address `a`.
|
||||
/// 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`.
|
||||
/// 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.
|
||||
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
|
||||
|
@ -41,7 +41,7 @@ pub struct EthashParams {
|
||||
/// Namereg contract address.
|
||||
pub registrar: Address,
|
||||
/// Homestead transition block number.
|
||||
pub frontier_compatibility_mode_limit: u64,
|
||||
pub homestead_transition: u64,
|
||||
/// DAO hard-fork transition block (X).
|
||||
pub dao_hardfork_transition: u64,
|
||||
/// DAO hard-fork refund contract address (C).
|
||||
@ -54,6 +54,8 @@ pub struct EthashParams {
|
||||
pub difficulty_hardfork_bound_divisor: U256,
|
||||
/// Block on which there is no additional difficulty from the exponential bomb.
|
||||
pub bomb_defuse_transition: u64,
|
||||
/// Bad gas transition block number.
|
||||
pub eip150_transition: u64,
|
||||
}
|
||||
|
||||
impl From<ethjson::spec::EthashParams> for EthashParams {
|
||||
@ -66,13 +68,14 @@ impl From<ethjson::spec::EthashParams> for EthashParams {
|
||||
duration_limit: p.duration_limit.into(),
|
||||
block_reward: p.block_reward.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_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(),
|
||||
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),
|
||||
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 {
|
||||
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()
|
||||
} else {
|
||||
} else if env_info.number < self.ethash_params.eip150_transition {
|
||||
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> {
|
||||
if header.number() >= self.ethash_params.frontier_compatibility_mode_limit {
|
||||
if header.number() >= self.ethash_params.homestead_transition {
|
||||
try!(t.check_low_s());
|
||||
}
|
||||
Ok(())
|
||||
@ -290,7 +295,7 @@ impl Ethash {
|
||||
false => self.ethash_params.difficulty_bound_divisor,
|
||||
};
|
||||
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 {
|
||||
if header.timestamp() >= parent.timestamp() + duration_limit {
|
||||
|
@ -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.
|
||||
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.
|
||||
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.
|
||||
pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) }
|
||||
|
@ -118,11 +118,12 @@ impl Factory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new instance of specific `VMType` factory
|
||||
pub fn new(evm: VMType) -> Self {
|
||||
/// Create new instance of specific `VMType` factory, with a size in bytes
|
||||
/// for caching jump destinations.
|
||||
pub fn new(evm: VMType, cache_size: usize) -> Self {
|
||||
Factory {
|
||||
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]
|
||||
#[cfg(feature = "jit")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
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) => {
|
||||
#[test]
|
||||
#[cfg(feature = "jit")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
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 = "ignored-tests")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(feature = "ignored-tests")]
|
||||
fn $name_int() {
|
||||
$name_test(Factory::new(VMType::Interpreter));
|
||||
$name_test(Factory::new(VMType::Interpreter, 1024 * 32));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ use super::u256_to_address;
|
||||
use evm::{self, CostType};
|
||||
use evm::instructions::{self, Instruction, InstructionInfo};
|
||||
use evm::interpreter::stack::Stack;
|
||||
use evm::schedule::Schedule;
|
||||
|
||||
macro_rules! overflowing {
|
||||
($x: expr) => {{
|
||||
@ -31,7 +32,7 @@ macro_rules! overflowing {
|
||||
#[cfg_attr(feature="dev", allow(enum_variant_names))]
|
||||
enum InstructionCost<Cost: CostType> {
|
||||
Gas(Cost),
|
||||
GasMem(Cost, Cost),
|
||||
GasMem(Cost, Cost, Option<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))]
|
||||
/// 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(
|
||||
&mut self,
|
||||
ext: &evm::Ext,
|
||||
@ -64,7 +95,7 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
info: &InstructionInfo,
|
||||
stack: &Stack<U256>,
|
||||
current_mem_size: usize,
|
||||
) -> evm::Result<(Gas, Gas, usize)> {
|
||||
) -> evm::Result<(Gas, Gas, usize, Option<Gas>)> {
|
||||
let schedule = ext.schedule();
|
||||
let tier = instructions::get_tier_idx(info.tier);
|
||||
let default_gas = Gas::from(schedule.tier_step_gas[tier]);
|
||||
@ -90,26 +121,42 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
instructions::SLOAD => {
|
||||
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 => {
|
||||
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 => {
|
||||
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 => {
|
||||
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 => {
|
||||
let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31));
|
||||
let words = w >> 5;
|
||||
let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words);
|
||||
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 => {
|
||||
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2))))
|
||||
},
|
||||
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 => {
|
||||
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 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 => {
|
||||
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(
|
||||
try!(mem_needed(stack.peek(5), stack.peek(6))),
|
||||
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));
|
||||
|
||||
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() {
|
||||
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 => {
|
||||
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(
|
||||
try!(mem_needed(stack.peek(4), stack.peek(5))),
|
||||
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 => {
|
||||
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)));
|
||||
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 => {
|
||||
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);
|
||||
InstructionCost::Gas(gas)
|
||||
},
|
||||
_ => InstructionCost::Gas(default_gas)
|
||||
_ => InstructionCost::Gas(default_gas),
|
||||
};
|
||||
|
||||
match cost {
|
||||
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 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) => {
|
||||
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 gas = overflowing!(gas.overflow_add(copy_gas));
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,6 @@ impl<'a> CodeReader<'a> {
|
||||
|
||||
enum InstructionResult<Gas> {
|
||||
Ok,
|
||||
UseAllGas,
|
||||
GasLeft(Gas),
|
||||
UnusedGas(Gas),
|
||||
JumpToPosition(U256),
|
||||
// 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 code = ¶ms.code.as_ref().unwrap();
|
||||
let code = ¶ms.code.as_ref().expect("exec always called with code; qed");
|
||||
let valid_jump_destinations = self.cache.jump_destinations(¶ms.code_hash, code);
|
||||
|
||||
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));
|
||||
|
||||
// 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.
|
||||
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
|
||||
let result = try!(self.exec_instruction(
|
||||
gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack
|
||||
gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, provided
|
||||
));
|
||||
|
||||
evm_debug!({ informant.after_instruction(instruction) });
|
||||
|
||||
if let InstructionResult::UnusedGas(ref gas) = result {
|
||||
gasometer.current_gas = gasometer.current_gas + *gas;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Advance
|
||||
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) => {
|
||||
let pos = try!(self.verify_jump(position, &valid_jump_destinations));
|
||||
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)));
|
||||
},
|
||||
InstructionResult::StopExecution => break,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
informant.done();
|
||||
@ -250,7 +243,8 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
ext: &mut evm::Ext,
|
||||
instruction: Instruction,
|
||||
code: &mut CodeReader,
|
||||
stack: &mut Stack<U256>
|
||||
stack: &mut Stack<U256>,
|
||||
provided: Option<Cost>
|
||||
) -> evm::Result<InstructionResult<Cost>> {
|
||||
match instruction {
|
||||
instructions::JUMP => {
|
||||
@ -275,31 +269,32 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
let endowment = stack.pop_back();
|
||||
let init_off = 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 can_create = ext.balance(¶ms.address) >= endowment && ext.depth() < ext.schedule().max_depth;
|
||||
|
||||
if !can_create {
|
||||
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 {
|
||||
ContractCreateResult::Created(address, gas_left) => {
|
||||
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 => {
|
||||
stack.push(U256::zero());
|
||||
// TODO [todr] Should we just StopExecution here?
|
||||
Ok(InstructionResult::UseAllGas)
|
||||
Ok(InstructionResult::Ok)
|
||||
}
|
||||
};
|
||||
},
|
||||
instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL => {
|
||||
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 = u256_to_address(&code_address);
|
||||
|
||||
@ -317,17 +312,17 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
// 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() {
|
||||
false => Cost::from(ext.schedule().call_stipend),
|
||||
true => Cost::from(0)
|
||||
true => Cost::from(0),
|
||||
});
|
||||
|
||||
// Get sender & receive addresses, check if we have balance
|
||||
let (sender_address, receive_address, has_balance, call_type) = match instruction {
|
||||
instructions::CALL => {
|
||||
let has_balance = ext.balance(¶ms.address) >= value.unwrap();
|
||||
let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed");
|
||||
(¶ms.address, &code_address, has_balance, CallType::Call)
|
||||
},
|
||||
instructions::CALLCODE => {
|
||||
let has_balance = ext.balance(¶ms.address) >= value.unwrap();
|
||||
let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed");
|
||||
(¶ms.address, ¶ms.address, has_balance, CallType::CallCode)
|
||||
},
|
||||
instructions::DELEGATECALL => (¶ms.sender, ¶ms.address, true, CallType::DelegateCall),
|
||||
|
@ -21,25 +21,66 @@ use util::sha3::*;
|
||||
use bit_set::BitSet;
|
||||
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 {
|
||||
jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>>
|
||||
jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>>,
|
||||
max_size: usize,
|
||||
cur_size: Mutex<usize>,
|
||||
}
|
||||
|
||||
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> {
|
||||
if code_hash == &SHA3_EMPTY {
|
||||
return Self::find_jump_destinations(code);
|
||||
}
|
||||
|
||||
if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
|
||||
return d.clone();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -57,15 +98,15 @@ impl SharedCache {
|
||||
}
|
||||
position += 1;
|
||||
}
|
||||
|
||||
jump_dests.shrink_to_fit();
|
||||
Arc::new(jump_dests)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SharedCache {
|
||||
fn default() -> SharedCache {
|
||||
SharedCache {
|
||||
jump_destinations: Mutex::new(LruCache::new(CACHE_CODE_ITEMS)),
|
||||
}
|
||||
fn default() -> Self {
|
||||
SharedCache::new(DEFAULT_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,7 +359,7 @@ impl evm::Evm for JitEvm {
|
||||
data.timestamp = ext.env_info().timestamp as i64;
|
||||
|
||||
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();
|
||||
|
||||
match res {
|
||||
|
@ -80,6 +80,19 @@ pub struct Schedule {
|
||||
pub tx_data_non_zero_gas: usize,
|
||||
/// Gas price for copying memory
|
||||
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 {
|
||||
@ -93,6 +106,49 @@ impl Schedule {
|
||||
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 {
|
||||
Schedule{
|
||||
exceptional_failed_code_deposit: efcd,
|
||||
@ -126,6 +182,12 @@ impl Schedule {
|
||||
tx_data_zero_gas: 4,
|
||||
tx_data_non_zero_gas: 68,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -817,7 +817,7 @@ fn test_signextend(factory: super::Factory) {
|
||||
|
||||
#[test] // JIT just returns out of gas
|
||||
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 mut params = ActionParams::default();
|
||||
|
@ -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",
|
||||
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());
|
||||
self.state.add_balance(&t.sender().unwrap(), &refund_value);
|
||||
let sender = match t.sender() {
|
||||
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);
|
||||
self.state.add_balance(&self.info.author, &fees_value);
|
||||
|
||||
@ -598,7 +606,7 @@ mod tests {
|
||||
#[test]
|
||||
// Tracing is not suported in JIT
|
||||
fn test_call_to_create() {
|
||||
let factory = Factory::new(VMType::Interpreter);
|
||||
let factory = Factory::new(VMType::Interpreter, 1024 * 32);
|
||||
|
||||
// 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: 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: 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: 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]
|
||||
fn test_create_contract() {
|
||||
// Tracing is not supported in JIT
|
||||
let factory = Factory::new(VMType::Interpreter);
|
||||
let factory = Factory::new(VMType::Interpreter, 1024 * 32);
|
||||
// code:
|
||||
//
|
||||
// 60 10 - push 16
|
||||
|
@ -199,8 +199,9 @@ impl Header {
|
||||
match &mut *hash {
|
||||
&mut Some(ref h) => h.clone(),
|
||||
hash @ &mut None => {
|
||||
*hash = Some(self.rlp_sha3(Seal::With));
|
||||
hash.as_ref().unwrap().clone()
|
||||
let h = self.rlp_sha3(Seal::With);
|
||||
*hash = Some(h.clone());
|
||||
h
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,8 +212,9 @@ impl Header {
|
||||
match &mut *hash {
|
||||
&mut Some(ref h) => h.clone(),
|
||||
hash @ &mut None => {
|
||||
*hash = Some(self.rlp_sha3(Seal::Without));
|
||||
hash.as_ref().unwrap().clone()
|
||||
let h = self.rlp_sha3(Seal::Without);
|
||||
*hash = Some(h.clone());
|
||||
h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,8 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
|
||||
let mut spec = match era {
|
||||
ChainEra::Frontier => ethereum::new_frontier_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.overwrite_genesis_params(genesis);
|
||||
@ -116,14 +117,38 @@ mod frontier_era_tests {
|
||||
declare_test!{BlockchainTests_RandomTests_bl201507071825GO, "BlockchainTests/RandomTests/bl201507071825GO"}
|
||||
}
|
||||
|
||||
mod daohardfork_tests {
|
||||
mod transition_tests {
|
||||
use tests::helpers::*;
|
||||
use super::json_chain_test;
|
||||
|
||||
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_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"}
|
||||
}
|
||||
|
43
ethcore/src/json_tests/eip150_state.rs
Normal file
43
ethcore/src/json_tests/eip150_state.rs
Normal 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"}
|
@ -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()));
|
||||
let info = From::from(vm.env);
|
||||
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 mut substate = Substate::new();
|
||||
|
@ -37,6 +37,6 @@ declare_test!{BlockchainTests_Homestead_bcUncleTest, "BlockchainTests/Homestead/
|
||||
declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"}
|
||||
declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"}
|
||||
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_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"}
|
||||
|
@ -23,4 +23,5 @@ mod state;
|
||||
mod chain;
|
||||
mod homestead_state;
|
||||
mod homestead_chain;
|
||||
mod eip150_state;
|
||||
mod trie;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -30,7 +30,7 @@ use transaction::{Action, SignedTransaction};
|
||||
use receipt::{Receipt, RichReceipt};
|
||||
use spec::Spec;
|
||||
use engines::Engine;
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionOrigin};
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
use miner::work_notify::WorkPoster;
|
||||
use client::TransactionImportResult;
|
||||
use miner::price_info::PriceInfo;
|
||||
@ -76,6 +76,8 @@ pub struct MinerOptions {
|
||||
pub tx_gas_limit: U256,
|
||||
/// Maximum size of the transaction queue.
|
||||
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.
|
||||
pub pending_set: PendingSet,
|
||||
/// 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_own_tx: true,
|
||||
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,
|
||||
reseal_min_period: Duration::from_secs(2),
|
||||
work_queue_size: 20,
|
||||
enable_resubmission: true,
|
||||
tx_queue_gas_limit: GasLimit::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,7 +136,7 @@ impl GasPriceCalibrator {
|
||||
let gas_per_tx: f32 = 21000.0;
|
||||
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)));
|
||||
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;
|
||||
} else {
|
||||
@ -212,7 +215,9 @@ impl Miner {
|
||||
GasLimit::Fixed(ref limit) => *limit,
|
||||
_ => !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 {
|
||||
transaction_queue: txq,
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
@ -282,6 +287,7 @@ impl Miner {
|
||||
trace!(target: "miner", "prepare_block: done recalibration.");
|
||||
}
|
||||
|
||||
let _timer = PerfTimer::new("prepare_block");
|
||||
let (transactions, mut open_block, original_work_hash) = {
|
||||
let transactions = {self.transaction_queue.lock().top_transactions()};
|
||||
let mut sealing_work = self.sealing_work.lock();
|
||||
@ -468,8 +474,8 @@ impl Miner {
|
||||
let mut queue = self.transaction_queue.lock();
|
||||
queue.set_gas_limit(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.
|
||||
queue.set_total_gas_limit(gas_limit << 1);
|
||||
// Set total tx queue gas limit to be 20x the block gas limit.
|
||||
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 import = self.add_transactions_to_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 {
|
||||
Ok(ref res) => {
|
||||
@ -869,7 +875,11 @@ impl MinerService for Miner {
|
||||
gas_used: receipt.gas_used - prev_gas,
|
||||
contract_address: match tx.action {
|
||||
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(),
|
||||
}
|
||||
@ -1035,7 +1045,7 @@ impl MinerService for Miner {
|
||||
mod tests {
|
||||
|
||||
use std::time::Duration;
|
||||
use super::super::MinerService;
|
||||
use super::super::{MinerService, PrioritizationStrategy};
|
||||
use super::*;
|
||||
use util::*;
|
||||
use ethkey::{Generator, Random};
|
||||
@ -1089,6 +1099,7 @@ mod tests {
|
||||
tx_gas_limit: !U256::zero(),
|
||||
tx_queue_size: 1024,
|
||||
tx_queue_gas_limit: GasLimit::None,
|
||||
tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice,
|
||||
pending_set: PendingSet::AlwaysSealing,
|
||||
work_queue_size: 5,
|
||||
enable_resubmission: true,
|
||||
|
@ -47,7 +47,7 @@ mod transaction_queue;
|
||||
mod work_notify;
|
||||
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::external::{ExternalMiner, ExternalMinerService};
|
||||
pub use client::TransactionImportResult;
|
||||
|
@ -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(|obj| match *obj {
|
||||
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,
|
||||
}));
|
||||
@ -67,10 +68,14 @@ impl PriceInfo {
|
||||
let client = try!(Client::new().map_err(|_| ()));
|
||||
thread::spawn(move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let _ = client.request(FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice").unwrap(), SetPriceHandler {
|
||||
set_price: set_price,
|
||||
channel: tx,
|
||||
}).ok().and_then(|_| rx.recv().ok());
|
||||
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,
|
||||
channel: tx,
|
||||
}).ok().and_then(|_| rx.recv().ok());
|
||||
client.close();
|
||||
});
|
||||
Ok(())
|
||||
|
@ -49,7 +49,7 @@
|
||||
//! 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(st1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
//!
|
||||
@ -130,11 +130,20 @@ struct TransactionOrder {
|
||||
/// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5)
|
||||
/// High nonce_height = Low priority (processed later)
|
||||
nonce_height: U256,
|
||||
/// Gas specified in the transaction.
|
||||
gas: U256,
|
||||
/// Gas Price of the transaction.
|
||||
/// Low gas price = Low priority (processed later)
|
||||
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: H256,
|
||||
/// Origin of the transaction
|
||||
@ -145,11 +154,15 @@ struct 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 {
|
||||
nonce_height: tx.nonce() - base_nonce,
|
||||
gas: tx.transaction.gas.clone(),
|
||||
gas_price: tx.transaction.gas_price,
|
||||
gas: tx.transaction.gas,
|
||||
gas_factor: factor,
|
||||
strategy: strategy,
|
||||
hash: tx.hash(),
|
||||
origin: tx.origin,
|
||||
penalties: 0,
|
||||
@ -197,11 +210,28 @@ impl Ord for TransactionOrder {
|
||||
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
|
||||
let a_gas = self.gas_price;
|
||||
let b_gas = b.gas_price;
|
||||
if a_gas != b_gas {
|
||||
return b_gas.cmp(&a_gas);
|
||||
if self.gas_price != b.gas_price {
|
||||
return b.gas_price.cmp(&self.gas_price);
|
||||
}
|
||||
|
||||
// Compare hashes
|
||||
@ -326,14 +356,14 @@ impl TransactionSet {
|
||||
let to_drop : Vec<(Address, U256)> = {
|
||||
self.by_priority
|
||||
.iter()
|
||||
.skip_while(|order| {
|
||||
.filter(|order| {
|
||||
count = count + 1;
|
||||
let r = gas.overflowing_add(order.gas);
|
||||
if r.1 { return false }
|
||||
gas = r.0;
|
||||
// Own and retracted transactions are allowed to go above the gas limit, bot not above the count limit.
|
||||
(gas <= self.gas_limit || order.origin == TransactionOrigin::Local || order.origin == TransactionOrigin::RetractedBlock) &&
|
||||
count <= self.limit
|
||||
// Own and retracted transactions are allowed to go above all limits.
|
||||
order.origin != TransactionOrigin::Local && order.origin != TransactionOrigin::RetractedBlock &&
|
||||
(gas > self.gas_limit || count > self.limit)
|
||||
})
|
||||
.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`."))
|
||||
@ -345,6 +375,7 @@ impl TransactionSet {
|
||||
.fold(HashMap::new(), |mut removed, (sender, nonce)| {
|
||||
let order = self.drop(&sender, &nonce)
|
||||
.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)
|
||||
.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.
|
||||
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
|
||||
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)
|
||||
minimal_gas_price: U256,
|
||||
/// The maximum amount of gas any individual transaction may use.
|
||||
@ -434,18 +489,18 @@ pub struct TransactionQueue {
|
||||
|
||||
impl Default for TransactionQueue {
|
||||
fn default() -> Self {
|
||||
TransactionQueue::new()
|
||||
TransactionQueue::new(PrioritizationStrategy::GasPriceOnly)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionQueue {
|
||||
/// Creates new instance of this Queue
|
||||
pub fn new() -> Self {
|
||||
Self::with_limits(1024, !U256::zero(), !U256::zero())
|
||||
pub fn new(strategy: PrioritizationStrategy) -> Self {
|
||||
Self::with_limits(strategy, 1024, !U256::zero(), !U256::zero())
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
by_priority: BTreeSet::new(),
|
||||
by_address: Table::new(),
|
||||
@ -463,6 +518,7 @@ impl TransactionQueue {
|
||||
};
|
||||
|
||||
TransactionQueue {
|
||||
strategy: strategy,
|
||||
minimal_gas_price: U256::zero(),
|
||||
tx_gas_limit: tx_gas_limit,
|
||||
gas_limit: !U256::zero(),
|
||||
@ -644,7 +700,7 @@ impl TransactionQueue {
|
||||
None => vec![],
|
||||
};
|
||||
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());
|
||||
}
|
||||
// Same thing for future
|
||||
@ -653,7 +709,7 @@ impl TransactionQueue {
|
||||
None => vec![],
|
||||
};
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -678,6 +734,8 @@ impl TransactionQueue {
|
||||
let nonce = transaction.nonce();
|
||||
let current_nonce = fetch_account(&sender).nonce;
|
||||
|
||||
trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash());
|
||||
|
||||
// Remove from future
|
||||
let order = self.future.drop(&sender, &nonce);
|
||||
if order.is_some() {
|
||||
@ -841,6 +899,7 @@ impl TransactionQueue {
|
||||
return Err(TransactionError::AlreadyImported);
|
||||
}
|
||||
|
||||
let min_gas_price = (self.minimal_gas_price, self.strategy);
|
||||
let address = tx.sender();
|
||||
let nonce = tx.nonce();
|
||||
let hash = tx.hash();
|
||||
@ -878,7 +937,7 @@ impl TransactionQueue {
|
||||
if nonce > next_nonce {
|
||||
// We have a gap - put to future.
|
||||
// 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
|
||||
let removed = self.future.enforce_limit(&mut self.by_hash);
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n));
|
||||
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
|
||||
/// gas_price)
|
||||
fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, set: &mut TransactionSet, by_hash: &mut HashMap<H256, VerifiedTransaction>) -> bool {
|
||||
let order = TransactionOrder::for_transaction(&tx, base_nonce);
|
||||
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, min_gas_price.0, min_gas_price.1);
|
||||
let hash = tx.hash();
|
||||
let address = tx.sender();
|
||||
let nonce = tx.nonce();
|
||||
@ -953,12 +1012,14 @@ impl TransactionQueue {
|
||||
let old_fee = old.gas_price;
|
||||
let new_fee = order.gas_price;
|
||||
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)
|
||||
set.insert(address, nonce, old);
|
||||
// and remove new one
|
||||
by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`.");
|
||||
false
|
||||
} else {
|
||||
trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash);
|
||||
// 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`.");
|
||||
true
|
||||
@ -1010,12 +1071,12 @@ mod test {
|
||||
fn default_gas_val() -> U256 { 100_000.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 {
|
||||
action: Action::Create,
|
||||
value: U256::from(100),
|
||||
data: "3331600055".from_hex().unwrap(),
|
||||
gas: default_gas_val(),
|
||||
gas: gas,
|
||||
gas_price: gas_price,
|
||||
nonce: nonce
|
||||
}
|
||||
@ -1023,7 +1084,12 @@ mod test {
|
||||
|
||||
fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction {
|
||||
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 {
|
||||
@ -1038,8 +1104,8 @@ mod test {
|
||||
}
|
||||
|
||||
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 tx2 = new_unsigned_tx(nonce + nonce_increment, gas_price + gas_price_increment);
|
||||
let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price);
|
||||
let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment);
|
||||
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
@ -1049,8 +1115,8 @@ mod test {
|
||||
/// Returns two consecutive transactions, both with increased gas price
|
||||
fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) {
|
||||
let gas = default_gas_price() + gas_price_increment;
|
||||
let tx1 = new_unsigned_tx(default_nonce(), gas);
|
||||
let tx2 = new_unsigned_tx(default_nonce() + 1.into(), gas);
|
||||
let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas);
|
||||
let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas);
|
||||
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
@ -1077,17 +1143,21 @@ mod test {
|
||||
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]
|
||||
fn should_return_correct_nonces_when_dropped_because_of_limit() {
|
||||
// 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 sender = tx1.sender().unwrap();
|
||||
let nonce = tx1.nonce;
|
||||
txq.add(tx1.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.last_nonce(&sender), Some(nonce + U256::one()));
|
||||
assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into()));
|
||||
|
||||
// when
|
||||
let tx = new_tx(123.into(), 1.into());
|
||||
@ -1133,9 +1203,9 @@ mod test {
|
||||
x
|
||||
};
|
||||
// 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());
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero());
|
||||
let order2 = transaction_order(&tx2, U256::zero());
|
||||
set.insert(tx2.sender(), tx2.nonce(), order2.clone());
|
||||
assert_eq!(set.by_priority.len(), 2);
|
||||
assert_eq!(set.by_address.len(), 2);
|
||||
@ -1176,7 +1246,7 @@ mod test {
|
||||
x
|
||||
};
|
||||
// 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());
|
||||
assert_eq!(set.by_priority.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().1.len(), 1);
|
||||
// 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());
|
||||
assert_eq!(set.by_priority.len(), 1);
|
||||
assert_eq!(set.by_address.len(), 1);
|
||||
@ -1213,10 +1283,10 @@ mod test {
|
||||
};
|
||||
let tx = new_tx_default();
|
||||
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());
|
||||
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());
|
||||
}
|
||||
|
||||
@ -1233,7 +1303,7 @@ mod test {
|
||||
assert_eq!(set.gas_price_entry_limit(), 0.into());
|
||||
let tx = new_tx_default();
|
||||
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_eq!(set.gas_price_entry_limit(), 2.into());
|
||||
}
|
||||
@ -1241,7 +1311,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_handle_same_transaction_imported_twice_with_different_state_nonces() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_similar_tx_pair();
|
||||
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
|
||||
!U256::zero() };
|
||||
@ -1266,7 +1336,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_all_transactions_from_future() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
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:
|
||||
!U256::zero() };
|
||||
@ -1292,7 +1362,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_tx() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
|
||||
// when
|
||||
@ -1304,10 +1374,77 @@ mod test {
|
||||
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]
|
||||
fn gas_limit_should_never_overflow() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
txq.set_gas_limit(U256::zero());
|
||||
assert_eq!(txq.gas_limit, U256::zero());
|
||||
|
||||
@ -1321,7 +1458,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_import_transaction_above_gas_limit() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let gas = tx.gas;
|
||||
let limit = gas / U256::from(2);
|
||||
@ -1344,7 +1481,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_drop_transactions_from_senders_without_balance() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let account = |a: &Address| AccountDetails {
|
||||
nonce: default_account_details(a).nonce,
|
||||
@ -1367,7 +1504,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_import_transaction_below_min_gas_price_threshold_if_external() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
txq.set_minimal_gas_price(tx.gas_price + U256::one());
|
||||
|
||||
@ -1387,7 +1524,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_transaction_below_min_gas_price_threshold_if_local() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
txq.set_minimal_gas_price(tx.gas_price + U256::one());
|
||||
|
||||
@ -1406,8 +1543,8 @@ mod test {
|
||||
use rlp::{self, RlpStream, Stream};
|
||||
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_unsigned_tx(123.into(), 1.into());
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_unsigned_tx(123.into(), 100.into(), 1.into());
|
||||
let stx = {
|
||||
let mut s = RlpStream::new_list(9);
|
||||
s.append(&tx.nonce);
|
||||
@ -1431,7 +1568,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_txs_from_same_sender() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
@ -1449,7 +1586,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_prioritize_local_transactions_within_same_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
// the second one has same nonce but higher `gas_price`
|
||||
let (_, tx2) = new_similar_tx_pair();
|
||||
@ -1470,7 +1607,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_prioritize_reimported_transactions_within_same_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
// the second one has same nonce but higher `gas_price`
|
||||
let (_, tx2) = new_similar_tx_pair();
|
||||
@ -1491,7 +1628,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_prioritize_local_transactions_with_different_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
// when
|
||||
@ -1509,7 +1646,7 @@ mod test {
|
||||
fn should_penalize_transactions_from_sender_in_future() {
|
||||
// given
|
||||
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
|
||||
let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
|
||||
@ -1538,7 +1675,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_penalize_transactions_from_sender() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
// txa, txb - slightly bigger gas price to have consistent ordering
|
||||
let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
|
||||
@ -1571,7 +1708,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_pending_hashes() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
@ -1589,7 +1726,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_put_transaction_to_futures_if_gap_detected() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(2.into(), 0.into());
|
||||
|
||||
@ -1615,7 +1752,7 @@ mod test {
|
||||
!U256::zero() };
|
||||
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());
|
||||
txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External).unwrap();
|
||||
@ -1634,12 +1771,12 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_transactions_if_gap_filled() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let kp = Random.generate().unwrap();
|
||||
let secret = kp.secret();
|
||||
let tx = new_unsigned_tx(123.into(), 1.into()).sign(secret);
|
||||
let tx1 = new_unsigned_tx(124.into(), 1.into()).sign(secret);
|
||||
let tx2 = new_unsigned_tx(125.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(), default_gas_val(), 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();
|
||||
assert_eq!(txq.status().pending, 1);
|
||||
@ -1661,7 +1798,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_remove_transaction() {
|
||||
// given
|
||||
let mut txq2 = TransactionQueue::new();
|
||||
let mut txq2 = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(3.into(), 0.into());
|
||||
txq2.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq2.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@ -1682,7 +1819,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_transactions_to_future_if_gap_introduced() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let tx3 = new_tx_default();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@ -1703,7 +1840,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_clear_queue() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
// add
|
||||
@ -1723,7 +1860,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_drop_old_transactions_when_hitting_the_limit() {
|
||||
// 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 sender = tx.sender().unwrap();
|
||||
let nonce = tx.nonce;
|
||||
@ -1744,7 +1881,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into());
|
||||
let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into());
|
||||
@ -1763,23 +1900,28 @@ mod test {
|
||||
|
||||
#[test]
|
||||
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 (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx4.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).unwrap();
|
||||
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
// limited by gas
|
||||
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap_err();
|
||||
assert_eq!(txq.status().pending, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 (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(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(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
|
||||
assert_eq!(txq.status().pending, 4);
|
||||
@ -1787,10 +1929,10 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_drop_transactions_with_old_nonces() {
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
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() };
|
||||
|
||||
// when
|
||||
let res = txq.add(tx, &fetch_last_nonce, TransactionOrigin::External);
|
||||
@ -1807,7 +1949,7 @@ mod test {
|
||||
// given
|
||||
let 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();
|
||||
let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
assert_eq!(txq.status().future, 1);
|
||||
@ -1826,7 +1968,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_accept_same_transaction_twice_if_removed() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@ -1847,7 +1989,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_move_to_future_if_state_nonce_is_higher() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let tx3 = new_tx_default();
|
||||
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() {
|
||||
init_log();
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
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 mut tx2 = (*tx).clone();
|
||||
tx2.gas_price = U256::from(200);
|
||||
@ -1893,9 +2035,9 @@ mod test {
|
||||
#[test]
|
||||
fn should_replace_same_transaction_when_importing_to_futures() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
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 mut tx1 = (*tx0).clone();
|
||||
tx1.nonce = U256::from(124);
|
||||
@ -1927,7 +2069,7 @@ mod test {
|
||||
!U256::zero() };
|
||||
let next_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();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx1.clone(), &previous_nonce, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2, &previous_nonce, TransactionOrigin::External).unwrap();
|
||||
@ -1945,7 +2087,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_none_when_transaction_from_given_address_does_not_exist() {
|
||||
// given
|
||||
let txq = TransactionQueue::new();
|
||||
let txq = TransactionQueue::default();
|
||||
|
||||
// then
|
||||
assert_eq!(txq.last_nonce(&Address::default()), None);
|
||||
@ -1954,7 +2096,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_correct_nonce_when_transactions_from_given_address_exist() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let from = tx.sender().unwrap();
|
||||
let nonce = tx.nonce;
|
||||
@ -1970,7 +2112,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
|
||||
let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() };
|
||||
@ -1988,7 +2130,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_valid_last_nonce_after_remove_all() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into());
|
||||
let sender = tx1.sender().unwrap();
|
||||
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
|
||||
@ -2012,7 +2154,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_true_if_there_is_local_transaction_pending() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
assert_eq!(txq.has_local_pending_transactions(), false);
|
||||
|
||||
@ -2028,7 +2170,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_keep_right_order_in_future() {
|
||||
// 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 prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance:
|
||||
default_account_details(a).balance };
|
||||
@ -2045,15 +2187,16 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_correct_last_nonce() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2, tx2_2, tx3) = {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
let nonce = 123.into();
|
||||
let tx = new_unsigned_tx(nonce, 1.into());
|
||||
let tx2 = new_unsigned_tx(nonce + 1.into(), 1.into());
|
||||
let tx2_2 = new_unsigned_tx(nonce + 1.into(), 5.into());
|
||||
let tx3 = new_unsigned_tx(nonce + 2.into(), 1.into());
|
||||
let gas = default_gas_val();
|
||||
let tx = new_unsigned_tx(nonce, gas, 1.into());
|
||||
let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 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))
|
||||
|
@ -89,7 +89,7 @@ impl ClientService {
|
||||
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;
|
||||
|
||||
let pruning = config.pruning;
|
||||
|
@ -19,12 +19,20 @@
|
||||
use account_db::{AccountDB, AccountDBMut};
|
||||
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 rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View};
|
||||
|
||||
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.
|
||||
#[repr(u8)]
|
||||
enum CodeState {
|
||||
@ -88,6 +96,10 @@ impl Account {
|
||||
// walk the account's storage trie, returning an RLP item containing the
|
||||
// account properties and the storage.
|
||||
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 mut pairs = Vec::new();
|
||||
@ -142,6 +154,11 @@ impl Account {
|
||||
) -> Result<(Self, Option<Bytes>), Error> {
|
||||
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 balance = try!(rlp.val_at(1));
|
||||
let code_state: CodeState = {
|
||||
@ -214,7 +231,7 @@ mod tests {
|
||||
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use super::Account;
|
||||
use super::{ACC_EMPTY, Account};
|
||||
|
||||
#[test]
|
||||
fn encoding_basic() {
|
||||
@ -310,4 +327,14 @@ mod tests {
|
||||
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ use engines::Engine;
|
||||
use ids::BlockID;
|
||||
use views::BlockView;
|
||||
|
||||
use util::{Bytes, Hashable, HashDB, snappy};
|
||||
use util::{Bytes, Hashable, HashDB, snappy, U256, Uint};
|
||||
use util::memorydb::MemoryDB;
|
||||
use util::Mutex;
|
||||
use util::hash::{FixedHash, H256};
|
||||
@ -44,6 +44,7 @@ use self::block::AbridgedBlock;
|
||||
use self::io::SnapshotWriter;
|
||||
|
||||
use super::state_db::StateDB;
|
||||
use super::state::Account as StateAccount;
|
||||
|
||||
use crossbeam::{scope, ScopedJoinHandle};
|
||||
use rand::{Rng, OsRng};
|
||||
@ -409,6 +410,7 @@ impl StateRebuilder {
|
||||
/// Feed an uncompressed state chunk into the rebuilder.
|
||||
pub fn feed(&mut self, chunk: &[u8]) -> Result<(), ::error::Error> {
|
||||
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 mut pairs = Vec::with_capacity(rlp.item_count());
|
||||
|
||||
@ -476,7 +478,9 @@ impl StateRebuilder {
|
||||
};
|
||||
|
||||
for (hash, thin_rlp) in pairs {
|
||||
bloom.set(&*hash);
|
||||
if &thin_rlp[..] != &empty_rlp[..] {
|
||||
bloom.set(&*hash);
|
||||
}
|
||||
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.
|
||||
pub struct BlockRebuilder {
|
||||
chain: BlockChain,
|
||||
db: Arc<Database>,
|
||||
rng: OsRng,
|
||||
disconnected: Vec<(u64, H256)>,
|
||||
best_number: u64,
|
||||
@ -571,9 +576,10 @@ pub struct BlockRebuilder {
|
||||
|
||||
impl 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 {
|
||||
chain: chain,
|
||||
db: db,
|
||||
rng: try!(OsRng::new()),
|
||||
disconnected: Vec::new(),
|
||||
best_number: best_number,
|
||||
@ -616,15 +622,17 @@ impl BlockRebuilder {
|
||||
}
|
||||
|
||||
let is_best = cur_number == self.best_number;
|
||||
let mut batch = self.db.transaction();
|
||||
|
||||
// special-case the first block in each chunk.
|
||||
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()));
|
||||
}
|
||||
} 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();
|
||||
|
||||
parent_hash = BlockView::new(&block_bytes).hash();
|
||||
|
@ -98,7 +98,7 @@ impl Restoration {
|
||||
.map_err(UtilError::SimpleString)));
|
||||
|
||||
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();
|
||||
Ok(Restoration {
|
||||
@ -346,7 +346,7 @@ impl Service {
|
||||
|
||||
self.taking_snapshot.store(false, Ordering::SeqCst);
|
||||
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
|
||||
// is that the state we were snapshotting got pruned out
|
||||
// before we could finish.
|
||||
@ -415,9 +415,14 @@ impl Service {
|
||||
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)));
|
||||
|
||||
*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,
|
||||
block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32,
|
||||
};
|
||||
@ -535,7 +540,7 @@ impl SnapshotService for Service {
|
||||
|
||||
fn status(&self) -> RestorationStatus {
|
||||
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;
|
||||
*block_chunks_done = self.block_chunks.load(Ordering::SeqCst) as u32;
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ fn chunk_and_restore(amount: u64) {
|
||||
// restore it.
|
||||
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 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 engine = ::engines::NullEngine::new(Default::default(), Default::default());
|
||||
for chunk_hash in &reader.manifest().block_hashes {
|
||||
|
@ -30,7 +30,7 @@ use std::sync::Arc;
|
||||
trait Oracle: Send + Sync {
|
||||
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 {
|
||||
@ -45,10 +45,8 @@ impl<F> Oracle for StandardOracle<F>
|
||||
self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number())
|
||||
}
|
||||
|
||||
fn is_major_syncing(&self) -> bool {
|
||||
let queue_info = self.client.queue_info();
|
||||
|
||||
(self.sync_status)() || queue_info.unverified_queue_size + queue_info.verified_queue_size > 3
|
||||
fn is_major_importing(&self) -> bool {
|
||||
(self.sync_status)()
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +108,7 @@ impl ChainNotify for Watcher {
|
||||
_: Vec<H256>,
|
||||
_duration: u64)
|
||||
{
|
||||
if self.oracle.is_major_syncing() { return }
|
||||
if self.oracle.is_major_importing() { return }
|
||||
|
||||
trace!(target: "snapshot_watcher", "{} imported", imported.len());
|
||||
|
||||
@ -145,7 +143,7 @@ mod tests {
|
||||
self.0.get(&hash).cloned()
|
||||
}
|
||||
|
||||
fn is_major_syncing(&self) -> bool { false }
|
||||
fn is_major_importing(&self) -> bool { false }
|
||||
}
|
||||
|
||||
struct TestBroadcast(Option<u64>);
|
||||
|
@ -151,7 +151,8 @@ impl Spec {
|
||||
if self.state_root_memo.read().is_none() {
|
||||
*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.
|
||||
|
@ -288,6 +288,15 @@ impl Account {
|
||||
/// Determine whether there are any un-`commit()`-ed storage-setting operations.
|
||||
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)]
|
||||
/// 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} }
|
||||
|
@ -239,15 +239,15 @@ impl State {
|
||||
|
||||
/// Create a recoverable snaphot of this state.
|
||||
pub fn snapshot(&mut self) {
|
||||
self.snapshots.borrow_mut().push(HashMap::new());
|
||||
self.snapshots.get_mut().push(HashMap::new());
|
||||
}
|
||||
|
||||
/// Merge last snapshot with previous.
|
||||
pub fn discard_snapshot(&mut self) {
|
||||
// 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(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() {
|
||||
**prev = snapshot;
|
||||
} else {
|
||||
@ -261,11 +261,11 @@ impl State {
|
||||
|
||||
/// Revert to the last snapshot and discard it.
|
||||
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() {
|
||||
match v {
|
||||
Some(v) => {
|
||||
match self.cache.borrow_mut().entry(k) {
|
||||
match self.cache.get_mut().entry(k) {
|
||||
Entry::Occupied(mut e) => {
|
||||
// Merge snapshotted changes back into the main account
|
||||
// storage preserving the cache.
|
||||
@ -277,7 +277,7 @@ impl State {
|
||||
}
|
||||
},
|
||||
None => {
|
||||
match self.cache.borrow_mut().entry(k) {
|
||||
match self.cache.get_mut().entry(k) {
|
||||
Entry::Occupied(e) => {
|
||||
if e.get().is_dirty() {
|
||||
e.remove();
|
||||
@ -340,18 +340,20 @@ impl State {
|
||||
|
||||
/// Determine whether an account exists.
|
||||
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`.
|
||||
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()))
|
||||
}
|
||||
|
||||
/// Get the nonce of account `a`.
|
||||
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()))
|
||||
}
|
||||
|
||||
@ -415,18 +417,18 @@ impl State {
|
||||
|
||||
/// Get accounts' code.
|
||||
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()))
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
/// Get accounts' code size.
|
||||
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()))
|
||||
}
|
||||
|
||||
@ -505,7 +507,9 @@ impl State {
|
||||
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
|
||||
match a.account {
|
||||
Some(ref mut account) => {
|
||||
db.note_account_bloom(&address);
|
||||
if !account.is_empty() {
|
||||
db.note_account_bloom(&address);
|
||||
}
|
||||
let addr_hash = account.address_hash(address);
|
||||
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
|
||||
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()) {
|
||||
a.state = AccountState::Committed;
|
||||
match a.account {
|
||||
@ -559,7 +563,6 @@ impl State {
|
||||
pub fn populate_from(&mut self, accounts: PodState) {
|
||||
assert!(self.snapshots.borrow().is_empty());
|
||||
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))));
|
||||
}
|
||||
}
|
||||
@ -578,14 +581,14 @@ impl State {
|
||||
}
|
||||
|
||||
fn query_pod(&mut self, query: &PodState) {
|
||||
for (address, pod_account) in query.get() {
|
||||
self.ensure_cached(address, RequireCache::Code, |a| {
|
||||
if a.is_some() {
|
||||
for key in pod_account.storage.keys() {
|
||||
self.storage_at(address, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (address, pod_account) in query.get().into_iter()
|
||||
.filter(|&(ref a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some()))
|
||||
{
|
||||
// needs to be split into two parts for the refcell code here
|
||||
// to work.
|
||||
for key in pod_account.storage.keys() {
|
||||
self.storage_at(address, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,7 +616,7 @@ impl State {
|
||||
/// Check caches for required data
|
||||
/// First searches for account in the local, then the shared cache.
|
||||
/// 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 {
|
||||
// check local cache first
|
||||
if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) {
|
||||
@ -636,7 +639,7 @@ impl State {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
// 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
|
||||
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);
|
||||
|
||||
match &mut self.cache.borrow_mut().get_mut(a).unwrap().account {
|
||||
&mut Some(ref mut acc) => not_default(acc),
|
||||
slot => *slot = Some(default()),
|
||||
}
|
||||
|
||||
// at this point the account is guaranteed to be in the cache.
|
||||
// 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).unwrap();
|
||||
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),
|
||||
slot => *slot = Some(default()),
|
||||
}
|
||||
|
||||
// set the dirty flag after changing account data.
|
||||
entry.state = AccountState::Dirty;
|
||||
match entry.account {
|
||||
@ -1667,6 +1671,21 @@ fn remove() {
|
||||
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]
|
||||
fn remove_from_database() {
|
||||
let a = Address::zero();
|
||||
@ -1797,4 +1816,20 @@ fn create_empty() {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,17 +26,18 @@ use bloom_journal::{Bloom, BloomJournal};
|
||||
use db::COL_ACCOUNT_BLOOM;
|
||||
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 DEFAULT_ACCOUNT_PRESET: usize = 1000000;
|
||||
|
||||
pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count";
|
||||
|
||||
const STATE_CACHE_BLOCKS: usize = 12;
|
||||
|
||||
/// Shared canonical state cache.
|
||||
struct AccountCache {
|
||||
/// 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>>,
|
||||
/// Information on the modifications in recently committed blocks; specifically which addresses
|
||||
/// changed in which block. Ordered by block number.
|
||||
@ -92,6 +93,7 @@ pub struct StateDB {
|
||||
local_cache: Vec<CacheQueueItem>,
|
||||
/// Shared account bloom. Does not handle chain reorganizations.
|
||||
account_bloom: Arc<Mutex<Bloom>>,
|
||||
cache_size: usize,
|
||||
/// Hash of the block on top of which this instance was created or
|
||||
/// `None` if cache is disabled
|
||||
parent_hash: Option<H256>,
|
||||
@ -102,16 +104,41 @@ pub struct 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
|
||||
/// This bloom is used to handle request for the non-existant account fast
|
||||
pub fn load_bloom(db: &Database) -> Bloom {
|
||||
let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY)
|
||||
.expect("Low-level database error");
|
||||
|
||||
if hash_count_entry.is_none() {
|
||||
return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET);
|
||||
}
|
||||
let hash_count_bytes = hash_count_entry.unwrap();
|
||||
let hash_count_bytes = match hash_count_entry {
|
||||
Some(bytes) => bytes,
|
||||
None => return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET),
|
||||
};
|
||||
|
||||
assert_eq!(hash_count_bytes.len(), 1);
|
||||
let hash_count = hash_count_bytes[0];
|
||||
|
||||
@ -129,23 +156,6 @@ impl StateDB {
|
||||
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 {
|
||||
trace!(target: "account_bloom", "Check account bloom: {:?}", address);
|
||||
let bloom = self.account_bloom.lock();
|
||||
@ -172,19 +182,24 @@ impl StateDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commit all recent insert operations and canonical historical commits' removals from the
|
||||
/// old era to the backing database, reverting any non-canonical historical commit's inserts.
|
||||
pub fn commit(&mut self, batch: &mut DBTransaction, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
|
||||
/// Journal all recent operations under the given era and ID.
|
||||
pub fn journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result<u32, UtilError> {
|
||||
{
|
||||
let mut bloom_lock = self.account_bloom.lock();
|
||||
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_number = Some(now);
|
||||
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
|
||||
/// the global cache with the best block state.
|
||||
/// This function updates the global cache by removing entries
|
||||
@ -298,6 +313,7 @@ impl StateDB {
|
||||
account_cache: self.account_cache.clone(),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: self.account_bloom.clone(),
|
||||
cache_size: self.cache_size,
|
||||
parent_hash: None,
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
@ -311,6 +327,7 @@ impl StateDB {
|
||||
account_cache: self.account_cache.clone(),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: self.account_bloom.clone(),
|
||||
cache_size: self.cache_size,
|
||||
parent_hash: Some(parent.clone()),
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
@ -324,7 +341,8 @@ impl StateDB {
|
||||
|
||||
/// Heap size used.
|
||||
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`.
|
||||
@ -365,6 +383,11 @@ impl StateDB {
|
||||
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
|
||||
/// state and filtering out account modified in later blocks.
|
||||
fn is_allowed(addr: &Address, parent_hash: &Option<H256>, modifications: &VecDeque<BlockChanges>) -> bool {
|
||||
@ -404,77 +427,77 @@ impl StateDB {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
||||
use tests::helpers::*;
|
||||
use state::Account;
|
||||
use util::log::init_log;
|
||||
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
||||
use tests::helpers::*;
|
||||
use state::Account;
|
||||
use util::log::init_log;
|
||||
|
||||
#[test]
|
||||
fn state_db_smoke() {
|
||||
init_log();
|
||||
#[test]
|
||||
fn state_db_smoke() {
|
||||
init_log();
|
||||
|
||||
let mut state_db_result = get_temp_state_db();
|
||||
let state_db = state_db_result.take();
|
||||
let root_parent = H256::random();
|
||||
let address = Address::random();
|
||||
let h0 = H256::random();
|
||||
let h1a = H256::random();
|
||||
let h1b = H256::random();
|
||||
let h2a = H256::random();
|
||||
let h2b = H256::random();
|
||||
let h3a = H256::random();
|
||||
let h3b = H256::random();
|
||||
let mut batch = DBTransaction::new(state_db.journal_db().backing());
|
||||
let mut state_db_result = get_temp_state_db();
|
||||
let state_db = state_db_result.take();
|
||||
let root_parent = H256::random();
|
||||
let address = Address::random();
|
||||
let h0 = H256::random();
|
||||
let h1a = H256::random();
|
||||
let h1b = H256::random();
|
||||
let h2a = H256::random();
|
||||
let h2b = H256::random();
|
||||
let h3a = H256::random();
|
||||
let h3b = H256::random();
|
||||
let mut batch = DBTransaction::new(state_db.journal_db().backing());
|
||||
|
||||
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
||||
// balance [ 5 5 4 3 2 2 ]
|
||||
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.commit(&mut batch, 0, &h0, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
||||
// balance [ 5 5 4 3 2 2 ]
|
||||
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.journal_under(&mut batch, 0, &h0).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.commit(&mut batch, 1, &h1a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.journal_under(&mut batch, 1, &h1a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
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.commit(&mut batch, 1, &h1b, None).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
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.journal_under(&mut batch, 1, &h1b).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
|
||||
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.commit(&mut batch, 2, &h2b, None).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
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.journal_under(&mut batch, 2, &h2b).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
|
||||
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.commit(&mut batch, 2, &h2a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
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.journal_under(&mut batch, 2, &h2a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h2a);
|
||||
s.commit(&mut batch, 3, &h3a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
let mut s = state_db.boxed_clone_canon(&h2a);
|
||||
s.journal_under(&mut batch, 3, &h3a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5));
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5));
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h1a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h1a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h2b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h2b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h1b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h1b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
// reorg to 3b
|
||||
// blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ]
|
||||
let mut s = state_db.boxed_clone_canon(&h2b);
|
||||
s.commit(&mut batch, 3, &h3b, None).unwrap();
|
||||
s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true);
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
}
|
||||
// reorg to 3b
|
||||
// blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ]
|
||||
let mut s = state_db.boxed_clone_canon(&h2b);
|
||||
s.journal_under(&mut batch, 3, &h3b).unwrap();
|
||||
s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true);
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ use common::*;
|
||||
use devtools::*;
|
||||
use miner::Miner;
|
||||
use rlp::{Rlp, View};
|
||||
use spec::Spec;
|
||||
|
||||
#[test]
|
||||
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, 53, 1201, 600);
|
||||
|
||||
for _ in 0..40 {
|
||||
for _ in 0..400 {
|
||||
client.import_verified_blocks();
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ use spec::*;
|
||||
use state_db::StateDB;
|
||||
use block::{OpenBlock, Drain};
|
||||
use blockchain::{BlockChain, Config as BlockChainConfig};
|
||||
use state::State;
|
||||
use evm::Schedule;
|
||||
use engines::Engine;
|
||||
use ethereum;
|
||||
@ -35,19 +34,20 @@ use db::COL_STATE;
|
||||
pub enum ChainEra {
|
||||
Frontier,
|
||||
Homestead,
|
||||
DaoHardfork,
|
||||
Eip150,
|
||||
TransitionTest,
|
||||
}
|
||||
|
||||
pub struct TestEngine {
|
||||
engine: Arc<Engine>,
|
||||
max_depth: usize
|
||||
max_depth: usize,
|
||||
}
|
||||
|
||||
impl TestEngine {
|
||||
pub fn new(max_depth: usize) -> TestEngine {
|
||||
TestEngine {
|
||||
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 {
|
||||
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);
|
||||
StateDB::new(journal_db)
|
||||
StateDB::new(journal_db, 5 * 1024 * 1024)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -129,7 +129,7 @@ impl<T> TraceDB<T> where T: DatabaseExtras {
|
||||
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
|
||||
let mut batch = DBTransaction::new(&tracesdb);
|
||||
batch.put(db::COL_TRACE, b"version", TRACE_DB_VER);
|
||||
tracesdb.write(batch).unwrap();
|
||||
tracesdb.write(batch).expect("failed to update version");
|
||||
|
||||
TraceDB {
|
||||
traces: RwLock::new(HashMap::new()),
|
||||
@ -275,16 +275,6 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
|
||||
.map(Into::into)
|
||||
.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 trace_blooms = chain.replace(&replaced_range, enacted_blooms);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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> {
|
||||
@ -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 {
|
||||
LocalizedTrace {
|
||||
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]
|
||||
fn test_import() {
|
||||
|
@ -31,5 +31,13 @@ pub struct BlockChainInfo {
|
||||
/// Best blockchain block hash.
|
||||
pub best_block_hash: H256,
|
||||
/// 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>,
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ pub enum RestorationStatus {
|
||||
Inactive,
|
||||
/// Ongoing restoration.
|
||||
Ongoing {
|
||||
/// Total number of state chunks.
|
||||
state_chunks: u32,
|
||||
/// Total number of block chunks.
|
||||
block_chunks: u32,
|
||||
/// Number of state chunks completed.
|
||||
state_chunks_done: u32,
|
||||
/// Number of block chunks completed.
|
||||
|
@ -108,7 +108,7 @@ impl Filter {
|
||||
|
||||
/// Returns true if given trace matches the filter.
|
||||
pub fn matches(&self, trace: &FlatTrace) -> bool {
|
||||
let action = match trace.action {
|
||||
match trace.action {
|
||||
Action::Call(ref call) => {
|
||||
let from_matches = self.from_address.matches(&call.from);
|
||||
let to_matches = self.to_address.matches(&call.to);
|
||||
@ -116,7 +116,12 @@ impl Filter {
|
||||
}
|
||||
Action::Create(ref create) => {
|
||||
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
|
||||
},
|
||||
Action::Suicide(ref suicide) => {
|
||||
@ -124,11 +129,6 @@ impl Filter {
|
||||
let to_matches = self.to_address.matches(&suicide.refund_address);
|
||||
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
Loading…
Reference in New Issue
Block a user