diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0cc444846..e16350325 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -136,10 +136,15 @@ linux-armv7: stage: build image: ethcore/rust-armv7:latest only: + - master - beta - tags - stable script: + - export CC=arm-linux-gnueabihf-gcc + - export CXX=arm-linux-gnueabihf-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config @@ -171,10 +176,15 @@ linux-arm: stage: build image: ethcore/rust-arm:latest only: + - master - beta - tags - stable script: + - export CC=arm-linux-gnueabihf-gcc + - export CXX=arm-linux-gnueabihf-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config @@ -210,6 +220,10 @@ linux-armv6: - tags - stable script: + - export CC=arm-linux-gnueabi-gcc + - export CXX=arm-linux-gnueabi-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config @@ -234,10 +248,15 @@ linux-aarch64: stage: build image: ethcore/rust-aarch64:latest only: + - master - beta - tags - stable script: + - export CC=aarch64-linux-gnu-gcc + - export CXX=aarch64-linux-gnu-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config @@ -274,6 +293,7 @@ darwin: - stable script: - cargo build --release --verbose + - rm -rf parity.md5 - md5sum target/release/parity >> parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret @@ -344,6 +364,28 @@ test-linux: - rust-test dependencies: - linux-stable +test-darwin: + stage: test + before_script: + - git submodule update --init --recursive + script: + - export RUST_BACKTRACE=1 + - ./test.sh --verbose + tags: + - osx + dependencies: + - darwin +test-windows: + stage: test + before_script: + - git submodule update --init --recursive + script: + - export RUST_BACKTRACE=1 + - ./test.sh --verbose + tags: + - rust-windows + dependencies: + - windows js-release: stage: build image: ethcore/javascript:latest diff --git a/Cargo.lock b/Cargo.lock index fdb80f602..b9d989fbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,7 +3,7 @@ name = "parity" version = "1.4.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)", "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", @@ -145,15 +145,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clippy" -version = "0.0.90" +version = "0.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clippy_lints 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clippy_lints" -version = "0.0.90" +version = "0.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -279,7 +279,7 @@ dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", @@ -298,7 +298,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 (git+https://github.com/contain-rs/lru-cache)", + "lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -307,6 +307,7 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -330,7 +331,7 @@ dependencies = [ name = "ethcore-dapps" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", @@ -473,7 +474,7 @@ dependencies = [ name = "ethcore-rpc" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", "ethcore 1.4.0", "ethcore-devtools 1.4.0", @@ -503,7 +504,7 @@ dependencies = [ name = "ethcore-signer" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", "ethcore-io 1.4.0", @@ -542,7 +543,7 @@ version = "1.4.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", @@ -554,6 +555,7 @@ 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)", + "lru-cache 0.1.0 (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)", @@ -631,7 +633,7 @@ dependencies = [ name = "ethsync" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.4.0", "ethcore-io 1.4.0", @@ -906,8 +908,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lru-cache" -version = "0.0.7" -source = "git+https://github.com/contain-rs/lru-cache#13255e33c45ceb69a4b143f235a4322df5fb580e" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1211,7 +1213,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9f8baa9d0e54056c41a842b351597d0565beda98" +source = "git+https://github.com/ethcore/js-precompiled.git#ba726039185238d6fd604f092b089a7d52c0f436" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1948,8 +1950,8 @@ dependencies = [ "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" -"checksum clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "d19bda68c3db98e3a780342f6101b44312fef20a5f13ce756d1202a35922b01b" -"checksum clippy_lints 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "3d4ed67c69b9bb35169be2538691d290a3aa0cbfd4b9f0bfb7c221fc1d399a96" +"checksum clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)" = "6eacf01b0aad84a0817703498f72d252df7c0faf6a5b86d0be4265f1829e459f" +"checksum clippy_lints 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)" = "a49960c9aab544ce86b004dcb61620e8b898fea5fc0f697a028f460f48221ed6" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" "checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" "checksum ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)" = "" @@ -1986,7 +1988,7 @@ dependencies = [ "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 (git+https://github.com/contain-rs/lru-cache)" = "" +"checksum lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "656fa4dfcb02bcf1063c592ba3ff6a5303ee1f2afe98c8a889e8b1a77c6dfdb7" "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" diff --git a/Cargo.toml b/Cargo.toml index 62039696c..dc802a0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ ethcore-logger = { path = "logger" } rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 9c49c7e28..ddc23c87c 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -33,7 +33,7 @@ fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } mime_guess = { version = "1.6.1" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [build-dependencies] serde_codegen = { version = "0.8", optional = true } diff --git a/db/Cargo.toml b/db/Cargo.toml index 15ceb9b3b..27eadef4a 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethcore-devtools = { path = "../devtools" } ethcore-ipc = { path = "../ipc/rpc" } rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" } diff --git a/ethash/src/lib.rs b/ethash/src/lib.rs index 130882318..a04e2486b 100644 --- a/ethash/src/lib.rs +++ b/ethash/src/lib.rs @@ -69,14 +69,19 @@ impl EthashManager { Some(ref e) if *e == epoch => lights.recent.clone(), _ => match lights.prev_epoch.clone() { Some(e) if e == epoch => { - // swap - let t = lights.prev_epoch; - lights.prev_epoch = lights.recent_epoch; - lights.recent_epoch = t; - let t = lights.prev.clone(); - lights.prev = lights.recent.clone(); - lights.recent = t; - lights.recent.clone() + // don't swap if recent is newer. + if lights.recent_epoch > lights.prev_epoch { + None + } else { + // swap + let t = lights.prev_epoch; + lights.prev_epoch = lights.recent_epoch; + lights.recent_epoch = t; + let t = lights.prev.clone(); + lights.prev = lights.recent.clone(); + lights.recent = t; + lights.recent.clone() + } } _ => None, }, diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index ad9e545ce..1f8413339 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -24,8 +24,11 @@ rayon = "0.4.2" semver = "0.2" bit-set = "0.4" time = "0.1" +rand = "0.3" +byteorder = "0.5" +transient-hashmap = "0.1" evmjit = { path = "../evmjit", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } ethcore-util = { path = "../util" } ethcore-io = { path = "../util/io" } @@ -36,10 +39,8 @@ ethstore = { path = "../ethstore" } ethkey = { path = "../ethkey" } ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } -rand = "0.3" -lru-cache = { git = "https://github.com/contain-rs/lru-cache" } +lru-cache = "0.1.0" ethcore-bloom-journal = { path = "../util/bloom" } -byteorder = "0.5" [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index 5d951752f..5be7b1caf 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -39,10 +39,18 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ + "enode://08c7ee6a4f861ff0664a49532bcc86de1363acd608999d1b76609bb9bc278649906f069057630fd9493924a368b5d1dc9b8f8bf13ac26df72512f6d1fabd8c95@45.32.7.81:30303", "enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303", "enode://687be94c3a7beaa3d2fde82fa5046cdeb3e8198354e05b29d6e0d4e276713e3707ac10f784a7904938b06b46c764875c241b0337dd853385a4d8bfcbf8190647@95.183.51.229:30303", "enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303", - "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303" + "enode://ca5ae4eca09ba6787e29cf6d86f7634d07aae6b9e6317a59aff675851c0bf445068173208cf8ef7f5cd783d8e29b85b2fa3fa358124cf0546823149724f9bde1@138.68.1.16:30303", + "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303", + "enode://fa20444ef991596ce99b81652ac4e61de1eddc4ff21d3cd42762abd7ed47e7cf044d3c9ccddaf6035d39725e4eb372806787829ccb9a08ec7cb71883cb8c3abd@50.149.116.182:30303", + "enode://4bd6a4df3612c718333eb5ea7f817923a8cdf1bed89cee70d1710b45a0b6b77b2819846440555e451a9b602ad2efa2d2facd4620650249d8468008946887820a@71.178.232.20:30304", + "enode://921cf8e4c345fe8db913c53964f9cadc667644e7f20195a0b7d877bd689a5934e146ff2c2259f1bae6817b6585153a007ceb67d260b720fa3e6fc4350df25c7f@51.255.49.170:30303", + "enode://ffea3b01c000cdd89e1e9229fea3e80e95b646f9b2aa55071fc865e2f19543c9b06045cc2e69453e6b78100a119e66be1b5ad50b36f2ffd27293caa28efdd1b2@128.199.93.177:3030", + "enode://ee3da491ce6a155eb132708eb0e8d04b0637926ec0ae1b79e63fc97cb9fc3818f49250a0ae0d7f79ed62b66ec677f408c4e01741504dc7a051e274f1e803d454@91.121.65.179:40404", + "enode://48e063a6cf5f335b1ef2ed98126bf522cf254396f850c7d442fe2edbbc23398787e14cd4de7968a00175a82762de9cbe9e1407d8ccbcaeca5004d65f8398d759@159.203.255.59:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index d5f57defd..111dff30e 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -158,9 +158,17 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ - "enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@136.243.154.245:30303", + "enode://efe4f2493f4aff2d641b1db8366b96ddacfe13e7a6e9c8f8f8cf49f9cdba0fdf3258d8c8f8d0c5db529f8123c8f1d95f36d54d590ca1bb366a5818b9a4ba521c@163.172.187.252:30303", + "enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@163.172.157.114:30303", "enode://bcc7240543fe2cf86f5e9093d05753dd83343f8fda7bf0e833f65985c73afccf8f981301e13ef49c4804491eab043647374df1c4adf85766af88a624ecc3330e@136.243.154.244:30303", "enode://ed4227681ca8c70beb2277b9e870353a9693f12e7c548c35df6bca6a956934d6f659999c2decb31f75ce217822eefca149ace914f1cbe461ed5a2ebaf9501455@88.212.206.70:30303", + "enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303", + "enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303", + "enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303", + "enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303", + "enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303", + "enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303", + "enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 7a4958ddc..fed1a05fc 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -267,17 +267,17 @@ impl AccountProvider { /// Returns `true` if the password for `account` is `password`. `false` if not. pub fn test_password(&self, account: &Address, password: String) -> Result { - match self.sstore.sign(&account, &password, &Default::default()) { + match self.sstore.sign(account, &password, &Default::default()) { Ok(_) => Ok(true), Err(SSError::InvalidPassword) => Ok(false), Err(e) => Err(Error::SStore(e)), } - } + } /// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given. pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { - self.sstore.change_password(&account, &password, &new_password).map_err(Error::SStore) - } + self.sstore.change_password(account, &password, &new_password).map_err(Error::SStore) + } /// Helper method used for unlocking accounts. fn unlock_account(&self, account: Address, password: String, unlock: Unlock) -> Result<(), Error> { diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 5d7305b91..54c2a7a02 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -542,7 +542,7 @@ pub fn enact( Ok(b.close_and_lock()) } -#[inline(always)] +#[inline] #[cfg(not(feature = "slow-blocks"))] fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) -> Result<(), Error> { for t in transactions { diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index de0c72a38..d95c199ed 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -196,6 +196,7 @@ pub struct BlockChain { pending_best_block: RwLock>, pending_block_hashes: RwLock>, + pending_block_details: RwLock>, pending_transaction_addresses: RwLock>>, } @@ -414,6 +415,7 @@ impl<'a> Iterator for AncestryIter<'a> { } impl BlockChain { + #[cfg_attr(feature="dev", allow(useless_let_if_seq))] /// Create new instance of blockchain from given Genesis pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { // 400 is the avarage size of the key @@ -438,6 +440,7 @@ impl BlockChain { cache_man: Mutex::new(cache_man), pending_best_block: RwLock::new(None), pending_block_hashes: RwLock::new(HashMap::new()), + pending_block_details: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), }; @@ -565,7 +568,7 @@ impl BlockChain { let range = extras.number as bc::Number .. extras.number as bc::Number; let chain = bc::group::BloomGroupChain::new(self.blooms_config, self); let changes = chain.replace(&range, vec![]); - for (k, v) in changes.into_iter() { + for (k, v) in changes { batch.write(db::COL_EXTRA, &LogGroupPosition::from(k), &BloomGroup::from(v)); } batch.put(db::COL_EXTRA, b"best", &hash); @@ -789,11 +792,10 @@ impl BlockChain { /// the chain and the child's parent is this block. /// /// Used in snapshots to glue the chunks together at the end. - pub fn add_child(&self, block_hash: H256, child_hash: H256) { + pub fn add_child(&self, batch: &mut DBTransaction, block_hash: H256, child_hash: H256) { let mut parent_details = self.block_details(&block_hash) .unwrap_or_else(|| panic!("Invalid block hash: {:?}", block_hash)); - let mut batch = self.db.transaction(); parent_details.children.push(child_hash); let mut update = HashMap::new(); @@ -804,8 +806,6 @@ impl BlockChain { batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update, CacheUpdatePolicy::Overwrite); self.cache_man.lock().note_used(CacheID::BlockDetails(block_hash)); - - self.db.write(batch).unwrap(); } #[cfg_attr(feature="dev", allow(similar_names))] @@ -894,17 +894,6 @@ impl BlockChain { /// Prepares extras update. fn prepare_update(&self, batch: &mut DBTransaction, update: ExtrasUpdate, is_best: bool) { - { - let block_hashes: Vec<_> = update.block_details.keys().cloned().collect(); - - let mut write_details = self.block_details.write(); - batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update.block_details, CacheUpdatePolicy::Overwrite); - - let mut cache_man = self.cache_man.lock(); - for hash in block_hashes { - cache_man.note_used(CacheID::BlockDetails(hash)); - } - } { let mut write_receipts = self.block_receipts.write(); @@ -916,7 +905,7 @@ impl BlockChain { batch.extend_with_cache(db::COL_EXTRA, &mut *write_blocks_blooms, update.blocks_blooms, CacheUpdatePolicy::Remove); } - // These cached values must be updated last with all three locks taken to avoid + // These cached values must be updated last with all four locks taken to avoid // cache decoherence { let mut best_block = self.pending_best_block.write(); @@ -934,8 +923,10 @@ impl BlockChain { }, } let mut write_hashes = self.pending_block_hashes.write(); + let mut write_details = self.pending_block_details.write(); let mut write_txs = self.pending_transaction_addresses.write(); + batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update.block_details, CacheUpdatePolicy::Overwrite); batch.extend_with_cache(db::COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Overwrite); batch.extend_with_option_cache(db::COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Overwrite); } @@ -945,9 +936,11 @@ impl BlockChain { pub fn commit(&self) { let mut pending_best_block = self.pending_best_block.write(); let mut pending_write_hashes = self.pending_block_hashes.write(); + let mut pending_block_details = self.pending_block_details.write(); let mut pending_write_txs = self.pending_transaction_addresses.write(); let mut best_block = self.best_block.write(); + let mut write_block_details = self.block_details.write(); let mut write_hashes = self.block_hashes.write(); let mut write_txs = self.transaction_addresses.write(); // update best block @@ -960,9 +953,11 @@ impl BlockChain { let pending_hashes_keys: Vec<_> = pending_write_hashes.keys().cloned().collect(); let enacted_txs_keys: Vec<_> = enacted_txs.keys().cloned().collect(); + let pending_block_hashes: Vec<_> = pending_block_details.keys().cloned().collect(); write_hashes.extend(mem::replace(&mut *pending_write_hashes, HashMap::new())); write_txs.extend(enacted_txs.into_iter().map(|(k, v)| (k, v.expect("Transactions were partitioned; qed")))); + write_block_details.extend(mem::replace(&mut *pending_block_details, HashMap::new())); for hash in retracted_txs.keys() { write_txs.remove(hash); @@ -976,6 +971,10 @@ impl BlockChain { for hash in enacted_txs_keys { cache_man.note_used(CacheID::TransactionAddresses(hash)); } + + for hash in pending_block_hashes { + cache_man.note_used(CacheID::BlockDetails(hash)); + } } /// Iterator that lists `first` and then all of `first`'s ancestors, by hash. @@ -1296,6 +1295,11 @@ impl BlockChain { ancient_block_number: best_ancient_block.as_ref().map(|b| b.number), } } + + #[cfg(test)] + pub fn db(&self) -> &Arc { + &self.db + } } #[cfg(test)] diff --git a/ethcore/src/cache_manager.rs b/ethcore/src/cache_manager.rs index 02c8f08a1..6ad01b453 100644 --- a/ethcore/src/cache_manager.rs +++ b/ethcore/src/cache_manager.rs @@ -66,7 +66,7 @@ impl CacheManager where T: Eq + Hash { } fn rotate_cache_if_needed(&mut self) { - if self.cache_usage.len() == 0 { return } + if self.cache_usage.is_empty() { return } if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE { if let Some(cache) = self.cache_usage.pop_back() { diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index c330d53c7..926ca7bdc 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -60,12 +60,13 @@ use receipt::LocalizedReceipt; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; use trace; use trace::FlatTransactionTraces; -use evm::Factory as EvmFactory; +use evm::{Factory as EvmFactory, Schedule}; use miner::{Miner, MinerService}; use snapshot::{self, io as snapshot_io}; use factory::Factories; use rlp::{View, UntrustedRlp}; use state_db::StateDB; +use rand::OsRng; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -144,6 +145,7 @@ pub struct Client { last_hashes: RwLock>, factories: Factories, history: u64, + rng: Mutex, } impl Client { @@ -239,6 +241,7 @@ impl Client { last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, + rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), }; Ok(Arc::new(client)) } @@ -314,7 +317,7 @@ impl Client { if let Some(parent) = chain_has_parent { // Enact Verified Block let last_hashes = self.build_last_hashes(header.parent_hash().clone()); - let db = self.state_db.lock().boxed_clone_canon(&header.parent_hash()); + let db = self.state_db.lock().boxed_clone_canon(header.parent_hash()); let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); let locked_block = try!(enact_result.map_err(|e| { @@ -434,14 +437,26 @@ impl Client { /// 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 { + fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { let block = BlockView::new(&block_bytes); - let hash = block.header().hash(); + let header = block.header(); + let hash = header.hash(); let _import_lock = self.import_lock.lock(); { let _timer = PerfTimer::new("import_old_block"); + let mut rng = self.rng.lock(); let chain = self.chain.read(); + // verify block. + try!(::snapshot::verify_old_block( + &mut *rng, + &header, + &*self.engine, + &*chain, + Some(&block_bytes), + false, + )); + // Commit results let receipts = ::rlp::decode(&receipts_bytes); let mut batch = DBTransaction::new(&self.db.read()); @@ -451,7 +466,7 @@ impl Client { chain.commit(); } self.db.read().flush().expect("DB flush failed."); - hash + Ok(hash) } fn commit_block(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain { @@ -1042,7 +1057,7 @@ impl BlockChainClient for Client { return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); } } - Ok(self.import_old_block(block_bytes, receipts_bytes)) + self.import_old_block(block_bytes, receipts_bytes).map_err(Into::into) } fn queue_info(&self) -> BlockQueueInfo { @@ -1145,6 +1160,23 @@ impl BlockChainClient for Client { } impl MiningBlockChainClient for Client { + + fn latest_schedule(&self) -> Schedule { + let header_data = self.best_block_header(); + let view = HeaderView::new(&header_data); + + let env_info = EnvInfo { + number: view.number(), + author: view.author(), + timestamp: view.timestamp(), + difficulty: view.difficulty(), + last_hashes: self.build_last_hashes(view.hash()), + gas_used: U256::default(), + gas_limit: view.gas_limit(), + }; + self.engine.schedule(&env_info) + } + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &*self.engine; let chain = self.chain.read(); @@ -1220,3 +1252,33 @@ impl MayPanic for Client { self.panic_handler.on_panic(closure); } } + + +#[test] +fn should_not_cache_details_before_commit() { + use tests::helpers::*; + use std::thread; + use std::time::Duration; + use std::sync::atomic::{AtomicBool, Ordering}; + + let client = generate_dummy_client(0); + let genesis = client.chain_info().best_block_hash; + let (new_hash, new_block) = get_good_dummy_block_hash(); + + let go = { + // Separate thread uncommited transaction + let go = Arc::new(AtomicBool::new(false)); + let go_thread = go.clone(); + let another_client = client.reference().clone(); + thread::spawn(move || { + let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); + another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); + go_thread.store(true, Ordering::SeqCst); + }); + go + }; + + while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + + assert!(client.tree_route(&genesis, &new_hash).is_none()); +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 2b674433d..a291a1780 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -34,7 +34,7 @@ use log_entry::LocalizedLogEntry; use receipt::{Receipt, LocalizedReceipt}; use blockchain::extras::BlockReceipts; use error::{ImportResult}; -use evm::{Factory as EvmFactory, VMType}; +use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; @@ -147,7 +147,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -316,6 +316,10 @@ pub fn get_temp_state_db() -> GuardedTempResult { } impl MiningBlockChainClient for TestBlockChainClient { + fn latest_schedule(&self) -> Schedule { + Schedule::new_homestead_gas_fix() + } + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &*self.spec.engine; let genesis_header = self.spec.genesis_header(); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 0bc9e70fa..700b88f8b 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -27,7 +27,7 @@ use views::{BlockView}; use error::{ImportResult, CallError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; -use evm::Factory as EvmFactory; +use evm::{Factory as EvmFactory, Schedule}; use types::ids::*; use types::trace_filter::Filter as TraceFilter; use executive::Executed; @@ -236,6 +236,9 @@ pub trait MiningBlockChainClient : BlockChainClient { /// Import sealed block. Skips all verifications. fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; + + /// Returns latest schedule. + fn latest_schedule(&self) -> Schedule; } impl IpcConfig for BlockChainClient { } diff --git a/ethcore/src/db.rs b/ethcore/src/db.rs index 10672d730..92c0f1b39 100644 --- a/ethcore/src/db.rs +++ b/ethcore/src/db.rs @@ -114,7 +114,7 @@ pub trait Writable { R: Deref { match policy { CacheUpdatePolicy::Overwrite => { - for (key, value) in values.into_iter() { + for (key, value) in values { self.write(col, &key, &value); cache.insert(key, value); } @@ -135,7 +135,7 @@ pub trait Writable { R: Deref { match policy { CacheUpdatePolicy::Overwrite => { - for (key, value) in values.into_iter() { + for (key, value) in values { match value { Some(ref v) => self.write(col, &key, v), None => self.delete(col, &key), @@ -144,7 +144,7 @@ pub trait Writable { } }, CacheUpdatePolicy::Remove => { - for (key, value) in values.into_iter() { + for (key, value) in values { match value { Some(v) => self.write(col, &key, &v), None => self.delete(col, &key), diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index e87aa231f..04a0920fa 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -47,6 +47,13 @@ pub enum TransactionError { /// Transaction gas price got: U256, }, + /// Transaction's gas is below currently set minimal gas requirement. + InsufficientGas { + /// Minimal expected gas + minimal: U256, + /// Transaction gas + got: U256, + }, /// Sender doesn't have enough funds to pay for this transaction InsufficientBalance { /// Senders balance @@ -63,6 +70,12 @@ pub enum TransactionError { }, /// Transaction's gas limit (aka gas) is invalid. InvalidGasLimit(OutOfBounds), + /// Transaction sender is banned. + SenderBanned, + /// Transaction receipient is banned. + RecipientBanned, + /// Contract creation code is banned. + CodeBanned, } impl fmt::Display for TransactionError { @@ -75,12 +88,17 @@ impl fmt::Display for TransactionError { LimitReached => "Transaction limit reached".into(), InsufficientGasPrice { minimal, got } => format!("Insufficient gas price. Min={}, Given={}", minimal, got), + InsufficientGas { minimal, got } => + format!("Insufficient gas. Min={}, Given={}", minimal, got), InsufficientBalance { balance, cost } => format!("Insufficient balance for transaction. Balance={}, Cost={}", balance, cost), GasLimitExceeded { limit, got } => format!("Gas limit exceeded. Limit={}, Given={}", limit, got), InvalidGasLimit(ref err) => format!("Invalid gas limit. {}", err), + SenderBanned => "Sender is temporarily banned.".into(), + RecipientBanned => "Recipient is temporarily banned.".into(), + CodeBanned => "Contract code is temporarily banned.".into(), }; f.write_fmt(format_args!("Transaction error ({})", msg)) diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index 62cfe77d6..e609bf542 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -173,7 +173,7 @@ lazy_static! { arr[SIGNEXTEND as usize] = InstructionInfo::new("SIGNEXTEND", 0, 2, 1, false, GasPriceTier::Low); arr[SHA3 as usize] = InstructionInfo::new("SHA3", 0, 2, 1, false, GasPriceTier::Special); arr[ADDRESS as usize] = InstructionInfo::new("ADDRESS", 0, 0, 1, false, GasPriceTier::Base); - arr[BALANCE as usize] = InstructionInfo::new("BALANCE", 0, 1, 1, false, GasPriceTier::Ext); + arr[BALANCE as usize] = InstructionInfo::new("BALANCE", 0, 1, 1, false, GasPriceTier::Special); arr[ORIGIN as usize] = InstructionInfo::new("ORIGIN", 0, 0, 1, false, GasPriceTier::Base); arr[CALLER as usize] = InstructionInfo::new("CALLER", 0, 0, 1, false, GasPriceTier::Base); arr[CALLVALUE as usize] = InstructionInfo::new("CALLVALUE", 0, 0, 1, false, GasPriceTier::Base); @@ -183,8 +183,8 @@ lazy_static! { arr[CODESIZE as usize] = InstructionInfo::new("CODESIZE", 0, 0, 1, false, GasPriceTier::Base); arr[CODECOPY as usize] = InstructionInfo::new("CODECOPY", 0, 3, 0, true, GasPriceTier::VeryLow); arr[GASPRICE as usize] = InstructionInfo::new("GASPRICE", 0, 0, 1, false, GasPriceTier::Base); - arr[EXTCODESIZE as usize] = InstructionInfo::new("EXTCODESIZE", 0, 1, 1, false, GasPriceTier::Ext); - arr[EXTCODECOPY as usize] = InstructionInfo::new("EXTCODECOPY", 0, 4, 0, true, GasPriceTier::Ext); + arr[EXTCODESIZE as usize] = InstructionInfo::new("EXTCODESIZE", 0, 1, 1, false, GasPriceTier::Special); + arr[EXTCODECOPY as usize] = InstructionInfo::new("EXTCODECOPY", 0, 4, 0, true, GasPriceTier::Special); arr[BLOCKHASH as usize] = InstructionInfo::new("BLOCKHASH", 0, 1, 1, false, GasPriceTier::Ext); arr[COINBASE as usize] = InstructionInfo::new("COINBASE", 0, 0, 1, false, GasPriceTier::Base); arr[TIMESTAMP as usize] = InstructionInfo::new("TIMESTAMP", 0, 0, 1, false, GasPriceTier::Base); @@ -277,7 +277,7 @@ lazy_static! { arr[CALLCODE as usize] = InstructionInfo::new("CALLCODE", 0, 7, 1, true, GasPriceTier::Special); arr[RETURN as usize] = InstructionInfo::new("RETURN", 0, 2, 0, true, GasPriceTier::Zero); arr[DELEGATECALL as usize] = InstructionInfo::new("DELEGATECALL", 0, 6, 1, true, GasPriceTier::Special); - arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Zero); + arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Special); arr }; } diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index 3fde3f664..a2b940655 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -30,12 +30,20 @@ macro_rules! overflowing { } #[cfg_attr(feature="dev", allow(enum_variant_names))] -enum InstructionCost { +enum Request { Gas(Cost), - GasMem(Cost, Cost, Option), + GasMem(Cost, Cost), + GasMemProvide(Cost, Cost, Option), GasMemCopy(Cost, Cost, Cost) } +pub struct InstructionRequirements { + pub gas_cost: Cost, + pub provide_gas: Option, + pub memory_total_gas: Cost, + pub memory_required_size: usize, +} + pub struct Gasometer { pub current_gas: Gas, pub current_mem_gas: Gas, @@ -59,11 +67,19 @@ impl Gasometer { /// 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 { + pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option) -> evm::Result { + // Try converting requested gas to `Gas` (`U256/u64`) + // but in EIP150 even if we request more we should never fail from OOG + let requested = requested.map(Gas::from_u256); + 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); + let max_gas_provided = match cap_divisor { + 64 => gas_remaining - (gas_remaining >> 6), + cap_divisor => gas_remaining - gas_remaining / Gas::from(cap_divisor), + }; + if let Some(Ok(r)) = requested { Ok(min(r, max_gas_provided)) } else { @@ -78,7 +94,7 @@ impl Gasometer { } else { Ok(0.into()) } - } + }, } } @@ -88,21 +104,21 @@ impl Gasometer { /// We guarantee that the final element of the returned tuple (`provided`) will be `Some` /// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case, /// it will be the amount of gas that the current context provides to the child context. - pub fn get_gas_cost_mem( + pub fn requirements( &mut self, ext: &evm::Ext, instruction: Instruction, info: &InstructionInfo, stack: &Stack, current_mem_size: usize, - ) -> evm::Result<(Gas, Gas, usize, Option)> { + ) -> evm::Result> { let schedule = ext.schedule(); let tier = instructions::get_tier_idx(info.tier); let default_gas = Gas::from(schedule.tier_step_gas[tier]); let cost = match instruction { instructions::JUMPDEST => { - InstructionCost::Gas(Gas::from(1)) + Request::Gas(Gas::from(1)) }, instructions::SSTORE => { let address = H256::from(stack.peek(0)); @@ -116,16 +132,16 @@ impl Gasometer { // !is_zero(&val) && is_zero(newval) schedule.sstore_reset_gas }; - InstructionCost::Gas(Gas::from(gas)) + Request::Gas(Gas::from(gas)) }, instructions::SLOAD => { - InstructionCost::Gas(Gas::from(schedule.sload_gas)) + Request::Gas(Gas::from(schedule.sload_gas)) }, instructions::BALANCE => { - InstructionCost::Gas(Gas::from(schedule.balance_gas)) + Request::Gas(Gas::from(schedule.balance_gas)) }, instructions::EXTCODESIZE => { - InstructionCost::Gas(Gas::from(schedule.extcodesize_gas)) + Request::Gas(Gas::from(schedule.extcodesize_gas)) }, instructions::SUICIDE => { let mut gas = Gas::from(schedule.suicide_gas); @@ -135,28 +151,28 @@ impl Gasometer { gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into())); } - InstructionCost::Gas(gas) + Request::Gas(gas) }, instructions::MSTORE | instructions::MLOAD => { - InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)), None) + Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32))) }, instructions::MSTORE8 => { - InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)), None) + Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1))) }, instructions::RETURN => { - InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None) + Request::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, 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))), None) + Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, instructions::CALLDATACOPY | instructions::CODECOPY => { - InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) + Request::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) }, instructions::EXTCODECOPY => { - InstructionCost::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) + Request::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); @@ -164,7 +180,7 @@ impl Gasometer { 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))), None) + Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, instructions::CALL | instructions::CALLCODE => { let mut gas = Gas::from(schedule.call_gas); @@ -183,70 +199,82 @@ impl Gasometer { gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into())); }; - // 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)); + let requested = *stack.peek(0); - InstructionCost::GasMem(gas, mem, Some(provided)) + Request::GasMemProvide(gas, mem, Some(requested)) }, instructions::DELEGATECALL => { - let mut gas = Gas::from(schedule.call_gas); + let 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))) ); + let requested = *stack.peek(0); - // 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)) + Request::GasMemProvide(gas, mem, Some(requested)) }, instructions::CREATE => { - let mut gas = Gas::from(schedule.create_gas); + let gas = Gas::from(schedule.create_gas); let mem = try!(mem_needed(stack.peek(1), stack.peek(2))); - // 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)) + Request::GasMemProvide(gas, mem, None) }, instructions::EXP => { let expon = stack.peek(1); let bytes = ((expon.bits() + 7) / 8) as usize; let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes); - InstructionCost::Gas(gas) + Request::Gas(gas) }, - _ => InstructionCost::Gas(default_gas), + _ => Request::Gas(default_gas), }; - match cost { - InstructionCost::Gas(gas) => { - Ok((gas, self.current_mem_gas, 0, None)) + Ok(match cost { + Request::Gas(gas) => { + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: 0, + memory_total_gas: self.current_mem_gas, + } }, - InstructionCost::GasMem(gas, mem_size, provided) => { + Request::GasMem(gas, mem_size) => { let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let gas = overflowing!(gas.overflow_add(mem_gas_cost)); - Ok((gas, new_mem_gas, new_mem_size, provided)) + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } }, - InstructionCost::GasMemCopy(gas, mem_size, copy) => { + Request::GasMemProvide(gas, mem_size, requested) => { + let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let gas = overflowing!(gas.overflow_add(mem_gas_cost)); + let provided = try!(self.gas_provided(schedule, gas, requested)); + let total_gas = overflowing!(gas.overflow_add(provided)); + + InstructionRequirements { + gas_cost: total_gas, + provide_gas: Some(provided), + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } + }, + Request::GasMemCopy(gas, mem_size, copy) => { let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let copy = overflowing!(add_gas_usize(copy, 31)) >> 5; 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, None)) - } - } + + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } + }, + }) } fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, Gas, usize)> { @@ -256,7 +284,7 @@ impl Gasometer { let a = overflowing!(s.overflow_mul(Gas::from(schedule.memory_gas))); // Calculate s*s/quad_coeff_div - debug_assert_eq!(schedule.quad_coeff_div, 512); + assert_eq!(schedule.quad_coeff_div, 512); let b = overflowing!(s.overflow_mul_shr(s, 9)); Ok(overflowing!(a.overflow_add(b))) }; @@ -328,3 +356,4 @@ fn test_calculate_mem_cost() { assert_eq!(new_mem_gas, 3); assert_eq!(mem_size, 32); } + diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index a39e09e79..bb9791abe 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -54,14 +54,14 @@ const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 000 /// Abstraction over raw vector of Bytes. Easier state management of PC. struct CodeReader<'a> { position: ProgramCounter, - code: &'a Bytes + code: &'a [u8] } #[cfg_attr(feature="dev", allow(len_without_is_empty))] impl<'a> CodeReader<'a> { /// Create new code reader - starting at position 0. - fn new(code: &'a Bytes) -> Self { + fn new(code: &'a [u8]) -> Self { CodeReader { position: 0, code: code, @@ -120,14 +120,14 @@ impl evm::Evm for Interpreter { try!(self.verify_instruction(ext, instruction, info, &stack)); // Calculate gas cost - let (gas_cost, mem_gas, mem_size, provided) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size())); + let requirements = try!(gasometer.requirements(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()); + let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &requirements.gas_cost.as_u256()); - try!(gasometer.verify_gas(&gas_cost)); - self.mem.expand(mem_size); - gasometer.current_mem_gas = mem_gas; - gasometer.current_gas = gasometer.current_gas - gas_cost; + try!(gasometer.verify_gas(&requirements.gas_cost)); + self.mem.expand(requirements.memory_required_size); + gasometer.current_mem_gas = requirements.memory_total_gas; + gasometer.current_gas = gasometer.current_gas - requirements.gas_cost; evm_debug!({ informant.before_instruction(reader.position, instruction, info, &gasometer.current_gas, &stack) }); @@ -138,7 +138,7 @@ impl evm::Evm for Interpreter { // Execute instruction let result = try!(self.exec_instruction( - gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, provided + gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, requirements.provide_gas )); evm_debug!({ informant.after_instruction(instruction) }); diff --git a/ethcore/src/evm/interpreter/shared_cache.rs b/ethcore/src/evm/interpreter/shared_cache.rs index dee557522..cacc4dde3 100644 --- a/ethcore/src/evm/interpreter/shared_cache.rs +++ b/ethcore/src/evm/interpreter/shared_cache.rs @@ -15,20 +15,27 @@ // along with Parity. If not, see . use std::sync::Arc; -use lru_cache::LruCache; -use util::{H256, Mutex}; +use util::{H256, HeapSizeOf, Mutex}; use util::sha3::*; +use util::cache::MemoryLruCache; use bit_set::BitSet; use super::super::instructions; -const INITIAL_CAPACITY: usize = 32; const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024; +// stub for a HeapSizeOf implementation. +struct Bits(Arc); + +impl HeapSizeOf for Bits { + fn heap_size_of_children(&self) -> usize { + // dealing in bits here + self.0.capacity() * 8 + } +} + /// Global cache for EVM interpreter pub struct SharedCache { - jump_destinations: Mutex>>, - max_size: usize, - cur_size: Mutex, + jump_destinations: Mutex>, } impl SharedCache { @@ -36,9 +43,7 @@ impl SharedCache { /// 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), + jump_destinations: Mutex::new(MemoryLruCache::new(max_size)), } } @@ -49,37 +54,11 @@ impl SharedCache { } if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) { - return d.clone(); + return d.0.clone(); } let d = Self::find_jump_destinations(code); - - { - 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, - } - } - } + self.jump_destinations.lock().insert(code_hash.clone(), Bits(d.clone())); d } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 99ef5209b..cb3ca29e1 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -101,6 +101,7 @@ extern crate bit_set; extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; +extern crate transient_hashmap; #[macro_use] extern crate log; diff --git a/ethcore/src/migrations/v10.rs b/ethcore/src/migrations/v10.rs index 88884fb26..77531eb08 100644 --- a/ethcore/src/migrations/v10.rs +++ b/ethcore/src/migrations/v10.rs @@ -61,7 +61,7 @@ pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), let account_trie = try!(TrieDB::new(state_db.as_hashdb(), &state_root).map_err(|e| Error::Custom(format!("Cannot open trie: {:?}", e)))); for item in try!(account_trie.iter().map_err(|_| Error::MigrationImpossible)) { let (ref account_key, _) = try!(item.map_err(|_| Error::MigrationImpossible)); - let account_key_hash = H256::from_slice(&account_key); + let account_key_hash = H256::from_slice(account_key); bloom.set(&*account_key_hash); } diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs new file mode 100644 index 000000000..0329503bf --- /dev/null +++ b/ethcore/src/miner/banning_queue.rs @@ -0,0 +1,339 @@ +// 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 . + +//! Banning Queue +//! Transacton Queue wrapper maintaining additional list of banned senders and contract hashes. + +use std::time::Duration; +use std::ops::{Deref, DerefMut}; +use std::cell::Cell; +use transaction::{SignedTransaction, Action}; +use transient_hashmap::TransientHashMap; +use miner::{TransactionQueue, TransactionImportResult, TransactionOrigin, AccountDetails}; +use error::{Error, TransactionError}; +use util::{Uint, U256, H256, Address, Hashable}; + +type Count = u16; + +/// Auto-Banning threshold +pub enum Threshold { + /// Should ban after given number of misbehaves reported. + BanAfter(Count), + /// Should never ban anything + NeverBan +} + +impl Default for Threshold { + fn default() -> Self { + Threshold::NeverBan + } +} + +/// Transaction queue with banlist. +pub struct BanningTransactionQueue { + queue: TransactionQueue, + ban_threshold: Threshold, + senders_bans: TransientHashMap>, + recipients_bans: TransientHashMap>, + codes_bans: TransientHashMap>, +} + +impl BanningTransactionQueue { + /// Creates new banlisting transaction queue + pub fn new(queue: TransactionQueue, ban_threshold: Threshold, ban_lifetime: Duration) -> Self { + let ban_lifetime_sec = ban_lifetime.as_secs(); + assert!(ban_lifetime_sec > 0, "Lifetime has to be specified in seconds."); + BanningTransactionQueue { + queue: queue, + ban_threshold: ban_threshold, + senders_bans: TransientHashMap::new(ban_lifetime_sec), + recipients_bans: TransientHashMap::new(ban_lifetime_sec), + codes_bans: TransientHashMap::new(ban_lifetime_sec), + } + } + + /// Borrows internal queue. + /// NOTE: you can insert transactions to the queue even + /// if they would be rejected because of ban otherwise. + /// But probably you shouldn't. + pub fn queue(&mut self) -> &mut TransactionQueue { + &mut self.queue + } + + /// Add to the queue taking bans into consideration. + /// May reject transaction because of the banlist. + pub fn add_with_banlist( + &mut self, + transaction: SignedTransaction, + account_details: &F, + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { + if let Threshold::BanAfter(threshold) = self.ban_threshold { + // NOTE In all checks use direct query to avoid increasing ban timeout. + + // Check sender + if let Ok(sender) = transaction.sender() { + let count = self.senders_bans.direct().get(&sender).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because sender is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::SenderBanned)); + } + } + + // Check recipient + if let Action::Call(recipient) = transaction.action { + let count = self.recipients_bans.direct().get(&recipient).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because recipient is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::RecipientBanned)); + } + } + + // Check code + if let Action::Create = transaction.action { + let code_hash = transaction.data.sha3(); + let count = self.codes_bans.direct().get(&code_hash).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because code is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::CodeBanned)); + } + } + } + self.queue.add(transaction, TransactionOrigin::External, account_details, gas_estimator) + } + + /// Ban transaction with given hash. + /// Transaction has to be in the queue. + /// + /// Bans sender and recipient/code and returns `true` when any ban has reached threshold. + pub fn ban_transaction(&mut self, hash: &H256) -> bool { + let transaction = self.queue.find(hash); + match transaction { + Some(transaction) => { + let sender = transaction.sender().expect("Transaction is in queue, so the sender is already validated; qed"); + // Ban sender + let sender_banned = self.ban_sender(sender); + // Ban recipient and codehash + let is_banned = sender_banned || match transaction.action { + Action::Call(recipient) => { + self.ban_recipient(recipient) + }, + Action::Create => { + self.ban_codehash(transaction.data.sha3()) + }, + }; + is_banned + }, + None => false, + } + } + + /// Ban given sender. + /// If bans threshold is reached all subsequent transactions from this sender will be rejected. + /// Reaching bans threshold also removes all existsing transaction from this sender that are already in the + /// queue. + fn ban_sender(&mut self, address: Address) -> bool { + let count = { + let mut count = self.senders_bans.entry(address).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + count.get() + }; + match self.ban_threshold { + Threshold::BanAfter(threshold) if count > threshold => { + // Banlist the sender. + // Remove all transactions from the queue. + self.remove_all(address, !U256::zero()); + true + }, + _ => false + } + } + + /// Ban given recipient. + /// If bans threshold is reached all subsequent transactions to this address will be rejected. + /// Returns true if bans threshold has been reached. + fn ban_recipient(&mut self, address: Address) -> bool { + let count = { + let mut count = self.recipients_bans.entry(address).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + count.get() + }; + match self.ban_threshold { + // TODO [ToDr] Consider removing other transactions to the same recipient from the queue? + Threshold::BanAfter(threshold) if count > threshold => true, + _ => false + } + } + + + /// Ban given codehash. + /// If bans threshold is reached all subsequent transactions to contracts with this codehash will be rejected. + /// Returns true if bans threshold has been reached. + fn ban_codehash(&mut self, code_hash: H256) -> bool { + let mut count = self.codes_bans.entry(code_hash).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + + match self.ban_threshold { + // TODO [ToDr] Consider removing other transactions with the same code from the queue? + Threshold::BanAfter(threshold) if count.get() > threshold => true, + _ => false, + } + } +} + +impl Deref for BanningTransactionQueue { + type Target = TransactionQueue; + + fn deref(&self) -> &Self::Target { + &self.queue + } +} +impl DerefMut for BanningTransactionQueue { + fn deref_mut(&mut self) -> &mut Self::Target { + self.queue() + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + use super::{BanningTransactionQueue, Threshold}; + use ethkey::{Random, Generator}; + use transaction::{Transaction, SignedTransaction, Action}; + use error::{Error, TransactionError}; + use client::TransactionImportResult; + use miner::{TransactionQueue, TransactionOrigin, AccountDetails}; + use util::{Uint, U256, Address, FromHex, Hashable}; + + fn queue() -> BanningTransactionQueue { + BanningTransactionQueue::new(TransactionQueue::default(), Threshold::BanAfter(1), Duration::from_secs(180)) + } + + fn default_account_details(_address: &Address) -> AccountDetails { + AccountDetails { + nonce: U256::zero(), + balance: !U256::zero(), + } + } + + fn gas_required(_tx: &SignedTransaction) -> U256 { + 0.into() + } + + fn transaction(action: Action) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: action, + value: U256::from(100), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::from(10), + nonce: U256::from(0), + }.sign(keypair.secret()) + } + + fn unwrap_err(res: Result) -> TransactionError { + match res { + Err(Error::Transaction(e)) => e, + Ok(x) => panic!("Expected error, got: Ok({:?})", x), + Err(e) => panic!("Unexpected error type returned by queue: {:?}", e), + } + } + + #[test] + fn should_allow_to_borrow_the_queue() { + // given + let tx = transaction(Action::Create); + let mut txq = queue(); + + // when + txq.queue().add(tx, TransactionOrigin::External, &default_account_details, &gas_required).unwrap(); + + // then + // should also deref to queue + assert_eq!(txq.status().pending, 1); + } + + #[test] + fn should_not_accept_transactions_from_banned_sender() { + // given + let tx = transaction(Action::Create); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_sender(tx.sender().unwrap()); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_sender(tx.sender().unwrap()); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::SenderBanned); + // Should also remove transacion from the queue + assert_eq!(txq.find(&tx.hash()), None); + } + + #[test] + fn should_not_accept_transactions_to_banned_recipient() { + // given + let recipient = Address::default(); + let tx = transaction(Action::Call(recipient)); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_recipient(recipient); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_recipient(recipient); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::RecipientBanned); + } + + #[test] + fn should_not_accept_transactions_with_banned_code() { + // given + let tx = transaction(Action::Create); + let codehash = tx.data.sha3(); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_codehash(codehash); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_codehash(codehash); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::CodeBanned); + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3e87437e8..6d5a3f735 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -31,6 +31,7 @@ use receipt::{Receipt, RichReceipt}; use spec::Spec; use engines::Engine; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; +use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; use client::TransactionImportResult; use miner::price_info::PriceInfo; @@ -59,6 +60,22 @@ pub enum GasLimit { Fixed(U256), } +/// Transaction queue banning settings. +#[derive(Debug, PartialEq, Clone)] +pub enum Banning { + /// Banning in transaction queue is disabled + Disabled, + /// Banning in transaction queue is enabled + Enabled { + /// Upper limit of transaction processing time before banning. + offend_threshold: Duration, + /// Number of similar offending transactions before banning. + min_offends: u16, + /// Number of seconds the offender is banned for. + ban_duration: Duration, + }, +} + /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { @@ -86,6 +103,8 @@ pub struct MinerOptions { pub enable_resubmission: bool, /// Global gas limit for all transaction in the queue except for local and retracted. pub tx_queue_gas_limit: GasLimit, + /// Banning settings + pub tx_queue_banning: Banning, } impl Default for MinerOptions { @@ -98,11 +117,12 @@ impl Default for MinerOptions { tx_gas_limit: !U256::zero(), tx_queue_size: 1024, tx_queue_gas_limit: GasLimit::Auto, - tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice, + tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), work_queue_size: 20, enable_resubmission: true, + tx_queue_banning: Banning::Disabled, } } } @@ -186,7 +206,7 @@ struct SealingWork { /// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { // NOTE [ToDr] When locking always lock in this order! - transaction_queue: Arc>, + transaction_queue: Arc>, sealing_work: Mutex, next_allowed_reseal: Mutex, sealing_block_last_request: Mutex, @@ -215,11 +235,18 @@ impl Miner { GasLimit::Fixed(ref limit) => *limit, _ => !U256::zero(), }; - let txq = Arc::new(Mutex::new(TransactionQueue::with_limits( - options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit - ))); + + let txq = TransactionQueue::with_limits(options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit); + let txq = match options.tx_queue_banning { + Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), + Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( + txq, + Threshold::BanAfter(min_offends), + ban_duration, + ), + }; Miner { - transaction_queue: txq, + transaction_queue: Arc::new(Mutex::new(txq)), next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), sealing_work: Mutex::new(SealingWork{ @@ -323,10 +350,31 @@ impl Miner { let mut invalid_transactions = HashSet::new(); let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); - // TODO: push new uncles, too. + + // TODO Push new uncles too. for tx in transactions { let hash = tx.hash(); - match open_block.push_transaction(tx, None) { + let start = Instant::now(); + let result = open_block.push_transaction(tx, None); + let took = start.elapsed(); + + // Check for heavy transactions + match self.options.tx_queue_banning { + Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { + match self.transaction_queue.lock().ban_transaction(&hash) { + true => { + warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); + }, + false => { + transactions_to_penalize.insert(hash); + debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") + } + } + }, + _ => {}, + } + + match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -367,7 +415,7 @@ impl Miner { { let mut queue = self.transaction_queue.lock(); - for hash in invalid_transactions.into_iter() { + for hash in invalid_transactions { queue.remove_invalid(&hash, &fetch_account); } for hash in transactions_to_penalize { @@ -512,7 +560,7 @@ impl Miner { prepare_new } - fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut TransactionQueue) -> + fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> Vec> { let fetch_account = |a: &Address| AccountDetails { @@ -520,14 +568,25 @@ impl Miner { balance: chain.latest_balance(a), }; + let schedule = chain.latest_schedule(); + let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); transactions.into_iter() - .map(|tx| transaction_queue.add(tx, &fetch_account, origin)) + .map(|tx| match origin { + TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { + transaction_queue.add(tx, origin, &fetch_account, &gas_required) + }, + TransactionOrigin::External => { + transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) + } + }) .collect() } /// Are we allowed to do a non-mandatory reseal? fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } + #[cfg_attr(feature="dev", allow(wrong_self_convention))] + #[cfg_attr(feature="dev", allow(redundant_closure))] fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H where F: Fn() -> H, G: Fn(&ClosedBlock) -> H { let sealing_work = self.sealing_work.lock(); @@ -891,7 +950,7 @@ impl MinerService for Miner { fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap { self.from_pending_block( best_block, - || BTreeMap::new(), + BTreeMap::new, |pending| { let hashes = pending.transactions() .iter() @@ -1025,7 +1084,7 @@ impl MinerService for Miner { tx.sender().expect("Transaction is in block, so sender has to be defined.") }) .collect::>(); - for sender in to_remove.into_iter() { + for sender in to_remove { transaction_queue.remove_all(sender, chain.latest_nonce(&sender)); } }); @@ -1103,6 +1162,7 @@ mod tests { pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, + tx_queue_banning: Banning::Disabled, }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 5fe8dbf44..145d790dd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -44,11 +44,12 @@ mod miner; mod external; mod transaction_queue; +mod banning_queue; mod work_notify; mod price_info; pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; +pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use client::TransactionImportResult; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index fa0cce1e6..f8baf8989 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -48,10 +48,11 @@ //! nonce: U256::from(10), //! balance: U256::from(1_000_000), //! }; +//! let gas_estimator = |_tx: &SignedTransaction| 2.into(); //! //! 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(); +//! txq.add(st2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); +//! txq.add(st1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); //! //! // Check status //! assert_eq!(txq.status().pending, 2); @@ -446,6 +447,7 @@ pub struct AccountDetails { const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) % /// Describes the strategy used to prioritize transactions in the queue. +#[cfg_attr(feature="dev", allow(enum_variant_names))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PrioritizationStrategy { /// Use only gas price. Disregards the actual computation cost of the transaction. @@ -592,9 +594,20 @@ impl TransactionQueue { } } - /// Add signed transaction to queue to be verified and imported - pub fn add(&mut self, tx: SignedTransaction, fetch_account: &T, origin: TransactionOrigin) -> Result - where T: Fn(&Address) -> AccountDetails { + /// Add signed transaction to queue to be verified and imported. + /// + /// NOTE fetch_account and gas_estimator should be cheap to compute + /// otherwise it might open up an attack vector. + pub fn add( + &mut self, + tx: SignedTransaction, + origin: TransactionOrigin, + fetch_account: &F, + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "txqueue", @@ -625,8 +638,6 @@ impl TransactionQueue { })); } - try!(tx.check_low_s()); - if tx.gas > self.gas_limit || tx.gas > self.tx_gas_limit { trace!(target: "txqueue", "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", @@ -642,6 +653,24 @@ impl TransactionQueue { })); } + let minimal_gas = gas_estimator(&tx); + if tx.gas < minimal_gas { + trace!(target: "txqueue", + "Dropping transaction with insufficient gas: {:?} ({} > {})", + tx.hash(), + tx.gas, + minimal_gas, + ); + + return Err(Error::Transaction(TransactionError::InsufficientGas { + minimal: minimal_gas, + got: tx.gas, + })); + } + + // Verify signature + try!(tx.check_low_s()); + let vtx = try!(VerifiedTransaction::new(tx, origin)); let client_account = fetch_account(&vtx.sender()); @@ -904,16 +933,6 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); - { - // Rough size sanity check - let gas = &tx.transaction.gas; - if U256::from(tx.transaction.data.len()) > *gas { - // Droping transaction - trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); - return Err(TransactionError::LimitReached); - } - } - // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. @@ -1103,6 +1122,10 @@ mod test { } } + fn gas_estimator(_tx: &SignedTransaction) -> U256 { + U256::zero() + } + fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { 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); @@ -1154,14 +1177,14 @@ mod test { 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(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); // when let tx = new_tx(123.into(), 1.into()); - let res = txq.add(tx.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then // No longer the case as we don't even consider a transaction that isn't above a full @@ -1317,12 +1340,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx, &prev_nonce, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // and then there should be only one transaction in current (the one with higher gas_price) assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1342,12 +1365,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External); + let res = txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1366,7 +1389,7 @@ mod test { let tx = new_tx_default(); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1385,10 +1408,10 @@ mod test { 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); + let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1419,10 +1442,10 @@ mod test { 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); + let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1465,7 +1488,7 @@ mod test { txq.set_gas_limit(limit); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::GasLimitExceeded { @@ -1489,7 +1512,7 @@ mod test { }; // when - let res = txq.add(tx, &account, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &account, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientBalance { @@ -1509,7 +1532,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientGasPrice { @@ -1529,7 +1552,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::Local); + let res = txq.add(tx, TransactionOrigin::Local, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1559,7 +1582,7 @@ mod test { rlp::decode(s.as_raw()) }; // when - let res = txq.add(stx, &default_account_details, TransactionOrigin::External); + let res = txq.add(stx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert!(res.is_err()); @@ -1573,8 +1596,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1593,9 +1616,9 @@ mod test { // when // first insert the one with higher gas price - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but local - txq.add(tx.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1614,9 +1637,9 @@ mod test { // when // first insert local one with higher gas price - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but from retracted block - txq.add(tx.clone(), &default_account_details, TransactionOrigin::RetractedBlock).unwrap(); + txq.add(tx.clone(), TransactionOrigin::RetractedBlock, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1632,8 +1655,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1652,10 +1675,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(txb.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 4); @@ -1681,10 +1704,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(txb.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); let top = txq.top_transactions(); assert_eq!(top[0], tx1); @@ -1713,8 +1736,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.pending_hashes(); @@ -1731,8 +1754,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); // when - let res1 = txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - let res2 = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + let res1 = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(res1, TransactionImportResult::Current); @@ -1755,8 +1778,8 @@ mod test { 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(); - txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -1778,13 +1801,13 @@ mod test { 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(); + txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); // when - txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -1800,8 +1823,8 @@ mod test { // given 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(); + txq2.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq2.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq2.status().pending, 1); assert_eq!(txq2.status().future, 1); @@ -1822,10 +1845,10 @@ mod test { 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(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -1844,8 +1867,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // add - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); let stats = txq.status(); assert_eq!(stats.pending, 2); @@ -1864,11 +1887,11 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); // when - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then let t = txq.top_transactions(); @@ -1885,14 +1908,14 @@ mod test { 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()); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(txq.status().future, 1); @@ -1903,11 +1926,11 @@ mod test { 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).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // limited by gas - txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap_err(); + txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); assert_eq!(txq.status().pending, 2); } @@ -1917,13 +1940,13 @@ mod test { 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(); + txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).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(); + txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); + txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 4); } @@ -1935,7 +1958,7 @@ mod test { let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() }; // when - let res = txq.add(tx, &fetch_last_nonce, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &fetch_last_nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::Old); @@ -1951,12 +1974,12 @@ mod test { balance: !U256::zero() }; 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(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); assert_eq!(txq.status().pending, 0); // when - let res = txq.add(tx2.clone(), &nonce, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::AlreadyImported); @@ -1970,15 +1993,15 @@ mod test { // given 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(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when txq.remove_invalid(&tx1.hash(), &default_account_details); assert_eq!(txq.status().pending, 0); assert_eq!(txq.status().future, 1); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -1992,10 +2015,10 @@ mod test { 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(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -2022,8 +2045,8 @@ mod test { }; // when - txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2050,10 +2073,10 @@ mod test { }; // when - txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx0, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx0, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2071,8 +2094,8 @@ mod test { !U256::zero() }; 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(); + txq.add(tx1.clone(), TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -2103,7 +2126,7 @@ mod test { let details = |_a: &Address| AccountDetails { nonce: nonce, balance: !U256::zero() }; // when - txq.add(tx, &details, TransactionOrigin::External).unwrap(); + txq.add(tx, TransactionOrigin::External, &details, &gas_estimator).unwrap(); // then assert_eq!(txq.last_nonce(&from), Some(nonce)); @@ -2118,7 +2141,7 @@ mod test { let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() }; // Insert first transaction - txq.add(tx1, &details1, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(); // when txq.remove_all(tx2.sender().unwrap(), nonce2 + U256::one()); @@ -2138,9 +2161,9 @@ mod test { // when // Insert first transaction - assert_eq!(txq.add(tx1, &details1, TransactionOrigin::External).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current); // Second should go to future - assert_eq!(txq.add(tx2, &details1, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future); // Now block is imported txq.remove_all(sender, nonce2 - U256::from(1)); // tx2 should be not be promoted to current @@ -2159,9 +2182,9 @@ mod test { assert_eq!(txq.has_local_pending_transactions(), false); // when - assert_eq!(txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); assert_eq!(txq.has_local_pending_transactions(), false); - assert_eq!(txq.add(tx2, &default_account_details, TransactionOrigin::Local).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); // then assert_eq!(txq.has_local_pending_transactions(), true); @@ -2176,8 +2199,8 @@ mod test { default_account_details(a).balance }; // when - assert_eq!(txq.add(tx2, &prev_nonce, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); - assert_eq!(txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); // then assert_eq!(txq.future.by_priority.len(), 1); @@ -2202,14 +2225,14 @@ mod test { (tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret)) }; let sender = tx1.sender().unwrap(); - txq.add(tx1, &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx3, &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.future.by_priority.len(), 0); assert_eq!(txq.current.by_priority.len(), 3); // when - let res = txq.add(tx2_2, &default_account_details, TransactionOrigin::Local); + let res = txq.add(tx2_2, TransactionOrigin::Local, &default_account_details, &gas_estimator); // then assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); @@ -2217,4 +2240,24 @@ mod test { assert_eq!(txq.current.by_priority.len(), 3); } + #[test] + fn should_reject_transactions_below_bas_gas() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let high_gas = |_: &SignedTransaction| 100_001.into(); + + // when + let res1 = txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::Local, &default_account_details, &high_gas); + + // then + assert_eq!(res1.unwrap(), TransactionImportResult::Current); + assert_eq!(unwrap_tx_err(res2), TransactionError::InsufficientGas { + minimal: 100_001.into(), + got: 100_000.into(), + }); + + } + } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index d7f1096a6..08ab78116 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -89,7 +89,7 @@ impl ClientService { db_config.set_cache(::db::COL_STATE, size); } - db_config.compaction = config.db_compaction.compaction_profile(&client_path); + db_config.compaction = config.db_compaction.compaction_profile(client_path); db_config.wal = config.db_wal; let pruning = config.pruning; diff --git a/ethcore/src/snapshot/error.rs b/ethcore/src/snapshot/error.rs index acd9409f7..d634057dc 100644 --- a/ethcore/src/snapshot/error.rs +++ b/ethcore/src/snapshot/error.rs @@ -33,6 +33,12 @@ pub enum Error { BlockNotFound(H256), /// Incomplete chain. IncompleteChain, + /// Best block has wrong state root. + WrongStateRoot(H256, H256), + /// Wrong block hash. + WrongBlockHash(u64, H256, H256), + /// Too many blocks contained within the snapshot. + TooManyBlocks(u64, u64), /// Old starting block in a pruned database. OldBlockPrunedDB, /// Missing code. @@ -52,7 +58,11 @@ impl fmt::Display for Error { match *self { Error::InvalidStartingBlock(ref id) => write!(f, "Invalid starting block: {:?}", id), Error::BlockNotFound(ref hash) => write!(f, "Block not found in chain: {}", hash), - Error::IncompleteChain => write!(f, "Cannot create snapshot due to incomplete chain."), + Error::IncompleteChain => write!(f, "Incomplete blockchain."), + Error::WrongStateRoot(ref expected, ref found) => write!(f, "Final block has wrong state root. Expected {:?}, got {:?}", expected, found), + Error::WrongBlockHash(ref num, ref expected, ref found) => + write!(f, "Block {} had wrong hash. expected {:?}, got {:?}", num, expected, found), + Error::TooManyBlocks(ref expected, ref found) => write!(f, "Snapshot contained too many blocks. Expected {}, got {}", expected, found), Error::OldBlockPrunedDB => write!(f, "Attempted to create a snapshot at an old block while using \ a pruned database. Please re-run with the --pruning archive flag."), Error::MissingCode(ref missing) => write!(f, "Incomplete snapshot: {} contract codes not found.", missing.len()), diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 4fa00f771..22c44ba3b 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -26,6 +26,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use account_db::{AccountDB, AccountDBMut}; use blockchain::{BlockChain, BlockProvider}; use engines::Engine; +use header::Header; use ids::BlockID; use views::BlockView; @@ -202,7 +203,7 @@ impl<'a> BlockChunker<'a> { // cut off the chunk if too large. - if new_loaded_size > PREFERRED_CHUNK_SIZE && self.rlps.len() > 0 { + if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() { try!(self.write_chunk(last)); loaded_size = pair.len(); } else { @@ -528,6 +529,20 @@ fn rebuild_accounts( /// Proportion of blocks which we will verify `PoW` for. const POW_VERIFY_RATE: f32 = 0.02; +/// Verify an old block with the given header, engine, blockchain, body. If `always` is set, it will perform +/// the fullest verification possible. If not, it will take a random sample to determine whether it will +/// do heavy or light verification. +pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> { + if always || rng.gen::() <= POW_VERIFY_RATE { + match chain.block_header(header.parent_hash()) { + Some(parent) => engine.verify_block_family(&header, &parent, body), + None => engine.verify_block_seal(&header), + } + } else { + engine.verify_block_basic(&header, body) + } +} + /// Rebuilds the blockchain from chunks. /// /// Does basic verification for all blocks, but `PoW` verification for some. @@ -543,17 +558,23 @@ pub struct BlockRebuilder { rng: OsRng, disconnected: Vec<(u64, H256)>, best_number: u64, + best_hash: H256, + best_root: H256, + fed_blocks: u64, } impl BlockRebuilder { /// Create a new BlockRebuilder. - pub fn new(chain: BlockChain, db: Arc, best_number: u64) -> Result { + pub fn new(chain: BlockChain, db: Arc, manifest: &ManifestData) -> Result { Ok(BlockRebuilder { chain: chain, db: db, rng: try!(OsRng::new()), disconnected: Vec::new(), - best_number: best_number, + best_number: manifest.block_number, + best_hash: manifest.block_hash, + best_root: manifest.state_root, + fed_blocks: 0, }) } @@ -566,9 +587,14 @@ impl BlockRebuilder { let rlp = UntrustedRlp::new(chunk); let item_count = rlp.item_count(); + let num_blocks = (item_count - 3) as u64; trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3); + if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS { + return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into()) + } + // todo: assert here that these values are consistent with chunks being in order. let mut cur_number = try!(rlp.val_at::(0)) + 1; let mut parent_hash = try!(rlp.val_at::(1)); @@ -585,14 +611,27 @@ impl BlockRebuilder { let block = try!(abridged_block.to_block(parent_hash, cur_number, receipts_root)); let block_bytes = block.rlp_bytes(With); + let is_best = cur_number == self.best_number; - if self.rng.gen::() <= POW_VERIFY_RATE { - try!(engine.verify_block_seal(&block.header)) - } else { - try!(engine.verify_block_basic(&block.header, Some(&block_bytes))); + if is_best { + if block.header.hash() != self.best_hash { + return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into()) + } + + if block.header.state_root() != &self.best_root { + return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into()) + } } - let is_best = cur_number == self.best_number; + try!(verify_old_block( + &mut self.rng, + &block.header, + engine, + &self.chain, + Some(&block_bytes), + is_best + )); + let mut batch = self.db.transaction(); // special-case the first block in each chunk. @@ -610,11 +649,15 @@ impl BlockRebuilder { cur_number += 1; } - Ok(item_count as u64 - 3) + self.fed_blocks += num_blocks; + + Ok(num_blocks) } - /// Glue together any disconnected chunks. To be called at the end. - pub fn glue_chunks(self) { + /// Glue together any disconnected chunks and check that the chain is complete. + pub fn finalize(self, canonical: HashMap) -> Result<(), Error> { + let mut batch = self.db.transaction(); + for (first_num, first_hash) in self.disconnected { let parent_num = first_num - 1; @@ -623,8 +666,23 @@ impl BlockRebuilder { // the first block of the first chunks has nothing to connect to. if let Some(parent_hash) = self.chain.block_hash(parent_num) { // if so, add the child to it. - self.chain.add_child(parent_hash, first_hash); + self.chain.add_child(&mut batch, parent_hash, first_hash); } } + self.db.write_buffered(batch); + + let best_number = self.best_number; + for num in (0..self.fed_blocks).map(|x| best_number - x) { + + let hash = try!(self.chain.block_hash(num).ok_or(Error::IncompleteChain)); + + if let Some(canon_hash) = canonical.get(&num).cloned() { + if canon_hash != hash { + return Err(Error::WrongBlockHash(num, canon_hash, hash)); + } + } + } + + Ok(()) } } diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 9b66a5cdc..cc30a5c26 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -16,7 +16,7 @@ //! Snapshot network service implementation. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::io::ErrorKind; use std::fs; use std::path::PathBuf; @@ -74,6 +74,7 @@ struct Restoration { snappy_buffer: Bytes, final_state_root: H256, guard: Guard, + canonical_hashes: HashMap, db: Arc, } @@ -99,7 +100,7 @@ impl Restoration { .map_err(UtilError::SimpleString))); let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); - let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), manifest.block_number)); + let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), &manifest)); let root = manifest.state_root.clone(); Ok(Restoration { @@ -112,6 +113,7 @@ impl Restoration { snappy_buffer: Vec::new(), final_state_root: root, guard: params.guard, + canonical_hashes: HashMap::new(), db: raw_db, }) } @@ -138,13 +140,18 @@ impl Restoration { try!(self.blocks.feed(&self.snappy_buffer[..len], engine)); if let Some(ref mut writer) = self.writer.as_mut() { - try!(writer.write_block_chunk(hash, chunk)); + try!(writer.write_block_chunk(hash, chunk)); } } Ok(()) } + // note canonical hashes. + fn note_canonical(&mut self, hashes: &[(u64, H256)]) { + self.canonical_hashes.extend(hashes.iter().cloned()); + } + // finish up restoration. fn finalize(self) -> Result<(), Error> { use util::trie::TrieError; @@ -161,8 +168,8 @@ impl Restoration { // check for missing code. try!(self.state.check_missing()); - // connect out-of-order chunks. - self.blocks.glue_chunks(); + // connect out-of-order chunks and verify chain integrity. + try!(self.blocks.finalize(self.canonical_hashes)); if let Some(writer) = self.writer { try!(writer.finish(self.manifest)); @@ -352,7 +359,8 @@ impl Service { // "Cancelled" is mincing words a bit -- what really happened // is that the state we were snapshotting got pruned out // before we could finish. - info!("Cancelled prematurely-started periodic snapshot."); + info!("Periodic snapshot failed: block state pruned.\ + Run with a longer `--pruning-history` or with `--no-periodic-snapshot`"); return Ok(()) } else { return Err(e); @@ -580,6 +588,14 @@ impl SnapshotService for Service { trace!("Error sending snapshot service message: {:?}", e); } } + + fn provide_canon_hashes(&self, canonical: &[(u64, H256)]) { + let mut rest = self.restoration.lock(); + + if let Some(ref mut rest) = rest.as_mut() { + rest.note_canonical(canonical); + } + } } impl Drop for Service { diff --git a/ethcore/src/snapshot/snapshot_service_trait.rs b/ethcore/src/snapshot/snapshot_service_trait.rs index 65448090f..42223f878 100644 --- a/ethcore/src/snapshot/snapshot_service_trait.rs +++ b/ethcore/src/snapshot/snapshot_service_trait.rs @@ -48,6 +48,10 @@ pub trait SnapshotService : Sync + Send { /// Feed a raw block chunk to the service to be processed asynchronously. /// no-op if currently restoring. fn restore_block_chunk(&self, hash: H256, chunk: Bytes); + + /// Give the restoration in-progress some canonical block hashes for + /// extra verification (performed at the end) + fn provide_canon_hashes(&self, canonical: &[(u64, H256)]); } impl IpcConfig for SnapshotService { } diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 62c6ea2fe..12efcda77 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -26,6 +26,7 @@ use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use util::{Mutex, snappy}; use util::kvdb::{Database, DatabaseConfig}; +use std::collections::HashMap; use std::sync::Arc; fn chunk_and_restore(amount: u64) { @@ -58,18 +59,20 @@ fn chunk_and_restore(amount: u64) { // snapshot it. let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap()); let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap(); - writer.into_inner().finish(::snapshot::ManifestData { + let manifest = ::snapshot::ManifestData { state_hashes: Vec::new(), block_hashes: block_hashes, - state_root: Default::default(), + state_root: ::util::sha3::SHA3_NULL_RLP, block_number: amount, block_hash: best_hash, - }).unwrap(); + }; + + writer.into_inner().finish(manifest.clone()).unwrap(); // 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, new_db.clone(), amount).unwrap(); + let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).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 { @@ -78,7 +81,7 @@ fn chunk_and_restore(amount: u64) { rebuilder.feed(&chunk, &engine).unwrap(); } - rebuilder.glue_chunks(); + rebuilder.finalize(HashMap::new()).unwrap(); // and test it. let new_chain = BlockChain::new(Default::default(), &genesis, new_db); diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index b26c79cba..d8d281b17 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -247,23 +247,34 @@ impl Account { } /// Provide a database to get `code_hash`. Should not be called if it is a contract without code. - pub fn cache_code(&mut self, db: &HashDB) -> bool { + pub fn cache_code(&mut self, db: &HashDB) -> Option> { // TODO: fill out self.code_cache; trace!("Account::cache_code: ic={}; self.code_hash={:?}, self.code_cache={}", self.is_cached(), self.code_hash, self.code_cache.pretty()); - self.is_cached() || + + if self.is_cached() { return Some(self.code_cache.clone()) } + match db.get(&self.code_hash) { Some(x) => { self.code_size = Some(x.len()); self.code_cache = Arc::new(x.to_vec()); - true + Some(self.code_cache.clone()) }, _ => { warn!("Failed reverse get of {}", self.code_hash); - false + None }, } } + /// Provide code to cache. For correctness, should be the correct code for the + /// account. + pub fn cache_given_code(&mut self, code: Arc) { + trace!("Account::cache_given_code: ic={}; self.code_hash={:?}, self.code_cache={}", self.is_cached(), self.code_hash, self.code_cache.pretty()); + + self.code_size = Some(code.len()); + self.code_cache = code; + } + /// Provide a database to get `code_size`. Should not be called if it is a contract without code. pub fn cache_code_size(&mut self, db: &HashDB) -> bool { // TODO: fill out self.code_cache; @@ -413,7 +424,7 @@ impl Account { self.code_size = other.code_size; self.address_hash = other.address_hash; let mut cache = self.storage_cache.borrow_mut(); - for (k, v) in other.storage_cache.into_inner().into_iter() { + for (k, v) in other.storage_cache.into_inner() { cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here } self.storage_changes = other.storage_changes; @@ -476,7 +487,7 @@ mod tests { }; let mut a = Account::from_rlp(&rlp); - assert!(a.cache_code(&db.immutable())); + assert!(a.cache_code(&db.immutable()).is_some()); let mut a = Account::from_rlp(&rlp); assert_eq!(a.note_code(vec![0x55, 0x44, 0xffu8]), Ok(())); diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 6befcad12..7c0f43d97 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -127,11 +127,10 @@ impl AccountEntry { fn overwrite_with(&mut self, other: AccountEntry) { self.state = other.state; match other.account { - Some(acc) => match self.account { - Some(ref mut ours) => { + Some(acc) => { + if let Some(ref mut ours) = self.account { ours.overwrite_with(acc); - }, - None => {}, + } }, None => self.account = None, } @@ -281,13 +280,10 @@ impl State { } }, None => { - match self.cache.get_mut().entry(k) { - Entry::Occupied(e) => { - if e.get().is_dirty() { - e.remove(); - } - }, - _ => {} + if let Entry::Occupied(e) = self.cache.get_mut().entry(k) { + if e.get().is_dirty() { + e.remove(); + } } } } @@ -501,6 +497,7 @@ impl State { /// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit. /// `accounts` is mutable because we may need to commit the code or storage and record that. #[cfg_attr(feature="dev", allow(match_ref_pats))] + #[cfg_attr(feature="dev", allow(needless_borrow))] fn commit_into( factories: &Factories, db: &mut StateDB, @@ -509,17 +506,14 @@ impl State { ) -> Result<(), Error> { // first, commit the sub trees. for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { - match a.account { - Some(ref mut account) => { - 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()); - account.commit_code(account_db.as_hashdb_mut()); + if let Some(ref mut account) = a.account { + 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()); + account.commit_code(account_db.as_hashdb_mut()); } } @@ -586,7 +580,7 @@ impl State { fn query_pod(&mut self, query: &PodState) { for (address, pod_account) in query.get().into_iter() - .filter(|&(ref a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some())) + .filter(|&(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. @@ -605,14 +599,30 @@ impl State { pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post) } - fn update_account_cache(require: RequireCache, account: &mut Account, db: &HashDB) { - match require { - RequireCache::None => {}, - RequireCache::Code => { - account.cache_code(db); - } - RequireCache::CodeSize => { - account.cache_code_size(db); + // load required account data from the databases. + fn update_account_cache(require: RequireCache, account: &mut Account, state_db: &StateDB, db: &HashDB) { + match (account.is_cached(), require) { + (true, _) | (false, RequireCache::None) => {} + (false, require) => { + // if there's already code in the global cache, always cache it + // locally. + let hash = account.code_hash(); + match state_db.get_cached_code(&hash) { + Some(code) => account.cache_given_code(code), + None => match require { + RequireCache::None => {}, + RequireCache::Code => { + if let Some(code) = account.cache_code(db) { + // propagate code loaded from the database to + // the global code cache. + state_db.cache_code(hash, code) + } + } + RequireCache::CodeSize => { + account.cache_code_size(db); + } + } + } } } } @@ -626,7 +636,7 @@ impl State { if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { if let Some(ref mut account) = maybe_acc.account { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); return f(Some(account)); } return f(None); @@ -635,7 +645,7 @@ impl State { let result = self.db.get_cached(a, |mut acc| { if let Some(ref mut account) = acc { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); } f(acc.map(|a| &*a)) }); @@ -653,7 +663,7 @@ impl State { }; if let Some(ref mut account) = maybe_acc.as_mut() { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); } let r = f(maybe_acc.as_ref()); self.insert_cache(a, AccountEntry::new_clean(maybe_acc)); @@ -679,14 +689,12 @@ impl State { None => { let maybe_acc = if self.db.check_account_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); - let maybe_acc = match db.get(a) { + match db.get(a) { Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))), Ok(None) => AccountEntry::new_clean(None), Err(e) => panic!("Potential DB corruption encountered: {}", e), - }; - maybe_acc - } - else { + } + } else { AccountEntry::new_clean(None) }; self.insert_cache(a, maybe_acc); @@ -711,7 +719,7 @@ impl State { if require_code { let addr_hash = account.address_hash(a); let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash); - account.cache_code(accountdb.as_hashdb()); + Self::update_account_cache(RequireCache::Code, account, &self.db, accountdb.as_hashdb()); } account }, diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 2823b9b1b..affc0b405 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -16,6 +16,7 @@ use std::collections::{VecDeque, HashSet}; use lru_cache::LruCache; +use util::cache::MemoryLruCache; use util::journaldb::JournalDB; use util::hash::{H256}; use util::hashdb::HashDB; @@ -33,12 +34,17 @@ pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; const STATE_CACHE_BLOCKS: usize = 12; +// The percentage of supplied cache size to go to accounts. +const ACCOUNT_CACHE_RATIO: usize = 90; + /// 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>, + /// DB Code cache. Maps code hashes to shared bytes. + code: MemoryLruCache>>, /// Information on the modifications in recently committed blocks; specifically which addresses /// changed in which block. Ordered by block number. modifications: VecDeque, @@ -111,12 +117,15 @@ impl StateDB { // into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`. pub fn new(db: Box, cache_size: usize) -> StateDB { let bloom = Self::load_bloom(db.backing()); - let cache_items = cache_size / ::std::mem::size_of::>(); + let acc_cache_size = cache_size * ACCOUNT_CACHE_RATIO / 100; + let code_cache_size = cache_size - acc_cache_size; + let cache_items = acc_cache_size / ::std::mem::size_of::>(); StateDB { db: db, account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(cache_items), + code: MemoryLruCache::new(code_cache_size), modifications: VecDeque::new(), })), local_cache: Vec::new(), @@ -170,7 +179,7 @@ impl StateDB { pub fn commit_bloom(batch: &mut DBTransaction, journal: BloomJournal) -> Result<(), UtilError> { assert!(journal.hash_functions <= 255); - batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &vec![journal.hash_functions as u8]); + batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &[journal.hash_functions as u8]); let mut key = [0u8; 8]; let mut val = [0u8; 8]; @@ -216,7 +225,7 @@ impl StateDB { let mut clear = false; for block in enacted.iter().filter(|h| self.commit_hash.as_ref().map_or(true, |p| *h != p)) { clear = clear || { - if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|m| &m.hash == block) { trace!("Reverting enacted block {:?}", block); m.is_canon = true; for a in &m.accounts { @@ -232,7 +241,7 @@ impl StateDB { for block in retracted { clear = clear || { - if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|m| &m.hash == block) { trace!("Retracting block {:?}", block); m.is_canon = false; for a in &m.accounts { @@ -286,7 +295,7 @@ impl StateDB { is_canon: is_best, parent: parent.clone(), }; - let insert_at = cache.modifications.iter().enumerate().find(|&(_, ref m)| m.number < *number).map(|(i, _)| i); + let insert_at = cache.modifications.iter().enumerate().find(|&(_, m)| m.number < *number).map(|(i, _)| i); trace!("inserting modifications at {:?}", insert_at); if let Some(insert_at) = insert_at { cache.modifications.insert(insert_at, block_changes); @@ -342,7 +351,12 @@ impl StateDB { /// Heap size used. pub fn mem_used(&self) -> usize { // 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::>() + self.db.mem_used() + { + let cache = self.account_cache.lock(); + + cache.code.current_size() + + cache.accounts.len() * ::std::mem::size_of::>() + } } /// Returns underlying `JournalDB`. @@ -362,6 +376,15 @@ impl StateDB { }) } + /// Add a global code cache entry. This doesn't need to worry about canonicality because + /// it simply maps hashes to raw code and will always be correct in the absence of + /// hash collisions. + pub fn cache_code(&self, hash: H256, code: Arc>) { + let mut cache = self.account_cache.lock(); + + cache.code.insert(hash, code); + } + /// Get basic copy of the cached account. Does not include storage. /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached_account(&self, addr: &Address) -> Option> { @@ -369,7 +392,14 @@ impl StateDB { if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { return None; } - cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic())) + cache.accounts.get_mut(addr).map(|a| a.as_ref().map(|a| a.clone_basic())) + } + + /// Get cached code based on hash. + pub fn get_cached_code(&self, hash: &H256) -> Option>> { + let mut cache = self.account_cache.lock(); + + cache.code.get_mut(hash).map(|code| code.clone()) } /// Get value from a cached account. @@ -406,8 +436,7 @@ impl StateDB { // We search for our parent in that list first and then for // all its parent until we hit the canonical block, // checking against all the intermediate modifications. - let mut iter = modifications.iter(); - while let Some(ref m) = iter.next() { + for m in modifications { if &m.hash == parent { if m.is_canon { return true; @@ -420,7 +449,7 @@ impl StateDB { } } trace!("Cache lookup skipped for {:?}: parent hash is unknown", addr); - return false; + false } } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 4d91a8c00..f19874341 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -389,7 +389,7 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h r } -pub fn get_good_dummy_block() -> Bytes { +pub fn get_good_dummy_block_hash() -> (H256, Bytes) { let mut block_header = Header::new(); let test_spec = get_test_spec(); let test_engine = &test_spec.engine; @@ -400,7 +400,12 @@ pub fn get_good_dummy_block() -> Bytes { block_header.set_parent_hash(test_spec.genesis_header().hash()); block_header.set_state_root(test_spec.genesis_header().state_root().clone()); - create_test_block(&block_header) + (block_header.hash(), create_test_block(&block_header)) +} + +pub fn get_good_dummy_block() -> Bytes { + let (_, bytes) = get_good_dummy_block_hash(); + bytes } pub fn get_bad_state_dummy_block() -> Bytes { diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index bc867983d..6a1b55a1b 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -285,7 +285,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { let mut blooms = self.blooms.write(); batch.extend_with_cache(db::COL_TRACE, &mut *blooms, blooms_to_insert, CacheUpdatePolicy::Remove); // note_used must be called after locking blooms to avoid cache/traces deadlock on garbage collection - for key in blooms_keys.into_iter() { + for key in blooms_keys { self.note_used(CacheID::Bloom(key)); } } diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index ca9bc30b5..bb18c61a8 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -50,12 +50,12 @@ fn prefix_subtrace_addresses(mut traces: Vec) -> Vec { // [1, 0] let mut current_subtrace_index = 0; let mut first = true; - for trace in traces.iter_mut() { + for trace in &mut traces { match (first, trace.trace_address.is_empty()) { (true, _) => first = false, (_, true) => current_subtrace_index += 1, _ => {} - } + } trace.trace_address.push_front(current_subtrace_index); } traces @@ -78,7 +78,7 @@ fn should_prefix_address_properly() { let t = vec![vec![], vec![0], vec![0, 0], vec![0], vec![], vec![], vec![0], vec![]].into_iter().map(&f).collect(); let t = prefix_subtrace_addresses(t); assert_eq!(t, vec![vec![0], vec![0, 0], vec![0, 0, 0], vec![0, 0], vec![1], vec![2], vec![2, 0], vec![3]].into_iter().map(&f).collect::>()); -} +} impl Tracer for ExecutiveTracer { fn prepare_trace_call(&self, params: &ActionParams) -> Option { diff --git a/evmbin/Cargo.lock b/evmbin/Cargo.lock index 600af473b..fde83d1bf 100644 --- a/evmbin/Cargo.lock +++ b/evmbin/Cargo.lock @@ -155,7 +155,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.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (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", ] @@ -208,6 +208,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-devtools" @@ -223,7 +226,7 @@ dependencies = [ "crossbeam 0.2.10 (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.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -275,8 +278,9 @@ dependencies = [ "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.17 (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.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (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.77 (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)", @@ -334,6 +338,7 @@ dependencies = [ "itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -632,13 +637,28 @@ name = "odds" version = "0.2.23" 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 = "parking_lot" -version = "0.2.8" +version = "0.3.6" +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.17 (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.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -867,6 +887,11 @@ dependencies = [ "gcc 0.3.37 (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" @@ -1179,7 +1204,9 @@ dependencies = [ "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" "checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3" "checksum odds 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e04630a62b3f1cc8c58b4d8f2555a40136f02b420e158242936ef286a72d33a0" -"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" +"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" +"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" +"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0e31b86efadeaeb1235452171a66689682783149a6249ff334a2c5d8218d00a4" "checksum primal-bit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "464a91febc06166783d4f5ba3577b5ed8dda8e421012df80bfe48a971ed7be8f" "checksum primal-check 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "647c81b67bb9551a7b88d0bcd785ac35b7d0bf4b2f358683d7c2375d04daec51" @@ -1205,6 +1232,7 @@ dependencies = [ "checksum serde_codegen 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e575e583f7d162e163af117fb9791fbd2bd203c31023b3219617e12c5997a738" "checksum serde_codegen_internals 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "318f7e77aa5187391d74aaf4553d2189f56b0ce25e963414c951b97877ffdcec" "checksum serde_json 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb6b19e74d9f65b9d03343730b643d729a446b29376785cd65efdff4675e2fc" +"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 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4" "checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410" diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs index 79e6eb3bd..11fb3a876 100644 --- a/evmbin/src/ext.rs +++ b/evmbin/src/ext.rs @@ -31,7 +31,7 @@ pub struct FakeExt { impl Default for FakeExt { fn default() -> Self { FakeExt { - schedule: Schedule::new_homestead(), + schedule: Schedule::new_homestead_gas_fix(), store: HashMap::new(), depth: 1, } diff --git a/js/assets/images/contracts/iconomi-64x64.png b/js/assets/images/contracts/iconomi-64x64.png new file mode 100644 index 000000000..b3ef6b95b Binary files /dev/null and b/js/assets/images/contracts/iconomi-64x64.png differ diff --git a/js/assets/images/contracts/iconomi.png b/js/assets/images/contracts/iconomi.png new file mode 100644 index 000000000..cc7b69afe Binary files /dev/null and b/js/assets/images/contracts/iconomi.png differ diff --git a/js/assets/images/dapps/gavcoin-bg.jpg b/js/assets/images/dapps/gavcoin-bg.jpg new file mode 100644 index 000000000..c344f5979 Binary files /dev/null and b/js/assets/images/dapps/gavcoin-bg.jpg differ diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 392bdd3b8..95e561b77 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -23,28 +23,37 @@ rm -rf ./.git git init # add local files and send it up +echo "Setting up GitHub config for js-precompiled" setup_git_user + +echo "Checking out $CI_BUILD_REF_NAME branch" git remote add origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git git fetch origin 2>$GITLOG git checkout -b $CI_BUILD_REF_NAME + +echo "Committing compiled files for $UTCDATE" git add . -git commit -m "$UTCDATE [compiled]" +git commit -m "$UTCDATE" + +echo "Merging remote" git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG +PRECOMPILED_HASH=`git rev-parse HEAD` # back to root popd -# inti git with right origin +echo "Setting up GitHub config for parity" setup_git_user git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git - -# at this point we have a detached head on GitLab, reset git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG -# bump js-precompiled, add, commit & push +echo "Updating cargo package parity-ui-precompiled#$PRECOMPILED_HASH" cargo update -p parity-ui-precompiled -git add . || true +# --precise "$PRECOMPILED_HASH" + +echo "Committing updated files" +git add . git commit -m "[ci skip] js-precompiled $UTCDATE" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG diff --git a/js/src/api/rpc/personal/personal.js b/js/src/api/rpc/personal/personal.js index e35333102..ca7dbce9b 100644 --- a/js/src/api/rpc/personal/personal.js +++ b/js/src/api/rpc/personal/personal.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inAddress, inNumber10, inNumber16, inOptions } from '../../format/input'; +import { inAddress, inHex, inNumber10, inNumber16, inOptions } from '../../format/input'; import { outAccountInfo, outAddress, outSignerRequest } from '../../format/output'; export default class Personal { @@ -73,6 +73,12 @@ export default class Personal { .then(outAddress); } + newAccountFromSecret (secret, password) { + return this._transport + .execute('personal_newAccountFromSecret', inHex(secret), password) + .then(outAddress); + } + newAccountFromWallet (json, password) { return this._transport .execute('personal_newAccountFromWallet', json, password) diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index d84085c98..4ab97ab6c 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -// import { api } from '../parity'; +import { api } from '../parity'; import { attachInstances } from '../services'; import Header from './Header'; @@ -83,7 +83,7 @@ export default class Application extends Component { Promise .all([ attachInstances(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { accountsInfo = accountsInfo || {}; diff --git a/js/src/dapps/gavcoin/Accounts/accounts.css b/js/src/dapps/gavcoin/Accounts/accounts.css index 7c4511710..a7fdcee73 100644 --- a/js/src/dapps/gavcoin/Accounts/accounts.css +++ b/js/src/dapps/gavcoin/Accounts/accounts.css @@ -22,7 +22,7 @@ .account { margin: 0.5em !important; - background: rgb(50, 100, 150) !important; + background: #430 !important; display: inline-block !important; } diff --git a/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js b/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js index f97e20a6c..42de205c1 100644 --- a/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js +++ b/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js @@ -73,6 +73,7 @@ export default class ActionBuyIn extends Component { if (complete) { return ( @@ -84,10 +85,12 @@ export default class ActionBuyIn extends Component { return ([ , @@ -82,10 +83,12 @@ export default class ActionRefund extends Component { return ([ , @@ -85,10 +86,12 @@ export default class ActionTransfer extends Component { return ([ , . +*/ + +.body { + background-size: cover; + background-repeat: no-repeat; +} diff --git a/js/src/dapps/gavcoin/Application/application.js b/js/src/dapps/gavcoin/Application/application.js index 29c86c78d..1ae5d870a 100644 --- a/js/src/dapps/gavcoin/Application/application.js +++ b/js/src/dapps/gavcoin/Application/application.js @@ -32,6 +32,13 @@ import Events from '../Events'; import Loading from '../Loading'; import Status from '../Status'; +import styles from './application.css'; +import bgimage from '../../../../assets/images/dapps/gavcoin-bg.jpg'; + +const bgstyle = { + backgroundImage: `url(${bgimage})` +}; + const DIVISOR = 10 ** 6; export default class Application extends Component { @@ -70,7 +77,7 @@ export default class Application extends Component { } return ( -
+
{ this.renderModals() } { diff --git a/js/src/dapps/gavcoin/Events/events.css b/js/src/dapps/gavcoin/Events/events.css index 6fca62354..9be9b37af 100644 --- a/js/src/dapps/gavcoin/Events/events.css +++ b/js/src/dapps/gavcoin/Events/events.css @@ -90,5 +90,5 @@ } .newtranch { - background: rgba(50, 250, 50, 0.1); + background: rgba(255, 175, 0, 0.125); /*rgba(68, 51, 0, 0.15);*/ } diff --git a/js/src/dapps/gavcoin/Status/status.css b/js/src/dapps/gavcoin/Status/status.css index bb541b178..bb1587148 100644 --- a/js/src/dapps/gavcoin/Status/status.css +++ b/js/src/dapps/gavcoin/Status/status.css @@ -15,8 +15,8 @@ /* along with Parity. If not, see . */ .status { - background: rgba(25, 75, 125, 1); - color: rgba(255, 255, 255, 1); + background: rgba(255, 175, 0, 0.25); + color: #430; padding: 4em 0 2em 0; display: flex; flex-wrap: wrap; @@ -38,14 +38,16 @@ .byline { font-size: 1.25em; - color: rgba(255, 255, 255, 0.7); + color: #430; + opacity: 0.75; } .heading { text-transform: uppercase; letter-spacing: 0.25em; font-size: 1.5em; - color: rgba(255, 255, 255, 0.7); + color: #430; + opacity: 0.75; } .hero { diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index 1904be2d7..b7676d5f5 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -29,7 +29,7 @@ export function attachInterface () { .all([ registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/registry/addresses/actions.js b/js/src/dapps/registry/addresses/actions.js index dfd7d16a3..17975f9e6 100644 --- a/js/src/dapps/registry/addresses/actions.js +++ b/js/src/dapps/registry/addresses/actions.js @@ -22,12 +22,16 @@ export const fetch = () => (dispatch) => { return Promise .all([ api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([ accounts, data ]) => { - const addresses = accounts.map((address) => { - return { address, isAccount: true }; - }); + data = data || {}; + const addresses = Object.keys(data) + .filter((address) => data[address] && !data[address].meta.deleted) + .map((address) => ({ + ...data[address], address, + isAccount: accounts.includes(address) + })); dispatch(set(addresses)); }) .catch((error) => { diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index 7219ddff1..cab324f7e 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -50,7 +50,7 @@ export function attachInterface (callback) { .all([ registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/tokenreg/Accounts/actions.js b/js/src/dapps/tokenreg/Accounts/actions.js index f093b5300..f501399c2 100644 --- a/js/src/dapps/tokenreg/Accounts/actions.js +++ b/js/src/dapps/tokenreg/Accounts/actions.js @@ -38,7 +38,7 @@ export const loadAccounts = () => (dispatch) => { Promise .all([ api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([ accounts, accountsInfo ]) => { accountsInfo = accountsInfo || {}; diff --git a/js/src/jsonrpc/interfaces/personal.js b/js/src/jsonrpc/interfaces/personal.js index 748c8799f..2a9ce7c19 100644 --- a/js/src/jsonrpc/interfaces/personal.js +++ b/js/src/jsonrpc/interfaces/personal.js @@ -141,7 +141,7 @@ export default { }, newAccountFromPhrase: { - desc: 'Creates a new account from a brainwallet passphrase', + desc: 'Creates a new account from a recovery passphrase', params: [ { type: String, @@ -158,6 +158,24 @@ export default { } }, + newAccountFromSecret: { + desc: 'Creates a new account from a private ethstore secret key', + params: [ + { + type: Data, + desc: 'Secret, 32-byte hex' + }, + { + type: String, + desc: 'Password' + } + ], + returns: { + type: Address, + desc: 'The created address' + } + }, + newAccountFromWallet: { desc: 'Creates a new account from a JSON import', params: [ diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index 6f9b7bb21..6fe1b2a51 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -32,19 +32,25 @@ border-radius: 16px; margin: 0.75em 0.5em 0 0; max-height: 24px; + max-width: 100%; + display: flex; + align-items: center; } .balance img { - display: inline-block; height: 32px; margin: -4px 1em 0 0; width: 32px; } -.balance div { - display: inline-block; - /*font-family: 'Roboto Mono', monospace;*/ - line-height: 24px; +.balanceValue { margin: 0 1em 0 0; - vertical-align: top; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.balanceTag { + font-size: 0.85em; + padding-right: 0.75rem; } diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index b9df3e9fb..b6f786648 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -56,7 +56,10 @@ class Balance extends Component { { -
{ value } { token.tag }
+
+ { value } +
+
{ token.tag }
); }); diff --git a/js/src/ui/Container/Title/title.css b/js/src/ui/Container/Title/title.css index c3bf7a3b1..ee5cc58cd 100644 --- a/js/src/ui/Container/Title/title.css +++ b/js/src/ui/Container/Title/title.css @@ -16,6 +16,9 @@ */ .byline { color: #aaa; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .title { diff --git a/js/src/ui/Container/Title/title.js b/js/src/ui/Container/Title/title.js index 2acfc30bb..0c0d0e6b3 100644 --- a/js/src/ui/Container/Title/title.js +++ b/js/src/ui/Container/Title/title.js @@ -40,7 +40,7 @@ export default class Title extends Component { { this.props.title }
- { this.props.byline } + { this.props.byline }
); diff --git a/json/Cargo.toml b/json/Cargo.toml index 90c36cedc..8f7b0c227 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -10,7 +10,7 @@ rustc-serialize = "0.3" serde = "0.8" serde_json = "0.8" serde_macros = { version = "0.8", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [build-dependencies] serde_codegen = { version = "0.8", optional = true } diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index fd2e11f98..84e44ee77 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -41,13 +41,13 @@ disable = false port = 8545 interface = "local" cors = "null" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc"] +apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal_safe"] hosts = ["none"] [ipc] disable = false path = "$HOME/.parity/jsonrpc.ipc" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc"] +apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal_safe"] [dapps] disable = false @@ -74,7 +74,10 @@ gas_cap = "6283184" tx_queue_size = 1024 tx_queue_gas = "auto" tx_queue_strategy = "gas_factor" +tx_queue_ban_count = 1 +tx_queue_ban_time = 180 #s tx_gas_limit = "6283184" +tx_time_limit = 100 #ms extra_data = "Parity" remove_solved = false notify_work = ["http://localhost:3001"] diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 27e0cb4dc..d13d791ad 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -145,7 +145,7 @@ usage! { or |c: &Config| otry!(c.rpc).interface.clone(), flag_jsonrpc_cors: Option = None, or |c: &Config| otry!(c.rpc).cors.clone().map(Some), - flag_jsonrpc_apis: String = "web3,eth,net,ethcore,traces,rpc", + flag_jsonrpc_apis: String = "web3,eth,net,ethcore,traces,rpc,personal_safe", or |c: &Config| otry!(c.rpc).apis.clone().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", or |c: &Config| otry!(c.rpc).hosts.clone().map(|vec| vec.join(",")), @@ -155,7 +155,7 @@ usage! { or |c: &Config| otry!(c.ipc).disable.clone(), flag_ipc_path: String = "$HOME/.parity/jsonrpc.ipc", or |c: &Config| otry!(c.ipc).path.clone(), - flag_ipc_apis: String = "web3,eth,net,ethcore,traces,rpc", + flag_ipc_apis: String = "web3,eth,net,ethcore,traces,rpc,personal_safe", or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), // DAPPS @@ -187,6 +187,8 @@ usage! { or |c: &Config| otry!(c.mining).work_queue_size.clone(), flag_tx_gas_limit: Option = None, or |c: &Config| otry!(c.mining).tx_gas_limit.clone().map(Some), + flag_tx_time_limit: Option = None, + or |c: &Config| otry!(c.mining).tx_time_limit.clone().map(Some), flag_relay_set: String = "cheap", or |c: &Config| otry!(c.mining).relay_set.clone(), flag_usd_per_tx: String = "0", @@ -205,8 +207,12 @@ usage! { or |c: &Config| otry!(c.mining).tx_queue_size.clone(), flag_tx_queue_gas: String = "auto", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), - flag_tx_queue_strategy: String = "gas_factor", + flag_tx_queue_strategy: String = "gas_price", or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(), + flag_tx_queue_ban_count: u16 = 1u16, + or |c: &Config| otry!(c.mining).tx_queue_ban_count.clone(), + flag_tx_queue_ban_time: u16 = 180u16, + or |c: &Config| otry!(c.mining).tx_queue_ban_time.clone(), flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), flag_notify_work: Option = None, @@ -361,6 +367,7 @@ struct Mining { reseal_min_period: Option, work_queue_size: Option, tx_gas_limit: Option, + tx_time_limit: Option, relay_set: Option, usd_per_tx: Option, usd_per_eth: Option, @@ -371,6 +378,8 @@ struct Mining { tx_queue_size: Option, tx_queue_gas: Option, tx_queue_strategy: Option, + tx_queue_ban_count: Option, + tx_queue_ban_time: Option, remove_solved: Option, notify_work: Option>, } @@ -445,6 +454,20 @@ mod tests { assert_eq!(args.flag_chain, "xyz".to_owned()); } + #[test] + fn should_use_config_if_cli_is_missing() { + let mut config = Config::default(); + let mut footprint = Footprint::default(); + footprint.pruning_history = Some(128); + config.footprint = Some(footprint); + + // when + let args = Args::parse_with_config(&["parity"], config).unwrap(); + + // then + assert_eq!(args.flag_pruning_history, 128); + } + #[test] fn should_parse_full_config() { // given @@ -520,13 +543,13 @@ mod tests { flag_jsonrpc_port: 8545u16, flag_jsonrpc_interface: "local".into(), flag_jsonrpc_cors: Some("null".into()), - flag_jsonrpc_apis: "web3,eth,net,ethcore,traces,rpc".into(), + flag_jsonrpc_apis: "web3,eth,net,ethcore,traces,rpc,personal_safe".into(), flag_jsonrpc_hosts: "none".into(), // IPC flag_no_ipc: false, flag_ipc_path: "$HOME/.parity/jsonrpc.ipc".into(), - flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc".into(), + flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc,personal_safe".into(), // DAPPS flag_no_dapps: false, @@ -544,6 +567,7 @@ mod tests { flag_reseal_min_period: 4000u64, flag_work_queue_size: 20usize, flag_tx_gas_limit: Some("6283184".into()), + flag_tx_time_limit: Some(100u64), flag_relay_set: "cheap".into(), flag_usd_per_tx: "0".into(), flag_usd_per_eth: "auto".into(), @@ -554,6 +578,8 @@ mod tests { flag_tx_queue_size: 1024usize, flag_tx_queue_gas: "auto".into(), flag_tx_queue_strategy: "gas_factor".into(), + flag_tx_queue_ban_count: 1u16, + flag_tx_queue_ban_time: 180u16, flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -713,7 +739,10 @@ mod tests { tx_queue_size: Some(1024), tx_queue_gas: Some("auto".into()), tx_queue_strategy: None, + tx_queue_ban_count: None, + tx_queue_ban_time: None, tx_gas_limit: None, + tx_time_limit: None, extra_data: None, remove_solved: None, notify_work: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index af8e83f0e..bf7e82561 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -107,7 +107,7 @@ API and Console Options: --jsonrpc-apis APIS Specify the APIs available through the JSONRPC interface. APIS is a comma-delimited list of API name. Possible name are web3, eth, net, personal, - ethcore, ethcore_set, traces, rpc. + ethcore, ethcore_set, traces, rpc, personal_safe. (default: {flag_jsonrpc_apis}). --jsonrpc-hosts HOSTS List of allowed Host header values. This option will validate the Host header sent by the browser, it @@ -166,6 +166,11 @@ Sealing/Mining Options: --tx-gas-limit GAS Apply a limit of GAS as the maximum amount of gas a single transaction may have for it to be mined. (default: {flag_tx_gas_limit:?}) + --tx-time-limit MS Maximal time for processing single transaction. + If enabled senders/recipients/code of transactions + offending the limit will be banned from being included + in transaction queue for 180 seconds. + (default: {flag_tx_time_limit:?}) --relay-set SET Set of transactions to relay. SET may be: cheap - Relay any transaction in the queue (this may include invalid transactions); @@ -203,6 +208,13 @@ Sealing/Mining Options: gas_price - Prioritize txs with high gas price; gas_factor - Prioritize txs using gas price and gas limit ratio (default: {flag_tx_queue_strategy}). + --tx-queue-ban-count C Number of times maximal time for execution (--tx-time-limit) + can be exceeded before banning sender/recipient/code. + (default: {flag_tx_queue_ban_count}) + --tx-queue-ban-time SEC Banning time (in seconds) for offenders of specified + execution time limit. Also number of offending actions + have to reach the threshold within that time. + (default: {flag_tx_queue_ban_time} seconds) --remove-solved Move solved blocks from the work package queue instead of cloning them. This gives a slightly faster import speed, but means that extra solutions @@ -225,7 +237,7 @@ Footprint Options: auto - use the method most recently synced or default to fast if none synced (default: {flag_pruning}). --pruning-history NUM Set a number of recent states to keep when pruning - is active. [default: {flag_pruning_history}]. + is active. (default: {flag_pruning_history}). --cache-size-db MB Override database cache size (default: {flag_cache_size_db}). --cache-size-blocks MB Specify the prefered size of the blockchain cache in megabytes (default: {flag_cache_size_blocks}). diff --git a/parity/configuration.rs b/parity/configuration.rs index 5680e6110..1972b9f1e 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -24,7 +24,7 @@ use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address}; use util::log::Colour; use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; use ethcore::client::{VMType, Mode}; -use ethcore::miner::MinerOptions; +use ethcore::miner::{MinerOptions, Banning}; use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; @@ -387,6 +387,14 @@ impl Configuration { reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period), work_queue_size: self.args.flag_work_queue_size, enable_resubmission: !self.args.flag_remove_solved, + tx_queue_banning: match self.args.flag_tx_time_limit { + Some(limit) => Banning::Enabled { + min_offends: self.args.flag_tx_queue_ban_count, + offend_threshold: Duration::from_millis(limit), + ban_duration: Duration::from_secs(self.args.flag_tx_queue_ban_time as u64), + }, + None => Banning::Disabled, + } }; Ok(options) diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index f6ccf16a3..491f58a1b 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -33,7 +33,8 @@ pub enum Api { Web3, Net, Eth, - Personal, + PersonalSafe, + PersonalAccounts, Signer, Ethcore, EthcoreSet, @@ -51,7 +52,8 @@ impl FromStr for Api { "web3" => Ok(Web3), "net" => Ok(Net), "eth" => Ok(Eth), - "personal" => Ok(Personal), + "personal" => Ok(PersonalAccounts), + "personal_safe" => Ok(PersonalSafe), "signer" => Ok(Signer), "ethcore" => Ok(Ethcore), "ethcore_set" => Ok(EthcoreSet), @@ -114,7 +116,8 @@ fn to_modules(apis: &[Api]) -> BTreeMap { Api::Web3 => ("web3", "1.0"), Api::Net => ("net", "1.0"), Api::Eth => ("eth", "1.0"), - Api::Personal => ("personal", "1.0"), + Api::PersonalSafe => ("personal_safe", "1.0"), + Api::PersonalAccounts => ("personal", "1.0"), Api::Signer => ("signer", "1.0"), Api::Ethcore => ("ethcore", "1.0"), Api::EthcoreSet => ("ethcore_set", "1.0"), @@ -131,11 +134,11 @@ impl ApiSet { match *self { ApiSet::List(ref apis) => apis.clone(), ApiSet::UnsafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] .into_iter().collect() }, ApiSet::SafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] .into_iter().collect() }, } @@ -178,8 +181,11 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); } }, - Api::Personal => { - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.geth_compatibility).to_delegate()); + Api::PersonalAccounts => { + server.add_delegate(PersonalAccountsClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.geth_compatibility).to_delegate()); + }, + Api::PersonalSafe => { + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client).to_delegate()); }, Api::Signer => { server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_service).to_delegate()); @@ -224,7 +230,8 @@ mod test { assert_eq!(Api::Web3, "web3".parse().unwrap()); assert_eq!(Api::Net, "net".parse().unwrap()); assert_eq!(Api::Eth, "eth".parse().unwrap()); - assert_eq!(Api::Personal, "personal".parse().unwrap()); + assert_eq!(Api::PersonalAccounts, "personal".parse().unwrap()); + assert_eq!(Api::PersonalSafe, "personal_safe".parse().unwrap()); assert_eq!(Api::Signer, "signer".parse().unwrap()); assert_eq!(Api::Ethcore, "ethcore".parse().unwrap()); assert_eq!(Api::EthcoreSet, "ethcore_set".parse().unwrap()); @@ -245,14 +252,14 @@ mod test { #[test] fn test_api_set_unsafe_context() { - let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] .into_iter().collect(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } #[test] fn test_api_set_safe_context() { - let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] .into_iter().collect(); assert_eq!(ApiSet::SafeContext.list_apis(), expected); } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 34b68fb81..9ce638ea6 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,7 +29,7 @@ fetch = { path = "../util/fetch" } rustc-serialize = "0.3" transient-hashmap = "0.1" serde_macros = { version = "0.8.0", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } ethcore-ipc = { path = "../ipc/rpc" } time = "0.1" diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 572adca3a..475063832 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -221,6 +221,9 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { LimitReached => { "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() }, + InsufficientGas { minimal, got } => { + format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) + }, InsufficientGasPrice { minimal, got } => { format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) }, @@ -231,6 +234,9 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) }, InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), + SenderBanned => "Sender is banned in local queue.".into(), + RecipientBanned => "Recipient is banned in local queue.".into(), + CodeBanned => "Code is banned in local queue.".into(), }; Error { code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 1a1410ebd..2619b84da 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -334,4 +334,17 @@ impl Ethcore for EthcoreClient where self.dapps_port .ok_or_else(|| errors::dapps_disabled()) } + + fn next_nonce(&self, address: H160) -> Result { + try!(self.active()); + let address: Address = address.into(); + let miner = take_weak!(self.miner); + let client = take_weak!(self.client); + + Ok(miner.last_nonce(&address) + .map(|n| n + 1.into()) + .unwrap_or_else(|| client.latest_nonce(&address)) + .into() + ) + } } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index bf2013d99..c108f0b6b 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -32,6 +32,7 @@ mod ethcore; mod ethcore_set; mod net; mod personal; +mod personal_accounts; mod personal_signer; mod rpc; mod traces; @@ -43,6 +44,7 @@ pub use self::eth_filter::EthFilterClient; pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; pub use self::personal::PersonalClient; +pub use self::personal_accounts::PersonalAccountsClient; pub use self::personal_signer::SignerClient; pub use self::ethcore::EthcoreClient; pub use self::ethcore_set::EthcoreSetClient; diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 0d6b63240..aacf90c91 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -17,34 +17,26 @@ //! Account management (personal) rpc implementation use std::sync::{Arc, Weak}; use std::collections::{BTreeMap}; -use util::{Address}; use jsonrpc_core::*; -use ethkey::{Brain, Generator}; use v1::traits::Personal; -use v1::types::{H160 as RpcH160, TransactionRequest}; +use v1::types::{H160 as RpcH160}; use v1::helpers::errors; use v1::helpers::params::expect_no_params; -use v1::helpers::dispatch::sign_and_dispatch; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; -use ethcore::miner::MinerService; /// Account management (personal) rpc implementation. -pub struct PersonalClient where C: MiningBlockChainClient, M: MinerService { +pub struct PersonalClient where C: MiningBlockChainClient { accounts: Weak, client: Weak, - miner: Weak, - allow_perm_unlock: bool, } -impl PersonalClient where C: MiningBlockChainClient, M: MinerService { +impl PersonalClient where C: MiningBlockChainClient { /// Creates new PersonalClient - pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { + pub fn new(store: &Arc, client: &Arc) -> Self { PersonalClient { accounts: Arc::downgrade(store), client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - allow_perm_unlock: allow_perm_unlock, } } @@ -55,7 +47,7 @@ impl PersonalClient where C: MiningBlockChainClient, M: MinerService } } -impl Personal for PersonalClient where C: MiningBlockChainClient, M: MinerService { +impl Personal for PersonalClient where C: MiningBlockChainClient { fn accounts(&self, params: Params) -> Result { try!(self.active()); @@ -66,125 +58,6 @@ impl Personal for PersonalClient where C: MiningBl Ok(to_value(&accounts.into_iter().map(Into::into).collect::>())) } - fn new_account(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, )>(params).and_then( - |(pass, )| { - let store = take_weak!(self.accounts); - match store.new_account(&pass) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn new_account_from_phrase(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, String, )>(params).and_then( - |(phrase, pass, )| { - let store = take_weak!(self.accounts); - match store.insert_account(*Brain::new(phrase).generate().unwrap().secret(), &pass) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn new_account_from_wallet(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, String, )>(params).and_then( - |(json, pass, )| { - let store = take_weak!(self.accounts); - match store.import_presale(json.as_bytes(), &pass).or_else(|_| store.import_wallet(json.as_bytes(), &pass)) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn unlock_account(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String, Option)>(params).and_then( - |(account, account_pass, duration)| { - let account: Address = account.into(); - let store = take_weak!(self.accounts); - let r = match (self.allow_perm_unlock, duration) { - (false, _) => store.unlock_account_temporarily(account, account_pass), - (true, Some(0)) => store.unlock_account_permanently(account, account_pass), - (true, Some(d)) => store.unlock_account_timed(account, account_pass, d as u32 * 1000), - (true, None) => store.unlock_account_timed(account, account_pass, 300_000), - }; - match r { - Ok(_) => Ok(Value::Bool(true)), - Err(_) => Ok(Value::Bool(false)), - } - } - ) - } - - fn test_password(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String)>(params).and_then( - |(account, password)| { - let account: Address = account.into(); - take_weak!(self.accounts) - .test_password(&account, password) - .map(|b| Value::Bool(b)) - .map_err(|e| errors::account("Could not fetch account info.", e)) - } - ) - } - - fn change_password(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String, String)>(params).and_then( - |(account, password, new_password)| { - let account: Address = account.into(); - take_weak!(self.accounts) - .change_password(&account, password, new_password) - .map(|_| Value::Null) - .map_err(|e| errors::account("Could not fetch account info.", e)) - } - ) - } - - fn sign_and_send_transaction(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(TransactionRequest, String)>(params) - .and_then(|(request, password)| { - sign_and_dispatch( - &*take_weak!(self.client), - &*take_weak!(self.miner), - &*take_weak!(self.accounts), - request.into(), - Some(password) - ) - }) - } - - fn set_account_name(&self, params: Params) -> Result { - try!(self.active()); - let store = take_weak!(self.accounts); - from_params::<(RpcH160, String)>(params).and_then(|(addr, name)| { - let addr: Address = addr.into(); - store.set_account_name(addr.clone(), name.clone()).or_else(|_| store.set_address_name(addr, name)).expect("set_address_name always returns Ok; qed"); - Ok(Value::Null) - }) - } - - fn set_account_meta(&self, params: Params) -> Result { - try!(self.active()); - let store = take_weak!(self.accounts); - from_params::<(RpcH160, String)>(params).and_then(|(addr, meta)| { - let addr: Address = addr.into(); - store.set_account_meta(addr.clone(), meta.clone()).or_else(|_| store.set_address_meta(addr, meta)).expect("set_address_meta always returns Ok; qed"); - Ok(Value::Null) - }) - } - fn accounts_info(&self, params: Params) -> Result { try!(self.active()); try!(expect_no_params(params)); @@ -204,21 +77,4 @@ impl Personal for PersonalClient where C: MiningBl (format!("0x{}", a.hex()), Value::Object(m)) }).collect::>())) } - - fn geth_accounts(&self, params: Params) -> Result { - try!(self.active()); - try!(expect_no_params(params)); - let store = take_weak!(self.accounts); - Ok(to_value(&store.list_geth_accounts(false).into_iter().map(Into::into).collect::>())) - } - - fn import_geth_accounts(&self, params: Params) -> Result { - from_params::<(Vec,)>(params).and_then(|(addresses,)| { - let store = take_weak!(self.accounts); - Ok(to_value(&try!(store - .import_geth_accounts(addresses.into_iter().map(Into::into).collect(), false) - .map_err(|e| errors::account("Couldn't import Geth accounts", e)) - ).into_iter().map(Into::into).collect::>())) - }) - } } diff --git a/rpc/src/v1/impls/personal_accounts.rs b/rpc/src/v1/impls/personal_accounts.rs new file mode 100644 index 000000000..6e777f537 --- /dev/null +++ b/rpc/src/v1/impls/personal_accounts.rs @@ -0,0 +1,207 @@ +// 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 . + +//! Account management (personal) rpc implementation +use std::sync::{Arc, Weak}; +use util::{Address}; +use jsonrpc_core::*; +use ethkey::{Brain, Generator}; +use v1::traits::PersonalAccounts; +use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; +use v1::helpers::errors; +use v1::helpers::params::expect_no_params; +use v1::helpers::dispatch::sign_and_dispatch; +use ethcore::account_provider::AccountProvider; +use ethcore::client::MiningBlockChainClient; +use ethcore::miner::MinerService; + +/// Account management (personal) rpc implementation. +pub struct PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + accounts: Weak, + client: Weak, + miner: Weak, + allow_perm_unlock: bool, +} + +impl PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + /// Creates new PersonalClient + pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { + PersonalAccountsClient { + accounts: Arc::downgrade(store), + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + allow_perm_unlock: allow_perm_unlock, + } + } + + fn active(&self) -> Result<(), Error> { + // TODO: only call every 30s at most. + take_weak!(self.client).keep_alive(); + Ok(()) + } +} + +impl PersonalAccounts for PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + + fn new_account(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, )>(params).and_then( + |(pass, )| { + let store = take_weak!(self.accounts); + match store.new_account(&pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn new_account_from_phrase(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, String, )>(params).and_then( + |(phrase, pass, )| { + let store = take_weak!(self.accounts); + match store.insert_account(*Brain::new(phrase).generate().unwrap().secret(), &pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn new_account_from_wallet(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, String, )>(params).and_then( + |(json, pass, )| { + let store = take_weak!(self.accounts); + match store.import_presale(json.as_bytes(), &pass).or_else(|_| store.import_wallet(json.as_bytes(), &pass)) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn new_account_from_secret(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH256, String, )>(params).and_then( + |(secret, pass, )| { + let store = take_weak!(self.accounts); + match store.insert_account(secret.into(), &pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn unlock_account(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String, Option)>(params).and_then( + |(account, account_pass, duration)| { + let account: Address = account.into(); + let store = take_weak!(self.accounts); + let r = match (self.allow_perm_unlock, duration) { + (false, _) => store.unlock_account_temporarily(account, account_pass), + (true, Some(0)) => store.unlock_account_permanently(account, account_pass), + (true, Some(d)) => store.unlock_account_timed(account, account_pass, d as u32 * 1000), + (true, None) => store.unlock_account_timed(account, account_pass, 300_000), + }; + match r { + Ok(_) => Ok(Value::Bool(true)), + Err(_) => Ok(Value::Bool(false)), + } + } + ) + } + + fn test_password(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String)>(params).and_then( + |(account, password)| { + let account: Address = account.into(); + take_weak!(self.accounts) + .test_password(&account, password) + .map(|b| Value::Bool(b)) + .map_err(|e| errors::account("Could not fetch account info.", e)) + } + ) + } + + fn change_password(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String, String)>(params).and_then( + |(account, password, new_password)| { + let account: Address = account.into(); + take_weak!(self.accounts) + .change_password(&account, password, new_password) + .map(|_| Value::Null) + .map_err(|e| errors::account("Could not fetch account info.", e)) + } + ) + } + + fn sign_and_send_transaction(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(TransactionRequest, String)>(params) + .and_then(|(request, password)| { + sign_and_dispatch( + &*take_weak!(self.client), + &*take_weak!(self.miner), + &*take_weak!(self.accounts), + request.into(), + Some(password) + ) + }) + } + + fn set_account_name(&self, params: Params) -> Result { + try!(self.active()); + let store = take_weak!(self.accounts); + from_params::<(RpcH160, String)>(params).and_then(|(addr, name)| { + let addr: Address = addr.into(); + store.set_account_name(addr.clone(), name.clone()).or_else(|_| store.set_address_name(addr, name)).expect("set_address_name always returns Ok; qed"); + Ok(Value::Null) + }) + } + + fn set_account_meta(&self, params: Params) -> Result { + try!(self.active()); + let store = take_weak!(self.accounts); + from_params::<(RpcH160, String)>(params).and_then(|(addr, meta)| { + let addr: Address = addr.into(); + store.set_account_meta(addr.clone(), meta.clone()).or_else(|_| store.set_address_meta(addr, meta)).expect("set_address_meta always returns Ok; qed"); + Ok(Value::Null) + }) + } + + fn import_geth_accounts(&self, params: Params) -> Result { + from_params::<(Vec,)>(params).and_then(|(addresses,)| { + let store = take_weak!(self.accounts); + Ok(to_value(&try!(store + .import_geth_accounts(addresses.into_iter().map(Into::into).collect(), false) + .map_err(|e| errors::account("Couldn't import Geth accounts", e)) + ).into_iter().map(Into::into).collect::>())) + }) + } + + fn geth_accounts(&self, params: Params) -> Result { + try!(self.active()); + try!(expect_no_params(params)); + let store = take_weak!(self.accounts); + Ok(to_value(&store.list_geth_accounts(false).into_iter().map(Into::into).collect::>())) + } +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 5ba302cea..24560160c 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -26,6 +26,6 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalAccounts, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import}; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 5d7481551..d556d11ef 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; use ethcore::ethereum; -use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit}; +use ethcore::miner::{MinerOptions, Banning, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit}; use ethcore::account_provider::AccountProvider; use devtools::RandomTempPath; use util::Hashable; @@ -61,6 +61,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { tx_gas_limit: !U256::zero(), tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, tx_queue_gas_limit: GasLimit::None, + tx_queue_banning: Banning::Disabled, pending_set: PendingSet::SealingOrElseQueue, reseal_min_period: Duration::from_secs(0), work_queue_size: 50, diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index ea4112c19..e33f1a8f7 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use util::log::RotatingLogger; -use util::U256; +use util::{U256, Address}; use ethsync::ManageNetwork; use ethcore::client::{TestBlockChainClient}; use ethstore::ethkey::{Generator, Random}; @@ -320,3 +320,25 @@ fn rpc_ethcore_dapps_port() { assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); } + +#[test] +fn rpc_ethcore_next_nonce() { + let deps = Dependencies::new(); + let address = Address::default(); + let io1 = deps.default_client(); + let deps = Dependencies::new(); + deps.miner.last_nonces.write().insert(address.clone(), 2.into()); + let io2 = deps.default_client(); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "ethcore_nextNonce", + "params": [""#.to_owned() + &format!("0x{:?}", address) + r#""], + "id": 1 + }"#; + let response1 = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","result":"0x3","id":1}"#; + + assert_eq!(io1.handle_request_sync(&request), Some(response1.to_owned())); + assert_eq!(io2.handle_request_sync(&request), Some(response2.to_owned())); +} diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 91e1ef0f5..2dd186cca 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -19,7 +19,7 @@ use std::str::FromStr; use jsonrpc_core::IoHandler; use util::{U256, Uint, Address}; use ethcore::account_provider::AccountProvider; -use v1::{PersonalClient, Personal}; +use v1::{PersonalClient, PersonalAccountsClient, PersonalAccounts, Personal}; use v1::tests::helpers::TestMinerService; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Action, Transaction}; @@ -50,10 +50,12 @@ fn setup() -> PersonalTester { let accounts = accounts_provider(); let client = blockchain_client(); let miner = miner_service(); - let personal = PersonalClient::new(&accounts, &client, &miner, false); + let personal = PersonalClient::new(&accounts, &client); + let personal_accounts = PersonalAccountsClient::new(&accounts, &client, &miner, false); let io = IoHandler::new(); io.add_delegate(personal.to_delegate()); + io.add_delegate(personal_accounts.to_delegate()); let tester = PersonalTester { accounts: accounts, diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index ea5f0b13d..e787ce5ac 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -125,5 +125,9 @@ build_rpc_trait! { /// Returns current Dapps Server port or an error if dapps server is disabled. #[rpc(name = "ethcore_dappsPort")] fn dapps_port(&self) -> Result; + + /// Returns next nonce for particular sender. Should include all transactions in the queue. + #[rpc(name = "ethcore_nextNonce")] + fn next_nonce(&self, H160) -> Result; } } diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index e804c5553..ea0834463 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -30,7 +30,7 @@ pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter}; pub use self::eth_signing::EthSigning; pub use self::net::Net; -pub use self::personal::{Personal, PersonalSigner}; +pub use self::personal::{Personal, PersonalAccounts, PersonalSigner}; pub use self::ethcore::Ethcore; pub use self::ethcore_set::EthcoreSet; pub use self::traces::Traces; diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 2114131d4..8ad1b7ac6 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -18,12 +18,28 @@ use std::sync::Arc; use jsonrpc_core::*; -/// Personal rpc interface. +/// Personal rpc interface. Safe (read-only) functions. pub trait Personal: Sized + Send + Sync + 'static { /// Lists all stored accounts fn accounts(&self, _: Params) -> Result; + /// Returns accounts information. + fn accounts_info(&self, _: Params) -> Result; + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("personal_listAccounts", Personal::accounts); + delegate.add_method("personal_accountsInfo", Personal::accounts_info); + + delegate + } +} + +/// Personal rpc methods altering stored accounts or their settings. +pub trait PersonalAccounts: Sized + Send + Sync + 'static { + /// Creates new account (it becomes new current unlocked account) /// Param is the password for the account. fn new_account(&self, _: Params) -> Result; @@ -36,6 +52,10 @@ pub trait Personal: Sized + Send + Sync + 'static { /// Second parameter is password for the wallet and the new account. fn new_account_from_wallet(&self, params: Params) -> Result; + /// Creates new account from the given raw secret. + /// Second parameter is password for the new account. + fn new_account_from_secret(&self, params: Params) -> Result; + /// Unlocks specified account for use (can only be one unlocked account at one moment) fn unlock_account(&self, _: Params) -> Result; @@ -56,31 +76,27 @@ pub trait Personal: Sized + Send + Sync + 'static { /// Set an account's metadata string. fn set_account_meta(&self, _: Params) -> Result; - /// Returns accounts information. - fn accounts_info(&self, _: Params) -> Result; + /// Imports a number of Geth accounts, with the list provided as the argument. + fn import_geth_accounts(&self, _: Params) -> Result; /// Returns the accounts available for importing from Geth. fn geth_accounts(&self, _: Params) -> Result; - /// Imports a number of Geth accounts, with the list provided as the argument. - fn import_geth_accounts(&self, _: Params) -> Result; - /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("personal_listAccounts", Personal::accounts); - delegate.add_method("personal_newAccount", Personal::new_account); - delegate.add_method("personal_newAccountFromPhrase", Personal::new_account_from_phrase); - delegate.add_method("personal_newAccountFromWallet", Personal::new_account_from_wallet); - delegate.add_method("personal_unlockAccount", Personal::unlock_account); - delegate.add_method("personal_testPassword", Personal::test_password); - delegate.add_method("personal_changePassword", Personal::change_password); - delegate.add_method("personal_signAndSendTransaction", Personal::sign_and_send_transaction); - delegate.add_method("personal_setAccountName", Personal::set_account_name); - delegate.add_method("personal_setAccountMeta", Personal::set_account_meta); - delegate.add_method("personal_accountsInfo", Personal::accounts_info); - delegate.add_method("personal_listGethAccounts", Personal::geth_accounts); - delegate.add_method("personal_importGethAccounts", Personal::import_geth_accounts); + delegate.add_method("personal_newAccount", PersonalAccounts::new_account); + delegate.add_method("personal_newAccountFromPhrase", PersonalAccounts::new_account_from_phrase); + delegate.add_method("personal_newAccountFromWallet", PersonalAccounts::new_account_from_wallet); + delegate.add_method("personal_newAccountFromSecret", PersonalAccounts::new_account_from_secret); + delegate.add_method("personal_unlockAccount", PersonalAccounts::unlock_account); + delegate.add_method("personal_testPassword", PersonalAccounts::test_password); + delegate.add_method("personal_changePassword", PersonalAccounts::change_password); + delegate.add_method("personal_signAndSendTransaction", PersonalAccounts::sign_and_send_transaction); + delegate.add_method("personal_setAccountName", PersonalAccounts::set_account_name); + delegate.add_method("personal_setAccountMeta", PersonalAccounts::set_account_meta); + delegate.add_method("personal_importGethAccounts", PersonalAccounts::import_geth_accounts); + delegate.add_method("personal_listGethAccounts", PersonalAccounts::geth_accounts); delegate } @@ -108,6 +124,7 @@ pub trait PersonalSigner: Sized + Send + Sync + 'static { delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request); delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request); delegate.add_method("personal_generateAuthorizationToken", PersonalSigner::generate_token); + delegate } } diff --git a/signer/Cargo.toml b/signer/Cargo.toml index b8a7c5ce4..651d96cb3 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -23,7 +23,7 @@ ethcore-rpc = { path = "../rpc" } ethcore-devtools = { path = "../devtools" } parity-ui = { path = "../dapps/ui", version = "1.4", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [features] dev = ["clippy"] diff --git a/signer/src/authcode_store.rs b/signer/src/authcode_store.rs index d8068fc88..d8474441c 100644 --- a/signer/src/authcode_store.rs +++ b/signer/src/authcode_store.rs @@ -99,6 +99,7 @@ impl AuthCodes { } /// Checks if given hash is correct identifier of `SignerUI` + #[cfg_attr(feature="dev", allow(wrong_self_convention))] pub fn is_valid(&mut self, hash: &H256, time: u64) -> bool { let now = self.now.now(); // check time diff --git a/sync/Cargo.toml b/sync/Cargo.toml index d27929186..95d738eb4 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -17,7 +17,7 @@ ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} log = "0.3" env_logger = "0.3" time = "0.1.34" diff --git a/sync/src/api.rs b/sync/src/api.rs index b227dcd60..67a81237a 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -34,7 +34,7 @@ use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; -pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"bam"; +pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; /// Sync configuration #[derive(Debug, Clone, Copy)] diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index db385b9d5..ed608d9c1 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -114,7 +114,7 @@ impl BlockCollection { /// Insert a set of headers into collection and advance subchain head pointers. pub fn insert_headers(&mut self, headers: Vec) { - for h in headers.into_iter() { + for h in headers { if let Err(e) = self.insert_header(h) { trace!(target: "sync", "Ignored invalid header: {:?}", e); } @@ -125,7 +125,7 @@ impl BlockCollection { /// Insert a collection of block bodies for previously downloaded headers. pub fn insert_bodies(&mut self, bodies: Vec) -> usize { let mut inserted = 0; - for b in bodies.into_iter() { + for b in bodies { if let Err(e) = self.insert_body(b) { trace!(target: "sync", "Ignored invalid body: {:?}", e); } else { @@ -141,7 +141,7 @@ impl BlockCollection { return 0; } let mut inserted = 0; - for r in receipts.into_iter() { + for r in receipts { if let Err(e) = self.insert_receipt(r) { trace!(target: "sync", "Ignored invalid receipt: {:?}", e); } else { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 916e7424e..d18adb6ea 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -209,8 +209,8 @@ pub struct SyncStatus { impl SyncStatus { /// Indicates if snapshot download is in progress pub fn is_snapshot_syncing(&self) -> bool { - self.state == SyncState::SnapshotManifest - || self.state == SyncState::SnapshotData + self.state == SyncState::SnapshotManifest + || self.state == SyncState::SnapshotData || self.state == SyncState::SnapshotWaiting } @@ -381,7 +381,7 @@ impl ChainSync { /// Returns information on peers connections pub fn peers(&self, io: &SyncIo) -> Vec { self.peers.iter() - .filter_map(|(&peer_id, ref peer_data)| + .filter_map(|(&peer_id, peer_data)| io.peer_session_info(peer_id).map(|session_info| PeerInfoDigest { id: session_info.id.map(|id| id.hex()), @@ -453,7 +453,7 @@ impl ChainSync { self.init_downloaders(io.chain()); self.reset_and_continue(io); } - + /// Restart sync after bad block has been detected. May end up re-downloading up to QUEUE_SIZE blocks fn init_downloaders(&mut self, chain: &BlockChainClient) { // Do not assume that the block queue/chain still has our last_imported_block @@ -1017,7 +1017,7 @@ impl ChainSync { return; } let (peer_latest, peer_difficulty, peer_snapshot_number, peer_snapshot_hash) = { - if let Some(ref peer) = self.peers.get_mut(&peer_id) { + if let Some(peer) = self.peers.get_mut(&peer_id) { if peer.asking != PeerAsking::Nothing || !peer.can_sync() { return; } @@ -1142,6 +1142,7 @@ impl ChainSync { } /// Checks if there are blocks fully downloaded that can be imported into the blockchain and does the import. + #[cfg_attr(feature="dev", allow(block_in_if_condition_stmt))] fn collect_blocks(&mut self, io: &mut SyncIo, block_set: BlockSet) { match block_set { BlockSet::NewBlocks => { @@ -1150,9 +1151,9 @@ impl ChainSync { } }, BlockSet::OldBlocks => { - if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { - self.restart(io); - } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { + if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { + self.restart(io); + } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { trace!(target: "sync", "Background block download is complete"); self.old_blocks = None; } @@ -1242,7 +1243,7 @@ impl ChainSync { return true; } } - return false; + false } /// Generic request sender @@ -1370,7 +1371,7 @@ impl ChainSync { while number <= last && count < max_count { if let Some(hdr) = overlay.get(&number) { trace!(target: "sync", "{}: Returning cached fork header", peer_id); - data.extend(hdr); + data.extend_from_slice(hdr); count += 1; } else if let Some(mut hdr) = io.chain().block_header(BlockID::Number(number)) { data.append(&mut hdr); @@ -1427,16 +1428,18 @@ impl ChainSync { } count = min(count, MAX_NODE_DATA_TO_SEND); let mut added = 0usize; - let mut data = Bytes::new(); + let mut data = Vec::new(); for i in 0..count { - if let Some(mut hdr) = io.chain().state_data(&try!(r.val_at::(i))) { - data.append(&mut hdr); + if let Some(hdr) = io.chain().state_data(&try!(r.val_at::(i))) { + data.push(hdr); added += 1; } } trace!(target: "sync", "{} -> GetNodeData: return {} entries", peer_id, added); let mut rlp = RlpStream::new_list(added); - rlp.append_raw(&data, added); + for d in data.into_iter() { + rlp.append(&d); + } Ok(Some((NODE_DATA_PACKET, rlp))) } @@ -1707,7 +1710,7 @@ impl ChainSync { self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } } - if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if let Some(ref mut peer) = self.peers.get_mut(peer_id) { peer.latest_hash = chain_info.best_block_hash.clone(); } sent += 1; @@ -1725,7 +1728,7 @@ impl ChainSync { sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { Some(rlp) => { { - if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if let Some(ref mut peer) = self.peers.get_mut(peer_id) { peer.latest_hash = chain_info.best_block_hash.clone(); } } @@ -1793,7 +1796,7 @@ impl ChainSync { // Send RLPs let sent = lucky_peers.len(); if sent > 0 { - for (peer_id, rlp) in lucky_peers.into_iter() { + for (peer_id, rlp) in lucky_peers { self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp); } @@ -2025,7 +2028,9 @@ mod tests { assert!(rlp_result.is_some()); // the length of one rlp-encoded hashe - assert_eq!(34, rlp_result.unwrap().1.out().len()); + let rlp = rlp_result.unwrap().1.out(); + let rlp = Rlp::new(&rlp); + assert_eq!(1, rlp.item_count()); io.sender = Some(2usize); diff --git a/sync/src/tests/snapshot.rs b/sync/src/tests/snapshot.rs index 58b7ec786..813513e84 100644 --- a/sync/src/tests/snapshot.rs +++ b/sync/src/tests/snapshot.rs @@ -23,6 +23,7 @@ use super::helpers::*; pub struct TestSnapshotService { manifest: Option, chunks: HashMap, + canon_hashes: Mutex>, restoration_manifest: Mutex>, state_restoration_chunks: Mutex>, @@ -34,6 +35,7 @@ impl TestSnapshotService { TestSnapshotService { manifest: None, chunks: HashMap::new(), + canon_hashes: Mutex::new(HashMap::new()), restoration_manifest: Mutex::new(None), state_restoration_chunks: Mutex::new(HashMap::new()), block_restoration_chunks: Mutex::new(HashMap::new()), @@ -57,6 +59,7 @@ impl TestSnapshotService { TestSnapshotService { manifest: Some(manifest), chunks: chunks, + canon_hashes: Mutex::new(HashMap::new()), restoration_manifest: Mutex::new(None), state_restoration_chunks: Mutex::new(HashMap::new()), block_restoration_chunks: Mutex::new(HashMap::new()), @@ -110,6 +113,10 @@ impl SnapshotService for TestSnapshotService { self.block_restoration_chunks.lock().insert(hash, chunk); } } + + fn provide_canon_hashes(&self, hashes: &[(u64, H256)]) { + self.canon_hashes.lock().extend(hashes.iter().cloned()); + } } #[test] diff --git a/util/Cargo.toml b/util/Cargo.toml index c560a6bb5..78cca92e0 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -23,7 +23,7 @@ rlp = { path = "rlp" } heapsize = { version = "0.3", features = ["unstable"] } itertools = "0.4" sha3 = { path = "sha3" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethcore-devtools = { path = "../devtools" } libc = "0.2.7" vergen = "0.1" @@ -36,6 +36,7 @@ ansi_term = "0.7" tiny-keccak= "1.0" ethcore-bloom-journal = { path = "bloom" } regex = "0.1" +lru-cache = "0.1.0" [features] default = [] diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index b5292db60..8b566fcf2 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -14,7 +14,7 @@ time = "0.1.34" tiny-keccak = "1.0" rust-crypto = "0.2.34" slab = "0.2" -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} igd = "0.5.0" libc = "0.2.7" parking_lot = "0.3" diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 8d5578e83..6d6009535 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -395,7 +395,7 @@ impl Session { PACKET_PEERS => Ok(SessionData::None), PACKET_USER ... PACKET_LAST => { let mut i = 0usize; - while packet_id > self.info.capabilities[i].id_offset + self.info.capabilities[i].packet_count { + while packet_id >= self.info.capabilities[i].id_offset + self.info.capabilities[i].packet_count { i += 1; if i == self.info.capabilities.len() { debug!(target: "network", "Unknown packet: {:?}", packet_id); @@ -406,6 +406,7 @@ impl Session { // map to protocol let protocol = self.info.capabilities[i].protocol; let pid = packet_id - self.info.capabilities[i].id_offset; + trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) }, _ => { diff --git a/util/src/cache.rs b/util/src/cache.rs new file mode 100644 index 000000000..2b2c50c8b --- /dev/null +++ b/util/src/cache.rs @@ -0,0 +1,79 @@ +// 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 . + +//! Lru-cache related utilities as quick-and-dirty wrappers around the lru-cache +//! crate. +// TODO: push changes upstream in a clean way. + +use heapsize::HeapSizeOf; +use lru_cache::LruCache; + +use std::hash::Hash; + +const INITIAL_CAPACITY: usize = 4; + +/// An LRU-cache which operates on memory used. +pub struct MemoryLruCache { + inner: LruCache, + cur_size: usize, + max_size: usize, +} + +impl MemoryLruCache { + /// Create a new cache with a maximum size in bytes. + pub fn new(max_size: usize) -> Self { + MemoryLruCache { + inner: LruCache::new(INITIAL_CAPACITY), + max_size: max_size, + cur_size: 0, + } + } + + /// Insert an item. + pub fn insert(&mut self, key: K, val: V) { + let cap = self.inner.capacity(); + + // grow the cache as necessary; it operates on amount of items + // but we're working based on memory usage. + if self.inner.len() == cap && self.cur_size < self.max_size { + self.inner.set_capacity(cap * 2); + } + + // account for any element displaced from the cache. + if let Some(lru) = self.inner.insert(key, val) { + self.cur_size -= lru.heap_size_of_children(); + } + + // remove elements until we are below the memory target. + while self.cur_size > self.max_size { + match self.inner.remove_lru() { + Some((_, v)) => self.cur_size -= v.heap_size_of_children(), + _ => break, + } + } + } + + /// Get a reference to an item in the cache. It is a logic error for its + /// heap size to be altered while borrowed. + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.inner.get_mut(key) + } + + /// Currently-used size of values in bytes. + pub fn current_size(&self) -> usize { + self.cur_size + } +} \ No newline at end of file diff --git a/util/src/journaldb/archivedb.rs b/util/src/journaldb/archivedb.rs index 940f92375..a8800045b 100644 --- a/util/src/journaldb/archivedb.rs +++ b/util/src/journaldb/archivedb.rs @@ -78,7 +78,7 @@ impl HashDB for ArchiveDB { ret.insert(h, 1); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } @@ -152,7 +152,7 @@ impl JournalDB for ArchiveDB { let mut inserts = 0usize; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc > 0 { batch.put(self.column, &key, &value); @@ -164,7 +164,7 @@ impl JournalDB for ArchiveDB { } } - for (mut key, value) in self.overlay.drain_aux().into_iter() { + for (mut key, value) in self.overlay.drain_aux() { key.push(AUX_FLAG); batch.put(self.column, &key, &value); } @@ -185,7 +185,7 @@ impl JournalDB for ArchiveDB { let mut inserts = 0usize; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc > 0 { if try!(self.backing.get(self.column, &key)).is_some() { @@ -204,7 +204,7 @@ impl JournalDB for ArchiveDB { } } - for (mut key, value) in self.overlay.drain_aux().into_iter() { + for (mut key, value) in self.overlay.drain_aux() { key.push(AUX_FLAG); batch.put(self.column, &key, &value); } diff --git a/util/src/journaldb/earlymergedb.rs b/util/src/journaldb/earlymergedb.rs index 1e782c580..d17c0ef1e 100644 --- a/util/src/journaldb/earlymergedb.rs +++ b/util/src/journaldb/earlymergedb.rs @@ -63,9 +63,11 @@ enum RemoveFrom { /// the removals actually take effect. /// /// journal format: +/// ``` /// [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, n] => [ ... ] +/// ``` /// /// When we make a new commit, we make a journal of all blocks in the recent history and record /// all keys that were inserted and deleted. The journal is ordered by era; multiple commits can @@ -80,6 +82,7 @@ enum RemoveFrom { /// which includes an original key, if any. /// /// The semantics of the `counter` are: +/// ``` /// insert key k: /// counter already contains k: count += 1 /// counter doesn't contain k: @@ -91,9 +94,11 @@ enum RemoveFrom { /// count == 1: remove counter /// count == 0: remove key from backing db /// counter doesn't contain k: remove key from backing db +/// ``` /// /// Practically, this means that for each commit block turning from recent to ancient we do the /// following: +/// ``` /// is_canonical: /// inserts: Ignored (left alone in the backing database). /// deletes: Enacted; however, recent history queue is checked for ongoing references. This is @@ -102,8 +107,9 @@ enum RemoveFrom { /// inserts: Reverted; however, recent history queue is checked for ongoing references. This is /// reduced as a preference to deletion from the backing database. /// deletes: Ignored (they were never inserted). +/// ``` /// -/// TODO: store_reclaim_period +/// TODO: `store_reclaim_period` pub struct EarlyMergeDB { overlay: MemoryDB, backing: Arc, @@ -310,7 +316,7 @@ impl HashDB for EarlyMergeDB { ret.insert(h, 1); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/journaldb/overlayrecentdb.rs b/util/src/journaldb/overlayrecentdb.rs index 83868d06b..42fe84557 100644 --- a/util/src/journaldb/overlayrecentdb.rs +++ b/util/src/journaldb/overlayrecentdb.rs @@ -379,7 +379,7 @@ impl HashDB for OverlayRecentDB { ret.insert(h, 1); } - for (key, refs) in self.transaction_overlay.keys().into_iter() { + for (key, refs) in self.transaction_overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/journaldb/refcounteddb.rs b/util/src/journaldb/refcounteddb.rs index 57621f321..d63f8837d 100644 --- a/util/src/journaldb/refcounteddb.rs +++ b/util/src/journaldb/refcounteddb.rs @@ -36,12 +36,14 @@ use std::env; /// the removals actually take effect. /// /// journal format: +/// ``` /// [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, n] => [ ... ] +/// ``` /// /// when we make a new commit, we journal the inserts and removes. -/// for each end_era that we journaled that we are no passing by, +/// for each `end_era` that we journaled that we are no passing by, /// we remove all of its removes assuming it is canonical and all /// of its inserts otherwise. // TODO: store last_era, reclaim_period. diff --git a/util/src/lib.rs b/util/src/lib.rs index e362459a6..f5558bcfc 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -105,6 +105,7 @@ extern crate ansi_term; extern crate tiny_keccak; extern crate rlp; extern crate regex; +extern crate lru_cache; #[macro_use] extern crate heapsize; @@ -143,6 +144,7 @@ pub mod semantic_version; pub mod log; pub mod path; pub mod snappy; +pub mod cache; mod timer; pub use common::*; diff --git a/util/src/migration/mod.rs b/util/src/migration/mod.rs index 80cfa29b6..9c1776699 100644 --- a/util/src/migration/mod.rs +++ b/util/src/migration/mod.rs @@ -231,7 +231,7 @@ impl Manager { trace!(target: "migration", "Total migrations to execute for version {}: {}", version, migrations.len()); if migrations.is_empty() { return Err(Error::MigrationImpossible) }; - let columns = migrations.iter().nth(0).and_then(|m| m.pre_columns()); + let columns = migrations.get(0).and_then(|m| m.pre_columns()); trace!(target: "migration", "Expecting database to contain {:?} columns", columns); let mut db_config = DatabaseConfig { diff --git a/util/src/overlaydb.rs b/util/src/overlaydb.rs index 9ebc7d1d4..009ef151e 100644 --- a/util/src/overlaydb.rs +++ b/util/src/overlaydb.rs @@ -66,7 +66,7 @@ impl OverlayDB { pub fn commit_to_batch(&mut self, batch: &mut DBTransaction) -> Result { let mut ret = 0u32; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc != 0 { match self.payload(&key) { @@ -133,7 +133,7 @@ impl HashDB for OverlayDB { ret.insert(h, r as i32); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/trie/journal.rs b/util/src/trie/journal.rs index 49bd1bf0f..55aed70ac 100644 --- a/util/src/trie/journal.rs +++ b/util/src/trie/journal.rs @@ -84,7 +84,7 @@ impl Journal { pub fn apply(self, db: &mut HashDB) -> Score { trace!("applying {:?} changes", self.0.len()); let mut ret = Score{inserts: 0, removes: 0}; - for d in self.0.into_iter() { + for d in self.0 { match d { Operation::Delete(h) => { trace!("TrieDBMut::apply --- {:?}", &h); diff --git a/util/src/trie/triedb.rs b/util/src/trie/triedb.rs index ad1e509a0..cd8c9939a 100644 --- a/util/src/trie/triedb.rs +++ b/util/src/trie/triedb.rs @@ -87,7 +87,7 @@ impl<'db> TrieDB<'db> { /// Convert a vector of hashes to a hashmap of hash to occurrences. pub fn to_map(hashes: Vec) -> HashMap { let mut r: HashMap = HashMap::new(); - for h in hashes.into_iter() { + for h in hashes { *r.entry(h).or_insert(0) += 1; } r @@ -97,7 +97,7 @@ impl<'db> TrieDB<'db> { /// trie. pub fn db_items_remaining(&self) -> super::Result> { let mut ret = self.db.keys(); - for (k, v) in Self::to_map(try!(self.keys())).into_iter() { + for (k, v) in Self::to_map(try!(self.keys())) { let keycount = *ret.get(&k).unwrap_or(&0); match keycount <= v as i32 { true => ret.remove(&k),