diff --git a/.gitignore b/.gitignore index 3226ea5a2..47546d0ed 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ # Build artifacts out/ + +.vscode diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d6fc6b2b2..9921af75e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,6 @@ stages: + - test + - js-build - build variables: GIT_DEPTH: "3" @@ -7,13 +9,13 @@ variables: RUSTFLAGS: "" CARGOFLAGS: "" cache: - key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" + key: "$CI_BUILD_REF_NAME" untracked: true linux-stable: stage: build image: ethcore/rust:stable only: - - master + - $NIGHTLY - beta - tags - stable @@ -43,7 +45,7 @@ linux-stable-14.04: stage: build image: ethcore/rust-14.04:latest only: - - master + - $NIGHTLY - beta - tags - stable @@ -73,7 +75,7 @@ linux-beta: stage: build image: ethcore/rust:beta only: - - master + - $NIGHTLY - beta - tags - stable @@ -92,7 +94,7 @@ linux-nightly: stage: build image: ethcore/rust:nightly only: - - master + - $NIGHTLY - beta - tags - stable @@ -111,7 +113,7 @@ linux-centos: stage: build image: ethcore/rust-centos:latest only: - - master + - $NIGHTLY - beta - tags - stable @@ -136,6 +138,7 @@ linux-armv7: stage: build image: ethcore/rust-armv7:latest only: + - $NIGHTLY - beta - tags - stable @@ -175,6 +178,7 @@ linux-arm: stage: build image: ethcore/rust-arm:latest only: + - $NIGHTLY - beta - tags - stable @@ -214,6 +218,7 @@ linux-armv6: stage: build image: ethcore/rust-armv6:latest only: + - $NIGHTLY - beta - tags - stable @@ -246,6 +251,7 @@ linux-aarch64: stage: build image: ethcore/rust-aarch64:latest only: + - $NIGHTLY - beta - tags - stable @@ -284,6 +290,7 @@ linux-aarch64: darwin: stage: build only: + - $NIGHTLY - beta - tags - stable @@ -304,6 +311,7 @@ darwin: windows: stage: build only: + - $NIGHTLY - beta - tags - stable @@ -350,49 +358,51 @@ windows: - target/release/parity.pdb - nsis/InstallParity.exe name: "x86_64-pc-windows-msvc_parity" -#test-darwin: -# stage: build -# before_script: -# - git submodule update --init --recursive -# script: -# - export RUST_BACKTRACE=1 -# - ./test.sh $CARGOFLAGS --no-release -# tags: -# - osx -#test-windows: -# stage: build -# before_script: -# - git submodule update --init --recursive -# script: -# - set RUST_BACKTRACE=1 -# - cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release -# tags: -# - rust-windows -# allow_failure: true -test-linux: - stage: build +test-darwin: + stage: test + only: + - beta + - tags + - stable before_script: - git submodule update --init --recursive script: - export RUST_BACKTRACE=1 - ./test.sh $CARGOFLAGS --no-release tags: - - rust-test -js-release: - stage: build - image: ethcore/javascript:latest + - osx +test-windows: + stage: test only: - - master + - beta + - tags + - stable before_script: - - ./js/scripts/install-deps.sh + - git submodule update --init --recursive script: - - ./js/scripts/build.sh - - ./js/scripts/release.sh + - set RUST_BACKTRACE=1 + - cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release tags: - - javascript + - rust-windows + allow_failure: true +test-rust-stable: + stage: test + image: ethcore/rust:stable + before_script: + - git submodule update --init --recursive + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF_NAME $(git merge-base $CI_BUILD_REF_NAME master) | grep \.js | wc -l) + - echo $JS_FILES_MODIFIED + - if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi + script: + - export RUST_BACKTRACE=1 + - echo $JS_FILES_MODIFIED + - if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi + tags: + - rust + - rust-stable js-tests: - stage: build - image: ethcore/javascript:latest + stage: test + image: ethcore/rust:stable before_script: - ./js/scripts/install-deps.sh script: @@ -401,3 +411,18 @@ js-tests: - ./js/scripts/build.sh tags: - javascript-test +js-release: + stage: js-build + only: + - master + - beta + - stable + - tags + image: ethcore/rust:stable + before_script: + - ./js/scripts/install-deps.sh + script: + - ./js/scripts/build.sh + - ./js/scripts/release.sh + tags: + - javascript diff --git a/Cargo.lock b/Cargo.lock index 7f9c73d6b..d9891668b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "ethcore-bigint" -version = "0.1.1" +version = "0.1.2" dependencies = [ "heapsize 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)", @@ -549,7 +549,7 @@ dependencies = [ "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)", - "ethcore-bigint 0.1.1", + "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -580,7 +580,7 @@ name = "ethcrypto" version = "0.1.0" dependencies = [ "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.1", + "ethcore-bigint 0.1.2", "ethkey 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -603,7 +603,7 @@ version = "0.2.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.1", + "ethcore-bigint 0.1.2", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#f8bd7fa67e91daea3ac698ebcc447fae494802cb" +source = "git+https://github.com/ethcore/js-precompiled.git#3a77750a2d29e2babb52bc4dfa5c40b2e26b97ce" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1463,7 +1463,7 @@ name = "rlp" version = "0.1.0" dependencies = [ "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", - "ethcore-bigint 0.1.1", + "ethcore-bigint 0.1.2", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/README.md b/README.md index 08c04c097..4861d7ac5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url] +[![Build Status][travis-image]][travis-url] [![build status](https://gitlab.ethcore.io/Mirrors/ethcore-parity/badges/master/build.svg)](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url] [Internal Documentation][doc-url] @@ -56,7 +56,14 @@ We recommend installing Rust through [rustup](https://www.rustup.rs/). If you do ```bash $ curl https://sh.rustup.rs -sSf | sh ``` + + Parity also requires `gcc`, `g++` and `make` packages to be installed. +- OSX: + ```bash + $ curl https://sh.rustup.rs -sSf | sh + ``` + `clang` and `make` are required. These come with Xcode command line tools or can be installed with homebrew. - Windows Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index 5be7b1caf..d8749ba91 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -11,7 +11,11 @@ "blockReward": "0x4563918244F40000", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "homesteadTransition": "0x118c30", - "eip150Transition": "0x2625a0" + "eip150Transition": "0x2625a0", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/eip150_test.json b/ethcore/res/ethereum/eip150_test.json index 39d4b3fe8..34ef478dc 100644 --- a/ethcore/res/ethereum/eip150_test.json +++ b/ethcore/res/ethereum/eip150_test.json @@ -10,7 +10,11 @@ "blockReward": "0x4563918244F40000", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "homesteadTransition": "0x0", - "eip150Transition": "0x0" + "eip150Transition": "0x0", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/eip161_test.json b/ethcore/res/ethereum/eip161_test.json new file mode 100644 index 000000000..884053d2a --- /dev/null +++ b/ethcore/res/ethereum/eip161_test.json @@ -0,0 +1,47 @@ +{ + "name": "Homestead (Test)", + "engine": { + "Ethash": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "homesteadTransition": "0x0", + "eip150Transition": "0x0", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0" + } + } + }, + "params": { + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x1" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } } + } +} diff --git a/ethcore/res/ethereum/expanse.json b/ethcore/res/ethereum/expanse.json index d2d036487..8d580b6f5 100644 --- a/ethcore/res/ethereum/expanse.json +++ b/ethcore/res/ethereum/expanse.json @@ -15,7 +15,11 @@ "difficultyHardforkTransition": "0x59d9", "difficultyHardforkBoundDivisor": "0x0200", "bombDefuseTransition": "0x30d40", - "eip150Transition": "0x7fffffffffffffff" + "eip150Transition": "0x7fffffffffffffff", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 111dff30e..ecaefa4c3 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -130,7 +130,11 @@ "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], - "eip150Transition": "0x259518" + "eip150Transition": "0x259518", + "eip155Transition": 2642462, + "eip160Transition": 2642462, + "eip161abcTransition": 2642462, + "eip161dTransition": 2642462 } } }, diff --git a/ethcore/res/ethereum/frontier_like_test.json b/ethcore/res/ethereum/frontier_like_test.json index 99a7ad712..8f41c61c8 100644 --- a/ethcore/res/ethereum/frontier_like_test.json +++ b/ethcore/res/ethereum/frontier_like_test.json @@ -130,7 +130,11 @@ "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], - "eip150Transition": "0x7fffffffffffffff" + "eip150Transition": "0x7fffffffffffffff", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/frontier_test.json b/ethcore/res/ethereum/frontier_test.json index 1cb3d8cfc..0fad8f37e 100644 --- a/ethcore/res/ethereum/frontier_test.json +++ b/ethcore/res/ethereum/frontier_test.json @@ -10,7 +10,11 @@ "blockReward": "0x4563918244F40000", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "homesteadTransition": "0x7fffffffffffffff", - "eip150Transition": "0x7fffffffffffffff" + "eip150Transition": "0x7fffffffffffffff", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/homestead_test.json b/ethcore/res/ethereum/homestead_test.json index ad64ce2d5..a757a7bc6 100644 --- a/ethcore/res/ethereum/homestead_test.json +++ b/ethcore/res/ethereum/homestead_test.json @@ -10,7 +10,11 @@ "blockReward": "0x4563918244F40000", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "homesteadTransition": "0x0", - "eip150Transition": "0x7fffffffffffffff" + "eip150Transition": "0x7fffffffffffffff", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 67d9ce044..9d54169c3 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -10,7 +10,11 @@ "blockReward": "0x4563918244F40000", "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", "homesteadTransition": "0x789b0", - "eip150Transition": "0x1b34d8" + "eip150Transition": "0x1b34d8", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/olympic.json b/ethcore/res/ethereum/olympic.json index ebc7abd4e..655410ee1 100644 --- a/ethcore/res/ethereum/olympic.json +++ b/ethcore/res/ethereum/olympic.json @@ -10,7 +10,11 @@ "blockReward": "0x14D1120D7B160000", "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050", "homesteadTransition": "0x7fffffffffffffff", - "eip150Transition": "0x7fffffffffffffff" + "eip150Transition": "0x7fffffffffffffff", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 97066e40c..853333e7d 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 97066e40ccd061f727deb5cd860e4d9135aa2551 +Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 diff --git a/ethcore/res/ethereum/transition_test.json b/ethcore/res/ethereum/transition_test.json index c004bc2ba..aebea2b4f 100644 --- a/ethcore/res/ethereum/transition_test.json +++ b/ethcore/res/ethereum/transition_test.json @@ -130,7 +130,11 @@ "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], - "eip150Transition": "0xa" + "eip150Transition": "0xa", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index d95c199ed..5910d0309 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -1468,7 +1468,7 @@ mod tests { action: Action::Create, value: 100.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let b1a = canon_chain @@ -1532,7 +1532,7 @@ mod tests { action: Action::Create, value: 100.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let t2 = Transaction { nonce: 1.into(), @@ -1541,7 +1541,7 @@ mod tests { action: Action::Create, value: 100.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let t3 = Transaction { nonce: 2.into(), @@ -1550,7 +1550,7 @@ mod tests { action: Action::Create, value: 100.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let b1a = canon_chain .with_transaction(t1.clone()) @@ -1856,7 +1856,7 @@ mod tests { action: Action::Create, value: 101.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let t2 = Transaction { nonce: 0.into(), gas_price: 0.into(), @@ -1864,7 +1864,7 @@ mod tests { action: Action::Create, value: 102.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let t3 = Transaction { nonce: 0.into(), gas_price: 0.into(), @@ -1872,7 +1872,7 @@ mod tests { action: Action::Create, value: 103.into(), data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let tx_hash1 = t1.hash(); let tx_hash2 = t2.hash(); let tx_hash3 = t3.hash(); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e35c27224..e95869732 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -33,7 +33,7 @@ use io::*; use views::{HeaderView, BodyView, BlockView}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; use header::BlockNumber; -use state::State; +use state::{State, CleanupMode}; use spec::Spec; use basic_types::Seal; use engines::Engine; @@ -268,6 +268,22 @@ impl Client { } } + /// The env info as of the best block. + fn latest_env_info(&self) -> EnvInfo { + let header_data = self.best_block_header(); + let view = HeaderView::new(&header_data); + + 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(), + } + } + fn build_last_hashes(&self, parent_hash: H256) -> Arc { { let hashes = self.last_hashes.read(); @@ -796,7 +812,7 @@ impl BlockChainClient for Client { let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance)); + state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = try!(Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(t, options)); @@ -1014,7 +1030,9 @@ impl BlockChainClient for Client { transaction_hash: transaction_hash.clone(), transaction_index: transaction_index, log_index: i - }).collect() + }).collect(), + log_bloom: receipt.log_bloom, + state_root: receipt.state_root, }) }, _ => None @@ -1175,24 +1193,16 @@ impl BlockChainClient for Client { fn pending_transactions(&self) -> Vec { self.miner.pending_transactions(self.chain.read().best_block_number()) } + + fn signing_network_id(&self) -> Option { + self.engine.signing_network_id(&self.latest_env_info()) + } } 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) + self.engine.schedule(&self.latest_env_info()) } fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 4e0a94345..9da6f84f0 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -237,7 +237,7 @@ impl TestBlockChainClient { gas_price: U256::one(), nonce: U256::zero() }; - let signed_tx = tx.sign(keypair.secret()); + let signed_tx = tx.sign(keypair.secret(), None); txs.append(&signed_tx); txs.out() }, @@ -303,7 +303,7 @@ impl TestBlockChainClient { gas_price: U256::one(), nonce: U256::zero() }; - let signed_tx = tx.sign(keypair.secret()); + let signed_tx = tx.sign(keypair.secret(), None); self.set_balance(signed_tx.sender().unwrap(), 10_000_000.into()); let res = self.miner.import_external_transactions(self, vec![signed_tx]); let res = res.into_iter().next().unwrap().expect("Successful import"); @@ -324,7 +324,7 @@ pub fn get_temp_state_db() -> GuardedTempResult { impl MiningBlockChainClient for TestBlockChainClient { fn latest_schedule(&self) -> Schedule { - Schedule::new_homestead_gas_fix() + Schedule::new_post_eip150(true, true, true) } fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { @@ -643,6 +643,8 @@ impl BlockChainClient for TestBlockChainClient { self.miner.pending_transactions(self.chain_info().best_block_number) } + fn signing_network_id(&self) -> Option { None } + fn mode(&self) -> Mode { Mode::Active } fn set_mode(&self, _: Mode) { unimplemented!(); } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index d7844ba3d..60be4ba1b 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -215,7 +215,7 @@ pub trait BlockChainClient : Sync + Send { /// Calculate median gas price from recent blocks if they have any transactions. fn gas_price_median(&self, sample_size: usize) -> Option { let corpus = self.gas_price_corpus(sample_size); - corpus.get(corpus.len()/2).cloned() + corpus.get(corpus.len() / 2).cloned() } /// Get the gas price distribution based on recent blocks if they have any transactions. @@ -223,12 +223,17 @@ pub trait BlockChainClient : Sync + Send { let raw_corpus = self.gas_price_corpus(sample_size); let raw_len = raw_corpus.len(); // Throw out outliers. - let (corpus, _) = raw_corpus.split_at(raw_len-raw_len/40); + let (corpus, _) = raw_corpus.split_at(raw_len - raw_len / 40); Histogram::new(corpus, bucket_number) } + /// Get the preferred network ID to sign on + fn signing_network_id(&self) -> Option; + + /// Get the mode. fn mode(&self) -> Mode; + /// Set the mode. fn set_mode(&self, mode: Mode); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index b8797305b..759e53076 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -112,6 +112,9 @@ pub trait Engine : Sync + Send { /// Verify a particular transaction is valid. fn verify_transaction(&self, _t: &SignedTransaction, _header: &Header) -> Result<(), Error> { Ok(()) } + /// The network ID that transactions should be signed with. + fn signing_network_id(&self, _env_info: &EnvInfo) -> Option { None } + /// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods /// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer /// methods are needed for an Engine, this may be overridden. diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 98ec42466..6b3a12fc7 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -76,6 +76,8 @@ pub enum TransactionError { RecipientBanned, /// Contract creation code is banned. CodeBanned, + /// Invalid network ID given. + InvalidNetworkId, } impl fmt::Display for TransactionError { @@ -99,6 +101,7 @@ impl fmt::Display for TransactionError { SenderBanned => "Sender is temporarily banned.".into(), RecipientBanned => "Recipient is temporarily banned.".into(), CodeBanned => "Contract code is temporarily banned.".into(), + InvalidNetworkId => "Transaction of this network ID is not allowed on this chain.".into(), }; f.write_fmt(format_args!("Transaction error ({})", msg)) diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 060a20aa2..7fbb408cc 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -19,8 +19,9 @@ use util::*; use block::*; use builtin::Builtin; use env_info::EnvInfo; -use error::{BlockError, Error}; +use error::{BlockError, TransactionError, Error}; use header::Header; +use state::CleanupMode; use spec::CommonParams; use transaction::SignedTransaction; use engines::Engine; @@ -59,8 +60,16 @@ pub struct EthashParams { pub difficulty_hardfork_bound_divisor: U256, /// Block on which there is no additional difficulty from the exponential bomb. pub bomb_defuse_transition: u64, - /// Bad gas transition block number. + /// Number of first block where EIP-150 rules begin. pub eip150_transition: u64, + /// Number of first block where EIP-155 rules begin. + pub eip155_transition: u64, + /// Number of first block where EIP-160 rules begin. + pub eip160_transition: u64, + /// Number of first block where EIP-161.abc begin. + pub eip161abc_transition: u64, + /// Number of first block where EIP-161.d begins. + pub eip161d_transition: u64, } impl From for EthashParams { @@ -81,6 +90,10 @@ impl From for EthashParams { difficulty_hardfork_bound_divisor: p.difficulty_hardfork_bound_divisor.map_or(p.difficulty_bound_divisor.into(), Into::into), bomb_defuse_transition: p.bomb_defuse_transition.map_or(0x7fffffffffffffff, Into::into), eip150_transition: p.eip150_transition.map_or(0, Into::into), + eip155_transition: p.eip155_transition.map_or(0, Into::into), + eip160_transition: p.eip160_transition.map_or(0, Into::into), + eip161abc_transition: p.eip161abc_transition.map_or(0, Into::into), + eip161d_transition: p.eip161d_transition.map_or(0x7fffffffffffffff, Into::into), } } } @@ -132,7 +145,19 @@ impl Engine for Ethash { } else if env_info.number < self.ethash_params.eip150_transition { Schedule::new_homestead() } else { - Schedule::new_homestead_gas_fix() + Schedule::new_post_eip150( + env_info.number >= self.ethash_params.eip160_transition, + env_info.number >= self.ethash_params.eip161abc_transition, + env_info.number >= self.ethash_params.eip161d_transition + ) + } + } + + fn signing_network_id(&self, env_info: &EnvInfo) -> Option { + if env_info.number >= self.ethash_params.eip155_transition && self.params().network_id < 127 { + Some(self.params().network_id as u8) + } else { + None } } @@ -169,7 +194,7 @@ impl Engine for Ethash { let mut state = block.fields_mut().state; for child in &self.ethash_params.dao_hardfork_accounts { let b = state.balance(child); - state.transfer_balance(child, &self.ethash_params.dao_hardfork_beneficiary, &b); + state.transfer_balance(child, &self.ethash_params.dao_hardfork_beneficiary, &b, CleanupMode::NoEmpty); } // } } @@ -182,12 +207,12 @@ impl Engine for Ethash { let fields = block.fields_mut(); // Bestow block reward - fields.state.add_balance(fields.header.author(), &(reward + reward / U256::from(32) * U256::from(fields.uncles.len()))); + fields.state.add_balance(fields.header.author(), &(reward + reward / U256::from(32) * U256::from(fields.uncles.len())), CleanupMode::NoEmpty); // Bestow uncle rewards let current_number = fields.header.number(); for u in fields.uncles.iter() { - fields.state.add_balance(u.author(), &(reward * U256::from(8 + u.number() - current_number) / U256::from(8))); + fields.state.add_balance(u.author(), &(reward * U256::from(8 + u.number() - current_number) / U256::from(8)), CleanupMode::NoEmpty); } // Commit state so that we can actually figure out the state root. @@ -277,6 +302,13 @@ impl Engine for Ethash { if header.number() >= self.ethash_params.homestead_transition { try!(t.check_low_s()); } + + if let Some(n) = t.network_id() { + if header.number() < self.ethash_params.eip155_transition || n as usize != self.params().network_id { + return Err(TransactionError::InvalidNetworkId.into()) + } + } + Ok(()) } diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index d8299324d..253a12372 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -54,6 +54,9 @@ pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/ho /// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) } +/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. +pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) } + /// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8. pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) } diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index 6397f067e..1c340b5b1 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -52,6 +52,12 @@ pub trait Ext { /// Determine whether an account exists. fn exists(&self, address: &Address) -> bool; + /// Determine whether an account exists and is not null (zero balance/nonce, no code). + fn exists_and_not_null(&self, address: &Address) -> bool; + + /// Balance of the origin account. + fn origin_balance(&self) -> U256; + /// Returns address balance. fn balance(&self, address: &Address) -> U256; diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index a2b940655..beaaadac5 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -146,8 +146,13 @@ impl Gasometer { instructions::SUICIDE => { let mut gas = Gas::from(schedule.suicide_gas); + let is_value_transfer = !ext.origin_balance().is_zero(); let address = u256_to_address(stack.peek(0)); - if !ext.exists(&address) { + if ( + !schedule.no_empty && !ext.exists(&address) + ) || ( + schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address) + ) { gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into())); } @@ -190,12 +195,19 @@ impl Gasometer { ); let address = u256_to_address(stack.peek(1)); + let is_value_transfer = !stack.peek(2).is_zero(); - if instruction == instructions::CALL && !ext.exists(&address) { - gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into())); + if instruction == instructions::CALL { + if ( + !schedule.no_empty && !ext.exists(&address) + ) || ( + schedule.no_empty && is_value_transfer && !ext.exists_and_not_null(&address) + ) { + gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into())); + } }; - if !stack.peek(2).is_zero() { + if is_value_transfer { gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into())); }; diff --git a/ethcore/src/evm/schedule.rs b/ethcore/src/evm/schedule.rs index b8de785b3..b68f6acb5 100644 --- a/ethcore/src/evm/schedule.rs +++ b/ethcore/src/evm/schedule.rs @@ -93,6 +93,10 @@ pub struct Schedule { /// If Some(x): let limit = GAS * (x - 1) / x; let CALL's gas = min(requested, limit). let CREATE's gas = limit. /// If None: let CALL's gas = (requested > GAS ? [OOG] : GAS). let CREATE's gas = GAS pub sub_gas_cap_divisor: Option, + /// Don't ever make empty accounts; contracts start with nonce=1. Also, don't charge 25k when sending/suicide zero-value. + pub no_empty: bool, + /// Kill empty accounts if touched. + pub kill_empty: bool, } impl Schedule { @@ -106,16 +110,16 @@ impl Schedule { Self::new(true, true, 53000) } - /// Schedule for the Homestead-era of the Ethereum main net. - pub fn new_homestead_gas_fix() -> Schedule { - Schedule{ + /// Schedule for the post-EIP-150-era of the Ethereum main net. + pub fn new_post_eip150(fix_exp: bool, no_empty: bool, kill_empty: bool) -> Schedule { + Schedule { exceptional_failed_code_deposit: true, have_delegate_call: true, stack_limit: 1024, max_depth: 1024, tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0], exp_gas: 10, - exp_byte_gas: 10, + exp_byte_gas: if fix_exp {50} else {10}, sha3_gas: 30, sha3_word_gas: 6, sload_gas: 200, @@ -146,11 +150,13 @@ impl Schedule { suicide_gas: 5000, suicide_to_new_account_cost: 25000, sub_gas_cap_divisor: Some(64), + no_empty: no_empty, + kill_empty: kill_empty, } } fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule { - Schedule{ + Schedule { exceptional_failed_code_deposit: efcd, have_delegate_call: hdc, stack_limit: 1024, @@ -188,6 +194,8 @@ impl Schedule { suicide_gas: 0, suicide_to_new_account_cost: 0, sub_gas_cap_divisor: None, + no_empty: false, + kill_empty: false, } } } diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index ba002d649..7e69c0771 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -94,6 +94,14 @@ impl Ext for FakeExt { self.balances.contains_key(address) } + fn exists_and_not_null(&self, address: &Address) -> bool { + self.balances.get(address).map_or(false, |b| !b.is_zero()) + } + + fn origin_balance(&self) -> U256 { + unimplemented!() + } + fn balance(&self, address: &Address) -> U256 { *self.balances.get(address).unwrap() } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index f05cc4fd8..5da105e2f 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -17,7 +17,7 @@ //! Transaction Execution environment. use util::*; use action_params::{ActionParams, ActionValue}; -use state::{State, Substate}; +use state::{State, Substate, CleanupMode}; use engines::Engine; use types::executed::CallType; use env_info::EnvInfo; @@ -256,9 +256,11 @@ impl<'a> Executive<'a> { // backup used in case of running out of gas self.state.checkpoint(); + let schedule = self.engine.schedule(self.info); + // at first, transfer value to destination if let ActionValue::Transfer(val) = params.value { - self.state.transfer_balance(¶ms.sender, ¶ms.address, &val); + self.state.transfer_balance(¶ms.sender, ¶ms.address, &val, substate.to_cleanup_mode(&schedule)); } trace!("Executive::call(params={:?}) self.env_info={:?}", params, self.info); @@ -364,12 +366,14 @@ impl<'a> Executive<'a> { let mut unconfirmed_substate = Substate::new(); // create contract and transfer value to it if necessary + let schedule = self.engine.schedule(self.info); + let nonce_offset = if schedule.no_empty {1} else {0}.into(); let prev_bal = self.state.balance(¶ms.address); if let ActionValue::Transfer(val) = params.value { self.state.sub_balance(¶ms.sender, &val); - self.state.new_contract(¶ms.address, val + prev_bal); + self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset); } else { - self.state.new_contract(¶ms.address, prev_bal); + self.state.new_contract(¶ms.address, prev_bal, nonce_offset); } let trace_info = tracer.prepare_trace_create(¶ms); @@ -405,7 +409,7 @@ impl<'a> Executive<'a> { fn finalize( &mut self, t: &SignedTransaction, - substate: Substate, + mut substate: Substate, result: evm::Result, output: Bytes, trace: Vec, @@ -440,15 +444,23 @@ impl<'a> Executive<'a> { }; trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, sender); - self.state.add_balance(&sender, &refund_value); + // Below: NoEmpty is safe since the sender must already be non-null to have sent this transaction + self.state.add_balance(&sender, &refund_value, CleanupMode::NoEmpty); trace!("exec::finalize: Compensating author: fees_value={}, author={}\n", fees_value, &self.info.author); - self.state.add_balance(&self.info.author, &fees_value); + self.state.add_balance(&self.info.author, &fees_value, substate.to_cleanup_mode(&schedule)); // perform suicides for address in &substate.suicides { self.state.kill_account(address); } + // perform garbage-collection + for address in &substate.garbage { + if self.state.exists(address) && !self.state.exists_and_not_null(address) { + self.state.kill_account(address); + } + } + match result { Err(evm::Error::Internal) => Err(ExecutionError::Internal), Err(_) => { @@ -509,7 +521,7 @@ mod tests { use env_info::EnvInfo; use evm::{Factory, VMType}; use error::ExecutionError; - use state::Substate; + use state::{Substate, CleanupMode}; use tests::helpers::*; use trace::trace; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer}; @@ -538,7 +550,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(0x7)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(0x100u64)); + state.add_balance(&sender, &U256::from(0x100u64), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -597,7 +609,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100)); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -656,7 +668,7 @@ mod tests { params.call_type = CallType::Call; let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100)); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(5); let mut substate = Substate::new(); @@ -767,7 +779,7 @@ mod tests { params.value = ActionValue::Transfer(100.into()); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100)); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(5); let mut substate = Substate::new(); @@ -855,7 +867,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100)); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); @@ -907,7 +919,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from(100)); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100)); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(1024); let mut substate = Substate::new(); @@ -967,7 +979,7 @@ mod tests { let mut state = state_result.reference_mut(); state.init_code(&address_a, code_a.clone()); state.init_code(&address_b, code_b.clone()); - state.add_balance(&sender, &U256::from(100_000)); + state.add_balance(&sender, &U256::from(100_000), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(0); @@ -1040,13 +1052,13 @@ mod tests { gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::zero() - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let sender = t.sender().unwrap(); let contract = contract_address(&sender, &U256::zero()); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(18)); + state.add_balance(&sender, &U256::from(18), CleanupMode::NoEmpty); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1107,12 +1119,12 @@ mod tests { gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::one() - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let sender = t.sender().unwrap(); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(17)); + state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1140,12 +1152,12 @@ mod tests { gas: U256::from(80_001), gas_price: U256::zero(), nonce: U256::zero() - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let sender = t.sender().unwrap(); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(17)); + state.add_balance(&sender, &U256::from(17), CleanupMode::NoEmpty); let mut info = EnvInfo::default(); info.gas_used = U256::from(20_000); info.gas_limit = U256::from(100_000); @@ -1175,12 +1187,12 @@ mod tests { gas: U256::from(100_000), gas_price: U256::one(), nonce: U256::zero() - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let sender = t.sender().unwrap(); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from(100_017)); + state.add_balance(&sender, &U256::from(100_017), CleanupMode::NoEmpty); let mut info = EnvInfo::default(); info.gas_limit = U256::from(100_000); let engine = TestEngine::new(0); @@ -1215,7 +1227,7 @@ mod tests { params.value = ActionValue::Transfer(U256::from_str("0de0b6b3a7640000").unwrap()); let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); - state.add_balance(&sender, &U256::from_str("152d02c7e14af6800000").unwrap()); + state.add_balance(&sender, &U256::from_str("152d02c7e14af6800000").unwrap(), CleanupMode::NoEmpty); let info = EnvInfo::default(); let engine = TestEngine::new(0); let mut substate = Substate::new(); diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index bbe81a511..df1b64e67 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -114,6 +114,12 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT self.state.exists(address) } + fn exists_and_not_null(&self, address: &Address) -> bool { + self.state.exists_and_not_null(address) + } + + fn origin_balance(&self) -> U256 { self.balance(&self.origin_info.address) } + fn balance(&self, address: &Address) -> U256 { self.state.balance(address) } @@ -269,11 +275,11 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT let address = self.origin_info.address.clone(); let balance = self.balance(&address); if &address == refund_address { - // TODO [todr] To be consisted with CPP client we set balance to 0 in that case. + // TODO [todr] To be consistent with CPP client we set balance to 0 in that case. self.state.sub_balance(&address, &balance); } else { - trace!("Suiciding {} -> {} (xfer: {})", address, refund_address, balance); - self.state.transfer_balance(&address, refund_address, &balance); + trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance); + self.state.transfer_balance(&address, refund_address, &balance, self.substate.to_cleanup_mode(&self.schedule)); } self.tracer.trace_suicide(address, balance, refund_address.clone()); diff --git a/ethcore/src/json_tests/chain.rs b/ethcore/src/json_tests/chain.rs index bf32db133..b50241199 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -49,6 +49,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { ChainEra::Frontier => ethereum::new_frontier_test(), ChainEra::Homestead => ethereum::new_homestead_test(), ChainEra::Eip150 => ethereum::new_eip150_test(), + ChainEra::Eip161 => ethereum::new_eip161_test(), ChainEra::TransitionTest => ethereum::new_transition_test(), }; spec.set_genesis_state(state); diff --git a/ethcore/src/json_tests/eip161_state.rs b/ethcore/src/json_tests/eip161_state.rs new file mode 100644 index 000000000..da7997fa1 --- /dev/null +++ b/ethcore/src/json_tests/eip161_state.rs @@ -0,0 +1,51 @@ +// 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 . + +use super::test_common::*; +use tests::helpers::*; +use super::state::json_chain_test; + +fn do_json_test(json_data: &[u8]) -> Vec { + json_chain_test(json_data, ChainEra::Eip161) +} + +declare_test!{StateTests_EIP158_stEIP158SpecificTest, "StateTests/EIP158/stEIP158SpecificTest"} +declare_test!{StateTests_EIP158_stNonZeroCallsTest, "StateTests/EIP158/stNonZeroCallsTest"} +declare_test!{StateTests_EIP158_stZeroCallsTest, "StateTests/EIP158/stZeroCallsTest"} + +declare_test!{StateTests_EIP158_EIP150_stMemExpandingEIPCalls, "StateTests/EIP158/EIP150/stMemExpandingEIPCalls"} +declare_test!{StateTests_EIP158_EIP150_stEIPSpecificTest, "StateTests/EIP158/EIP150/stEIPSpecificTest"} +declare_test!{StateTests_EIP158_EIP150_stEIPsingleCodeGasPrices, "StateTests/EIP158/EIP150/stEIPsingleCodeGasPrices"} +declare_test!{StateTests_EIP158_EIP150_stChangedTests, "StateTests/EIP158/EIP150/stChangedTests"} + +declare_test!{StateTests_EIP158_Homestead_stBoundsTest, "StateTests/EIP158/Homestead/stBoundsTest"} +declare_test!{StateTests_EIP158_Homestead_stCallCodes, "StateTests/EIP158/Homestead/stCallCodes"} +declare_test!{StateTests_EIP158_Homestead_stCallCreateCallCodeTest, "StateTests/EIP158/Homestead/stCallCreateCallCodeTest"} +declare_test!{StateTests_EIP158_Homestead_stCallDelegateCodes, "StateTests/EIP158/Homestead/stCallDelegateCodes"} +declare_test!{StateTests_EIP158_Homestead_stCallDelegateCodesCallCode, "StateTests/EIP158/Homestead/stCallDelegateCodesCallCode"} +declare_test!{StateTests_EIP158_Homestead_stDelegatecallTest, "StateTests/EIP158/Homestead/stDelegatecallTest"} +declare_test!{StateTests_EIP158_Homestead_stHomeSteadSpecific, "StateTests/EIP158/Homestead/stHomeSteadSpecific"} +declare_test!{StateTests_EIP158_Homestead_stInitCodeTest, "StateTests/EIP158/Homestead/stInitCodeTest"} +declare_test!{StateTests_EIP158_Homestead_stLogTests, "StateTests/EIP158/Homestead/stLogTests"} +declare_test!{heavy => StateTests_EIP158_Homestead_stMemoryTest, "StateTests/EIP158/Homestead/stMemoryTest"} +declare_test!{StateTests_EIP158_Homestead_stPreCompiledContracts, "StateTests/EIP158/Homestead/stPreCompiledContracts"} +declare_test!{heavy => StateTests_EIP158_Homestead_stQuadraticComplexityTest, "StateTests/EIP158/Homestead/stQuadraticComplexityTest"} +declare_test!{StateTests_EIP158_Homestead_stRecursiveCreate, "StateTests/EIP158/Homestead/stRecursiveCreate"} +declare_test!{StateTests_EIP158_Homestead_stRefundTest, "StateTests/EIP158/Homestead/stRefundTest"} +declare_test!{StateTests_EIP158_Homestead_stSpecialTest, "StateTests/EIP158/Homestead/stSpecialTest"} +declare_test!{StateTests_EIP158_Homestead_stSystemOperationsTest, "StateTests/EIP158/Homestead/stSystemOperationsTest"} +declare_test!{StateTests_EIP158_Homestead_stTransactionTest, "StateTests/EIP158/Homestead/stTransactionTest"} +declare_test!{StateTests_EIP158_Homestead_stWalletTest, "StateTests/EIP158/Homestead/stWalletTest"} \ No newline at end of file diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 1d4faec62..60321f971 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -92,10 +92,18 @@ impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer { self.ext.exists(address) } + fn exists_and_not_null(&self, address: &Address) -> bool { + self.ext.exists_and_not_null(address) + } + fn balance(&self, address: &Address) -> U256 { self.ext.balance(address) } + fn origin_balance(&self) -> U256 { + self.ext.origin_balance() + } + fn blockhash(&self, number: &U256) -> H256 { self.ext.blockhash(number) } diff --git a/ethcore/src/json_tests/mod.rs b/ethcore/src/json_tests/mod.rs index 7a5dd30d2..13d3fb5bb 100644 --- a/ethcore/src/json_tests/mod.rs +++ b/ethcore/src/json_tests/mod.rs @@ -24,4 +24,5 @@ mod chain; mod homestead_state; mod homestead_chain; mod eip150_state; +mod eip161_state; mod trie; diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index c3e74af5d..bf84d50ee 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -29,6 +29,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { ChainEra::Frontier => ethereum::new_mainnet_like().engine, ChainEra::Homestead => ethereum::new_homestead_test().engine, ChainEra::Eip150 => ethereum::new_eip150_test().engine, + ChainEra::Eip161 => ethereum::new_eip161_test().engine, ChainEra::TransitionTest => ethereum::new_transition_test().engine, }; diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index 50061cbfd..dd5a4ef14 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -35,17 +35,24 @@ fn do_json_test(json_data: &[u8]) -> Vec { Some(x) if x < 1_150_000 => &old_schedule, Some(_) => &new_schedule }; + let allow_network_id_of_one = number.map_or(false, |n| n > 2600000); let rlp: Vec = test.rlp.into(); let res = UntrustedRlp::new(&rlp) .as_val() .map_err(From::from) - .and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call)); + .and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call, allow_network_id_of_one)); fail_unless(test.transaction.is_none() == res.is_err()); if let (Some(tx), Some(sender)) = (test.transaction, test.sender) { let t = res.unwrap(); fail_unless(t.sender().unwrap() == sender.into()); + let is_acceptable_network_id = match t.network_id() { + None => true, + Some(1) if allow_network_id_of_one => true, + _ => false, + }; + fail_unless(is_acceptable_network_id); let data: Vec = tx.data.into(); fail_unless(t.data == data); fail_unless(t.gas_price == tx.gas_price.into()); diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs index f127dc7e8..0fdea2ac3 100644 --- a/ethcore/src/miner/banning_queue.rs +++ b/ethcore/src/miner/banning_queue.rs @@ -245,7 +245,7 @@ mod tests { gas: U256::from(100_000), gas_price: U256::from(10), nonce: U256::from(0), - }.sign(keypair.secret()) + }.sign(keypair.secret(), None) } fn unwrap_err(res: Result) -> TransactionError { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 23dbe113a..11077a57f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -21,7 +21,7 @@ use util::*; use util::using_queue::{UsingQueue, GetAction}; use account_provider::AccountProvider; use views::{BlockView, HeaderView}; -use state::State; +use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; @@ -657,7 +657,7 @@ impl MinerService for Miner { let needed_balance = t.value + t.gas * t.gas_price; if balance < needed_balance { // give the sender a sufficient balance - state.add_balance(&sender, &(needed_balance - balance)); + state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty); } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = try!(Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)); @@ -942,6 +942,8 @@ impl MinerService for Miner { } }, logs: receipt.logs.clone(), + log_bloom: receipt.log_bloom, + state_root: receipt.state_root, } }) } @@ -1180,7 +1182,7 @@ mod tests { gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::zero(), - }.sign(keypair.secret()) + }.sign(keypair.secret(), None) } #[test] diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 51c1863f6..cc10bbe98 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -42,8 +42,8 @@ //! let t2 = Transaction { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), //! gas: U256::from(100_000), gas_price: U256::one(), nonce: U256::from(11) }; //! -//! let st1 = t1.sign(&key.secret()); -//! let st2 = t2.sign(&key.secret()); +//! let st1 = t1.sign(&key.secret(), None); +//! let st2 = t2.sign(&key.secret(), None); //! let default_account_details = |_a: &Address| AccountDetails { //! nonce: U256::from(10), //! balance: U256::from(1_000_000), @@ -1104,12 +1104,12 @@ mod test { fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction { let keypair = Random.generate().unwrap(); - new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret()) + new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret(), None) } fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction { let keypair = Random.generate().unwrap(); - new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret()) + new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret(), None) } fn new_tx_default() -> SignedTransaction { @@ -1133,7 +1133,7 @@ mod test { let keypair = Random.generate().unwrap(); let secret = &keypair.secret(); - (tx1.sign(secret), tx2.sign(secret)) + (tx1.sign(secret, None), tx2.sign(secret, None)) } /// Returns two consecutive transactions, both with increased gas price @@ -1144,7 +1144,7 @@ mod test { let keypair = Random.generate().unwrap(); let secret = &keypair.secret(); - (tx1.sign(secret), tx2.sign(secret)) + (tx1.sign(secret, None), tx2.sign(secret, None)) } fn new_tx_pair_default(nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { @@ -1798,9 +1798,9 @@ mod test { let mut txq = TransactionQueue::default(); let kp = Random.generate().unwrap(); let secret = kp.secret(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret); - let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret); - let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret); + let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret, None); + let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None); + let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None); txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); @@ -2038,11 +2038,11 @@ mod test { // given let mut txq = TransactionQueue::default(); let keypair = Random.generate().unwrap(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret()); + let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); let tx2 = { let mut tx2 = (*tx).clone(); tx2.gas_price = U256::from(200); - tx2.sign(keypair.secret()) + tx2.sign(keypair.secret(), None) }; // when @@ -2061,16 +2061,16 @@ mod test { // given let mut txq = TransactionQueue::default(); let keypair = Random.generate().unwrap(); - let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret()); + let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); let tx1 = { let mut tx1 = (*tx0).clone(); tx1.nonce = U256::from(124); - tx1.sign(keypair.secret()) + tx1.sign(keypair.secret(), None) }; let tx2 = { let mut tx2 = (*tx1).clone(); tx2.gas_price = U256::from(200); - tx2.sign(keypair.secret()) + tx2.sign(keypair.secret(), None) }; // when @@ -2223,7 +2223,7 @@ mod test { let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); - (tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret)) + (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) }; let sender = tx1.sender().unwrap(); txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 26318b673..b498cbd41 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -38,7 +38,7 @@ pub struct CommonParams { /// Maximum size of extra data. pub maximum_extra_data_size: usize, /// Network id. - pub network_id: U256, + pub network_id: usize, /// Main subprotocol name. pub subprotocol_name: String, /// Minimum gas limit. @@ -161,7 +161,7 @@ impl Spec { pub fn nodes(&self) -> &[String] { &self.nodes } /// Get the configured Network ID. - pub fn network_id(&self) -> U256 { self.params.network_id } + pub fn network_id(&self) -> usize { self.params.network_id } /// Get the configured Network ID. pub fn subprotocol_name(&self) -> String { self.params.subprotocol_name.clone() } @@ -251,7 +251,7 @@ impl Spec { } trace!(target: "spec", "ensure_db_good: Populated sec trie; root is {}", root); for (address, account) in self.genesis_state.get().iter() { - db.note_account_bloom(address); + db.note_non_null_account(address); account.insert_additional(&mut AccountDBMut::new(db.as_hashdb_mut(), address)); } assert!(db.as_hashdb().contains(&self.state_root())); diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index d8d281b17..76061f6a0 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -300,11 +300,17 @@ impl Account { pub fn storage_is_clean(&self) -> bool { self.storage_changes.is_empty() } /// Check if account has zero nonce, balance, no code and no storage. + /// + /// NOTE: Will panic if `!self.storage_is_clean()` pub fn is_empty(&self) -> bool { - self.storage_changes.is_empty() && + assert!(self.storage_is_clean(), "Account::is_empty() may only legally be called when storage is clean."); + self.is_null() && self.storage_root == SHA3_NULL_RLP + } + + /// Check if account has zero nonce, balance, no code. + pub fn is_null(&self) -> bool { self.balance.is_zero() && self.nonce.is_zero() && - self.storage_root == SHA3_NULL_RLP && self.code_hash == SHA3_EMPTY } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 7c0f43d97..01a7e3b15 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -199,6 +199,13 @@ enum RequireCache { Code, } +#[derive(PartialEq)] +pub enum CleanupMode<'a> { + ForceCreate, + NoEmpty, + KillEmpty(&'a mut HashSet
), +} + const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \ Therefore creating a SecTrieDB with this state's root will not fail."; @@ -329,8 +336,8 @@ impl State { /// Create a new contract at address `contract`. If there is already an account at the address /// it will have its code reset, ready for `init_code()`. - pub fn new_contract(&mut self, contract: &Address, balance: U256) { - self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce)))); + pub fn new_contract(&mut self, contract: &Address, balance: U256, nonce_offset: U256) { + self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce + nonce_offset)))); } /// Remove an existing account. @@ -341,10 +348,15 @@ impl State { /// Determine whether an account exists. pub fn exists(&self, a: &Address) -> bool { // Bloom filter does not contain empty accounts, so it is important here to - // check if account exists in the database directly before EIP-158 is in effect. + // check if account exists in the database directly before EIP-161 is in effect. self.ensure_cached(a, RequireCache::None, false, |a| a.is_some()) } + /// Determine whether an account exists and if not empty. + pub fn exists_and_not_null(&self, a: &Address) -> bool { + self.ensure_cached(a, RequireCache::None, false, |a| a.map_or(false, |a| !a.is_null())) + } + /// Get the balance of account `a`. pub fn balance(&self, a: &Address) -> U256 { self.ensure_cached(a, RequireCache::None, true, @@ -399,7 +411,7 @@ impl State { } // check bloom before any requests to trie - if !self.db.check_account_bloom(address) { return H256::zero() } + if !self.db.check_non_null_bloom(address) { return H256::zero() } // account is not found in the global cache, get from the DB and insert into local let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); @@ -433,10 +445,18 @@ impl State { } /// Add `incr` to the balance of account `a`. - pub fn add_balance(&mut self, a: &Address, incr: &U256) { + pub fn add_balance(&mut self, a: &Address, incr: &U256, cleanup_mode: CleanupMode) { trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)); - if !incr.is_zero() || !self.exists(a) { + let is_value_transfer = !incr.is_zero(); + if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)) { self.require(a, false).add_balance(incr); + } else { + match cleanup_mode { + CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a) && !self.exists_and_not_null(a) { + set.insert(a.clone()); + }, + _ => {} + } } } @@ -449,9 +469,9 @@ impl State { } /// Subtracts `by` from the balance of `from` and adds it to that of `to`. - pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256) { + pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, cleanup_mode: CleanupMode) { self.sub_balance(from, by); - self.add_balance(to, by); + self.add_balance(to, by, cleanup_mode); } /// Increment the nonce of account `a` by 1. @@ -507,13 +527,15 @@ impl State { // first, commit the sub trees. for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { 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()); + { + 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 !account.is_empty() { + db.note_non_null_account(address); + } } } @@ -653,7 +675,7 @@ impl State { Some(r) => r, None => { // first check bloom if it is not in database for sure - if check_bloom && !self.db.check_account_bloom(a) { return f(None); } + if check_bloom && !self.db.check_non_null_bloom(a) { return f(None); } // not found in the global cache, get from the DB and insert into local let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); @@ -687,7 +709,7 @@ impl State { match self.db.get_cached_account(a) { Some(acc) => self.insert_cache(a, AccountEntry::new_clean_cached(acc)), None => { - let maybe_acc = if self.db.check_account_bloom(a) { + let maybe_acc = if self.db.check_non_null_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); match db.get(a) { Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))), @@ -793,9 +815,9 @@ fn should_apply_create_transaction() { action: Action::Create, value: 100.into(), data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -853,9 +875,9 @@ fn should_trace_failed_create_transaction() { action: Action::Create, value: 100.into(), data: FromHex::from_hex("5b600056").unwrap(), - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -890,10 +912,10 @@ fn should_trace_call_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -933,9 +955,9 @@ fn should_trace_basic_call_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -975,7 +997,7 @@ fn should_trace_call_transaction_to_builtin() { action: Action::Call(0x1.into()), value: 0.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); let result = state.apply(&info, engine, &t, true).unwrap(); @@ -1017,7 +1039,7 @@ fn should_not_trace_subcall_transaction_to_builtin() { action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap()); let result = state.apply(&info, engine, &t, true).unwrap(); @@ -1060,7 +1082,7 @@ fn should_not_trace_callcode() { action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); @@ -1122,7 +1144,7 @@ fn should_not_trace_delegatecall() { action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); @@ -1181,10 +1203,10 @@ fn should_trace_failed_call_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1221,11 +1243,11 @@ fn should_trace_call_with_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1281,10 +1303,10 @@ fn should_trace_call_with_basic_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1336,10 +1358,10 @@ fn should_not_trace_call_with_invalid_basic_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds. - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1379,11 +1401,11 @@ fn should_trace_failed_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![],//600480600b6000396000f35b600056 - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1435,12 +1457,12 @@ fn should_trace_call_with_subcall_with_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap()); state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1510,12 +1532,12 @@ fn should_trace_failed_subcall_with_subcall_transaction() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![],//600480600b6000396000f35b600056 - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap()); state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap()); state.init_code(&0xc.into(), FromHex::from_hex("6000").unwrap()); - state.add_balance(t.sender().as_ref().unwrap(), &(100.into())); + state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { @@ -1583,11 +1605,11 @@ fn should_trace_suicide() { action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - }.sign(&"".sha3()); + }.sign(&"".sha3(), None); state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap()); - state.add_balance(&0xa.into(), &50.into()); - state.add_balance(t.sender().as_ref().unwrap(), &100.into()); + state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty); + state.add_balance(t.sender().as_ref().unwrap(), &100.into(), CleanupMode::NoEmpty); let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = vec![FlatTrace { trace_address: Default::default(), @@ -1658,7 +1680,7 @@ fn get_from_database() { let (root, db) = { let mut state = get_temp_state_in(temp.as_path()); state.inc_nonce(&a); - state.add_balance(&a, &U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); state.commit().unwrap(); assert_eq!(state.balance(&a), U256::from(69u64)); state.drop() @@ -1675,27 +1697,47 @@ fn remove() { let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); assert_eq!(state.exists(&a), false); + assert_eq!(state.exists_and_not_null(&a), false); state.inc_nonce(&a); assert_eq!(state.exists(&a), true); + assert_eq!(state.exists_and_not_null(&a), true); assert_eq!(state.nonce(&a), U256::from(1u64)); state.kill_account(&a); assert_eq!(state.exists(&a), false); + assert_eq!(state.exists_and_not_null(&a), false); assert_eq!(state.nonce(&a), U256::from(0u64)); } #[test] -fn empty_account_exists() { +fn empty_account_is_not_created() { let a = Address::zero(); let path = RandomTempPath::new(); let db = get_temp_state_db_in(path.as_path()); let (root, db) = { let mut state = State::new(db, U256::from(0), Default::default()); - state.add_balance(&a, &U256::default()); // create an empty account + state.add_balance(&a, &U256::default(), CleanupMode::NoEmpty); // create an empty account + state.commit().unwrap(); + state.drop() + }; + let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); + assert!(!state.exists(&a)); + assert!(!state.exists_and_not_null(&a)); +} + +#[test] +fn empty_account_exists_when_creation_forced() { + let a = Address::zero(); + let path = RandomTempPath::new(); + let db = get_temp_state_db_in(path.as_path()); + let (root, db) = { + let mut state = State::new(db, U256::from(0), Default::default()); + state.add_balance(&a, &U256::default(), CleanupMode::ForceCreate); // create an empty account state.commit().unwrap(); state.drop() }; let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap(); assert!(state.exists(&a)); + assert!(!state.exists_and_not_null(&a)); } #[test] @@ -1733,7 +1775,7 @@ fn alter_balance() { let mut state = state_result.reference_mut(); let a = Address::zero(); let b = 1u64.into(); - state.add_balance(&a, &U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); assert_eq!(state.balance(&a), U256::from(69u64)); state.commit().unwrap(); assert_eq!(state.balance(&a), U256::from(69u64)); @@ -1741,7 +1783,7 @@ fn alter_balance() { assert_eq!(state.balance(&a), U256::from(27u64)); state.commit().unwrap(); assert_eq!(state.balance(&a), U256::from(27u64)); - state.transfer_balance(&a, &b, &U256::from(18u64)); + state.transfer_balance(&a, &b, &U256::from(18u64), CleanupMode::NoEmpty); assert_eq!(state.balance(&a), U256::from(9u64)); assert_eq!(state.balance(&b), U256::from(18u64)); state.commit().unwrap(); @@ -1794,12 +1836,12 @@ fn checkpoint_basic() { let mut state = state_result.reference_mut(); let a = Address::zero(); state.checkpoint(); - state.add_balance(&a, &U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); assert_eq!(state.balance(&a), U256::from(69u64)); state.discard_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); state.checkpoint(); - state.add_balance(&a, &U256::from(1u64)); + state.add_balance(&a, &U256::from(1u64), CleanupMode::NoEmpty); assert_eq!(state.balance(&a), U256::from(70u64)); state.revert_to_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); @@ -1812,7 +1854,7 @@ fn checkpoint_nested() { let a = Address::zero(); state.checkpoint(); state.checkpoint(); - state.add_balance(&a, &U256::from(69u64)); + state.add_balance(&a, &U256::from(69u64), CleanupMode::NoEmpty); assert_eq!(state.balance(&a), U256::from(69u64)); state.discard_checkpoint(); assert_eq!(state.balance(&a), U256::from(69u64)); @@ -1835,7 +1877,7 @@ fn should_not_panic_on_state_diff_with_storage() { let a: Address = 0xa.into(); state.init_code(&a, b"abcdefg".to_vec()); - state.add_balance(&a, &256.into()); + state.add_balance(&a, &256.into(), CleanupMode::NoEmpty); state.set_storage(&a, 0xb.into(), 0xc.into()); let mut new_state = state.clone(); diff --git a/ethcore/src/state/substate.rs b/ethcore/src/state/substate.rs index de703f369..853b0e422 100644 --- a/ethcore/src/state/substate.rs +++ b/ethcore/src/state/substate.rs @@ -18,6 +18,8 @@ use std::collections::HashSet; use util::{Address, U256}; use log_entry::LogEntry; +use evm::Schedule; +use super::CleanupMode; /// State changes which should be applied in finalize, /// after transaction is fully executed. @@ -26,6 +28,9 @@ pub struct Substate { /// Any accounts that have suicided. pub suicides: HashSet
, + /// Any accounts that are tagged for garbage collection. + pub garbage: HashSet
, + /// Any logs. pub logs: Vec, @@ -45,10 +50,20 @@ impl Substate { /// Merge secondary substate `s` into self, accruing each element correspondingly. pub fn accrue(&mut self, s: Substate) { self.suicides.extend(s.suicides.into_iter()); + self.garbage.extend(s.garbage.into_iter()); self.logs.extend(s.logs.into_iter()); self.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count; self.contracts_created.extend(s.contracts_created.into_iter()); } + + /// Get the cleanup mode object from this. + pub fn to_cleanup_mode(&mut self, schedule: &Schedule) -> CleanupMode { + match (schedule.no_empty, schedule.kill_empty) { + (false, _) => CleanupMode::ForceCreate, + (true, false) => CleanupMode::NoEmpty, + (true, true) => CleanupMode::KillEmpty(&mut self.garbage), + } + } } #[cfg(test)] diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 3506b8951..3a3595a35 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -165,13 +165,13 @@ impl StateDB { bloom } - pub fn check_account_bloom(&self, address: &Address) -> bool { + pub fn check_non_null_bloom(&self, address: &Address) -> bool { trace!(target: "account_bloom", "Check account bloom: {:?}", address); let bloom = self.account_bloom.lock(); bloom.check(&*address.sha3()) } - pub fn note_account_bloom(&self, address: &Address) { + pub fn note_non_null_account(&self, address: &Address) { trace!(target: "account_bloom", "Note account bloom: {:?}", address); let mut bloom = self.account_bloom.lock(); bloom.set(&*address.sha3()); diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 3a24ccf21..99b251d66 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -16,6 +16,7 @@ use io::IoChannel; use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockID}; +use state::CleanupMode; use ethereum; use block::IsBlock; use tests::helpers::*; @@ -217,7 +218,7 @@ fn can_generate_gas_price_histogram() { let client = client_result.reference(); let hist = client.gas_price_histogram(20, 5).unwrap(); - let correct_hist = Histogram { bucket_bounds: vec_into![643,2293,3943,5593,7243,8893], counts: vec![4,2,4,6,3] }; + let correct_hist = Histogram { bucket_bounds: vec_into![643, 2294, 3945, 5596, 7247, 8898], counts: vec![4,2,4,6,4] }; assert_eq!(hist, correct_hist); } @@ -272,7 +273,7 @@ fn change_history_size() { let client = Client::new(ClientConfig::default(), &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap(); for _ in 0..20 { let mut b = client.prepare_open_block(Address::default(), (3141562.into(), 31415620.into()), vec![]); - b.block_mut().fields_mut().state.add_balance(&address, &5.into()); + b.block_mut().fields_mut().state.add_balance(&address, &5.into(), CleanupMode::NoEmpty); b.block_mut().fields_mut().state.commit().unwrap(); let b = b.close_and_lock().seal(&*test_spec.engine, vec![]).unwrap(); client.import_sealed_block(b).unwrap(); // account change is in the journal overlay diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index f19874341..89f4c9308 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -39,6 +39,7 @@ pub enum ChainEra { Frontier, Homestead, Eip150, + Eip161, TransitionTest, } @@ -193,7 +194,7 @@ pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_numbe action: Action::Create, data: vec![], value: U256::zero(), - }.sign(kp.secret()), None).unwrap(); + }.sign(kp.secret(), None), None).unwrap(); n += 1; } diff --git a/ethcore/src/types/receipt.rs b/ethcore/src/types/receipt.rs index 52e6747e8..deefeb383 100644 --- a/ethcore/src/types/receipt.rs +++ b/ethcore/src/types/receipt.rs @@ -93,6 +93,10 @@ pub struct RichReceipt { pub contract_address: Option
, /// Logs pub logs: Vec, + /// Logs bloom + pub log_bloom: LogBloom, + /// State root + pub state_root: H256, } /// Receipt with additional info. @@ -114,6 +118,10 @@ pub struct LocalizedReceipt { pub contract_address: Option
, /// Logs pub logs: Vec, + /// Logs bloom + pub log_bloom: LogBloom, + /// State root + pub state_root: H256, } #[test] diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 247a1b301..e26f98cfa 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -72,8 +72,8 @@ pub struct Transaction { impl Transaction { /// Append object with a without signature into RLP stream - pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream) { - s.begin_list(6); + pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, network_id: Option) { + s.begin_list(if let None = network_id { 6 } else { 9 }); s.append(&self.nonce); s.append(&self.gas_price); s.append(&self.gas); @@ -83,6 +83,11 @@ impl Transaction { }; s.append(&self.value); s.append(&self.data); + if let Some(n) = network_id { + s.append(&n); + s.append(&0u8); + s.append(&0u8); + } } } @@ -105,7 +110,7 @@ impl From for SignedTransaction { }, value: t.value.into(), data: t.data.into(), - }.sign(&t.secret.into()) + }.sign(&t.secret.into(), None) } } @@ -135,26 +140,26 @@ impl From for SignedTransaction { impl Transaction { /// The message hash of the transaction. - pub fn hash(&self) -> H256 { + pub fn hash(&self, network_id: Option) -> H256 { let mut stream = RlpStream::new(); - self.rlp_append_unsigned_transaction(&mut stream); + self.rlp_append_unsigned_transaction(&mut stream, network_id); stream.out().sha3() } /// Signs the transaction as coming from `sender`. - pub fn sign(self, secret: &Secret) -> SignedTransaction { - let sig = ::ethkey::sign(secret, &self.hash()) + pub fn sign(self, secret: &Secret, network_id: Option) -> SignedTransaction { + let sig = ::ethkey::sign(secret, &self.hash(network_id)) .expect("data is valid and context has signing capabilities; qed"); - self.with_signature(sig) + self.with_signature(sig, network_id) } /// Signs the transaction with signature. - pub fn with_signature(self, sig: Signature) -> SignedTransaction { + pub fn with_signature(self, sig: Signature, network_id: Option) -> SignedTransaction { SignedTransaction { unsigned: self, r: sig.r().into(), s: sig.s().into(), - v: sig.v() + 27, + v: sig.v() + if let Some(n) = network_id { 1 + n * 2 } else { 27 }, hash: Cell::new(None), sender: Cell::new(None), } @@ -204,7 +209,8 @@ impl Transaction { pub struct SignedTransaction { /// Plain Transaction. unsigned: Transaction, - /// The V field of the signature, either 27 or 28; helps describe the point on the curve. + /// The V field of the signature; the LS bit described which half of the curve our point falls + /// in. The MS bits describe which network this transaction is for. If 27/28, its for all networks. v: u8, /// The R field of the signature; helps describe the point on the curve. r: U256, @@ -266,7 +272,7 @@ impl HeapSizeOf for SignedTransaction { impl SignedTransaction { /// Append object with a signature into RLP stream - pub fn rlp_append_sealed_transaction(&self, s: &mut RlpStream) { + fn rlp_append_sealed_transaction(&self, s: &mut RlpStream) { s.begin_list(9); s.append(&self.nonce); s.append(&self.gas_price); @@ -295,8 +301,16 @@ impl SignedTransaction { } } - /// 0 is `v` is 27, 1 if 28, and 4 otherwise. - pub fn standard_v(&self) -> u8 { match self.v { 27 => 0, 28 => 1, _ => 4 } } + /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid. + pub fn standard_v(&self) -> u8 { match self.v { 0 => 4, v => (v - 1) & 1, } } + + /// The network ID, or `None` if this is a global transaction. + pub fn network_id(&self) -> Option { + match self.v { + 0 | 27 | 28 => None, + v => Some((v - 1) / 2), + } + } /// Construct a signature object from the sig. pub fn signature(&self) -> Signature { @@ -327,20 +341,25 @@ impl SignedTransaction { /// Returns the public key of the sender. pub fn public_key(&self) -> Result { - Ok(try!(recover(&self.signature(), &self.unsigned.hash()))) + Ok(try!(recover(&self.signature(), &self.unsigned.hash(self.network_id())))) } /// Do basic validation, checking for valid signature and minimum gas, // TODO: consider use in block validation. #[cfg(test)] #[cfg(feature = "json-tests")] - pub fn validate(self, schedule: &Schedule, require_low: bool) -> Result { + pub fn validate(self, schedule: &Schedule, require_low: bool, allow_network_id_of_one: bool) -> Result { if require_low && !self.signature().is_low_s() { return Err(EthkeyError::InvalidSignature.into()) } + match self.network_id() { + None => {}, + Some(1) if allow_network_id_of_one => {}, + _ => return Err(TransactionError::InvalidNetworkId.into()), + } try!(self.sender()); if self.gas < U256::from(self.gas_required(&schedule)) { - Err(From::from(TransactionError::InvalidGasLimit(::util::OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas}))) + Err(TransactionError::InvalidGasLimit(::util::OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas}).into()) } else { Ok(self) } @@ -380,6 +399,7 @@ fn sender_test() { } else { panic!(); } assert_eq!(t.value, U256::from(0x0au64)); assert_eq!(t.sender().unwrap(), "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".into()); + assert_eq!(t.network_id(), None); } #[test] @@ -394,8 +414,9 @@ fn signing() { gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec() - }.sign(&key.secret()); + }.sign(&key.secret(), None); assert_eq!(Address::from(key.public().sha3()), t.sender().unwrap()); + assert_eq!(t.network_id(), None); } #[test] @@ -409,7 +430,47 @@ fn fake_signing() { data: b"Hello!".to_vec() }.fake_sign(Address::from(0x69)); assert_eq!(Address::from(0x69), t.sender().unwrap()); + assert_eq!(t.network_id(), None); let t = t.clone(); assert_eq!(Address::from(0x69), t.sender().unwrap()); + assert_eq!(t.network_id(), None); } + +#[test] +fn should_recover_from_network_specific_signing() { + use ethkey::{Random, Generator}; + let key = Random.generate().unwrap(); + let t = Transaction { + action: Action::Create, + nonce: U256::from(42), + gas_price: U256::from(3000), + gas: U256::from(50_000), + value: U256::from(1), + data: b"Hello!".to_vec() + }.sign(&key.secret(), Some(69)); + assert_eq!(Address::from(key.public().sha3()), t.sender().unwrap()); + assert_eq!(t.network_id(), Some(69)); +} + +#[test] +fn should_agree_with_vitalik() { + use rustc_serialize::hex::FromHex; + use std::str::FromStr; + + let test_vector = |tx_data: &str, address: &'static str| { + let signed: SignedTransaction = decode(&FromHex::from_hex(tx_data).unwrap()); + assert_eq!(signed.sender().unwrap(), address.into()); + }; + + test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xda39a520355857fdb37ecb527fe814230fa9962c"); + test_vector("f864018504a817c80182a410943535353535353535353535353535353535353535018025a0c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc8a0c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0xd240215f30eafee2aaa5184d8f051ebb41c90b19"); + test_vector("f864028504a817c80282f618943535353535353535353535353535353535353535088025a0ad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a8a0ad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2f2822c55d894df1ba32961c43325dcb3d614ee8"); + test_vector("f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0xc21df0434ceab6e18a1300d18206e54e807b4456"); + test_vector("f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf975cee81edae2ab5883f4e2fb2a7f2fd56f4131"); + test_vector("f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a0ceebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a0ceebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xd477474c9f48dcbfde5d97f30646242ab7a17e06"); + test_vector("f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a0e455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2da0e455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xb49948deb719ca21e38d29e3360f534b39db0e76"); + test_vector("f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xbddfc81a8ce87b2360837049a6eda68ab2f58999"); + test_vector("f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a0e4b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c11a0e4b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x8f2edcf67f329a146dd4cb1e6b3a072daff85b38"); + test_vector("f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a0d2f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba0d2f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x4ec38d4782fd4a6ff85c1cde77ccf1ae3c54472c"); +} \ No newline at end of file diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 1b8eddfe8..bb9f042ae 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -395,7 +395,7 @@ mod tests { gas: U256::from(30_000), gas_price: U256::from(40_000), nonce: U256::one() - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let tr2 = Transaction { action: Action::Create, @@ -404,7 +404,7 @@ mod tests { gas: U256::from(30_000), gas_price: U256::from(40_000), nonce: U256::from(2) - }.sign(keypair.secret()); + }.sign(keypair.secret(), None); let good_transactions = [ tr1.clone(), tr2.clone() ]; diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs index 11fb3a876..cac89d76c 100644 --- a/evmbin/src/ext.rs +++ b/evmbin/src/ext.rs @@ -51,6 +51,14 @@ impl Ext for FakeExt { unimplemented!(); } + fn exists_and_not_null(&self, address: &Address) -> bool { + unimplemented!(); + } + + fn origin_balance(&self) -> U256 { + unimplemented!(); + } + fn balance(&self, _address: &Address) -> U256 { unimplemented!(); } diff --git a/js/build-server.js b/js/build-server.js index 9153f5ed2..f8c0eb828 100644 --- a/js/build-server.js +++ b/js/build-server.js @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +// test only /** * Run `PARITY_URL="127.0.0.1:8180" NODE_ENV="production" npm run build` diff --git a/js/package.json b/js/package.json index 367434260..4f441f328 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.1.47", + "version": "0.1.61", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 22fd91ab6..0fab71e2c 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -50,32 +50,33 @@ setup_git_user git remote set-url origin $GIT_PARITY git reset --hard origin/$BRANCH 2>$GITLOG -echo "*** Bumping package.json patch version" -cd js -npm --no-git-tag-version version -npm version patch -cd .. +if [ "$BRANCH" == "master" ]; then + cd js + echo "*** Bumping package.json patch version" + npm --no-git-tag-version version + npm version patch + + echo "*** Building packages for npmjs" + cd js + # echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login + echo "$NPM_TOKEN" >> ~/.npmrc + npm run ci:build:npm + + echo "*** Publishing $PACKAGE to npmjs" + cd .npmjs + npm publish --access public + cd .. +fi echo "*** Updating cargo parity-ui-precompiled#$PRECOMPILED_HASH" cargo update -p parity-ui-precompiled # --precise "$PRECOMPILED_HASH" echo "*** Committing updated files" -git add Cargo.lock js/package.json +git add . git commit -m "[ci skip] js-precompiled $UTCDATE" git push origin HEAD:refs/heads/$BRANCH 2>$GITLOG -echo "*** Building packages for npmjs" -cd js -# echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login -echo "$NPM_TOKEN" >> ~/.npmrc -npm run ci:build:npm - -echo "*** Publishing $PACKAGE to npmjs" -cd .npmjs -npm publish --access public -cd .. - # back to root echo "*** Release completed" popd diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index aff453517..658f54197 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -87,7 +87,8 @@ export default class Status { setTimeout(this._pollStatus, timeout); }; - if (isConnected !== this._store.getState().nodeStatus.isConnected) { + const wasConnected = this._store.getState().nodeStatus.isConnected; + if (isConnected !== wasConnected) { this._fetchEnode(); } diff --git a/js/src/ui/CopyToClipboard/copyToClipboard.js b/js/src/ui/CopyToClipboard/copyToClipboard.js new file mode 100644 index 000000000..3351f2e40 --- /dev/null +++ b/js/src/ui/CopyToClipboard/copyToClipboard.js @@ -0,0 +1,75 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import { IconButton } from 'material-ui'; +import Clipboard from 'react-copy-to-clipboard'; +import CopyIcon from 'material-ui/svg-icons/content/content-copy'; +import Theme from '../Theme'; +const { textColor, disabledTextColor } = Theme.flatButton; + +export default class CopyToClipboard extends Component { + static propTypes = { + data: PropTypes.string.isRequired, + label: PropTypes.string, + onCopy: PropTypes.func, + size: PropTypes.number, // in px + cooldown: PropTypes.number // in ms + }; + + static defaultProps = { + className: '', + label: 'copy to clipboard', + onCopy: () => {}, + size: 16, + cooldown: 1000 + }; + + state = { + copied: false + }; + + render () { + const { data, label, size } = this.props; + const { copied } = this.state; + + return ( + + + + + + ); + } + + onCopy = () => { + const { cooldown, onCopy } = this.props; + + this.setState({ copied: true }); + setTimeout(() => { + this.setState({ copied: false }); + }, cooldown); + + onCopy(); + } +} diff --git a/js/src/ui/CopyToClipboard/index.js b/js/src/ui/CopyToClipboard/index.js new file mode 100644 index 000000000..6391b478b --- /dev/null +++ b/js/src/ui/CopyToClipboard/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './copyToClipboard'; diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index d35f12d5b..648ff271b 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -19,12 +19,11 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import Contracts from '../../contracts'; import IdentityIcon from '../IdentityIcon'; import IdentityName from '../IdentityName'; import { Input, InputAddress } from '../Form'; -import { fetchBytecode, fetchMethod } from '../../redux/providers/blockchainActions'; - import styles from './methodDecoding.css'; const CONTRACT_CREATE = '0x60606040'; @@ -41,12 +40,7 @@ class MethodDecoding extends Component { address: PropTypes.string.isRequired, tokens: PropTypes.object, transaction: PropTypes.object, - historic: PropTypes.bool, - - fetchBytecode: PropTypes.func, - fetchMethod: PropTypes.func, - bytecodes: PropTypes.object, - methods: PropTypes.object + historic: PropTypes.bool } state = { @@ -63,58 +57,7 @@ class MethodDecoding extends Component { } componentWillMount () { - const { transaction } = this.props; - this.lookup(transaction); - } - - componentDidMount () { - this.setMethod(this.props); - } - - componentWillReceiveProps (newProps) { - const { transaction } = this.props; - this.setMethod(newProps); - - if (newProps.transaction.hash !== transaction.hash) { - this.lookup(transaction); - return; - } - } - - setMethod (props) { - const { bytecodes, methods } = props; - const { contractAddress, methodSignature, methodParams } = this.state; - - if (contractAddress && bytecodes[contractAddress]) { - const bytecode = bytecodes[contractAddress]; - - if (bytecode && bytecode !== '0x') { - this.setState({ isContract: true }); - } - } - - if (methodSignature && methods[methodSignature]) { - const method = methods[methodSignature]; - const { api } = this.context; - - let methodInputs = null; - let methodName = null; - - if (method && method.length) { - const abi = api.util.methodToAbi(method); - - methodName = abi.name; - methodInputs = api.util - .decodeMethodInput(abi, methodParams) - .map((value, index) => { - const type = abi.inputs[index].type; - - return { type, value }; - }); - } - - this.setState({ method, methodName, methodInputs }); - } + this.lookup(); } render () { @@ -321,7 +264,9 @@ class MethodDecoding extends Component { ); } - lookup (transaction) { + lookup () { + const { transaction } = this.props; + if (!transaction) { return; } @@ -347,26 +292,51 @@ class MethodDecoding extends Component { return; } - const { fetchBytecode, fetchMethod } = this.props; + Promise + .all([ + api.eth.getCode(contractAddress), + Contracts.get().signatureReg.lookup(signature) + ]) + .then(([bytecode, method]) => { + let methodInputs = null; + let methodName = null; - fetchBytecode(contractAddress); - fetchMethod(signature); + if (method && method.length) { + const { methodParams } = this.state; + const abi = api.util.methodToAbi(method); + + methodName = abi.name; + methodInputs = api.util + .decodeMethodInput(abi, methodParams) + .map((value, index) => { + const type = abi.inputs[index].type; + + return { type, value }; + }); + } + + this.setState({ + method, + methodName, + methodInputs, + bytecode, + isContract: bytecode && bytecode !== '0x' + }); + }) + .catch((error) => { + console.warn('lookup', error); + }); } } function mapStateToProps (state) { const { tokens } = state.balances; - const { bytecodes, methods } = state.blockchain; - return { - tokens, bytecodes, methods - }; + return { tokens }; } function mapDispatchToProps (dispatch) { - return bindActionCreators({ - fetchBytecode, fetchMethod - }, dispatch); + return bindActionCreators({}, dispatch); } export default connect( diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 808dec64a..c2b63097a 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -25,6 +25,7 @@ import Button from './Button'; import ConfirmDialog from './ConfirmDialog'; import Container, { Title as ContainerTitle } from './Container'; import ContextProvider from './ContextProvider'; +import CopyToClipboard from './CopyToClipboard'; import Errors from './Errors'; import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form'; import IdentityIcon from './IdentityIcon'; @@ -53,6 +54,7 @@ export { Container, ContainerTitle, ContextProvider, + CopyToClipboard, Errors, Form, FormWrap, diff --git a/js/src/views/Account/Transactions/Transaction/transaction.js b/js/src/views/Account/Transactions/Transaction/transaction.js index 3eed8bbfb..d84917d6b 100644 --- a/js/src/views/Account/Transactions/Transaction/transaction.js +++ b/js/src/views/Account/Transactions/Transaction/transaction.js @@ -17,46 +17,37 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import moment from 'moment'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; - -import { fetchBlock, fetchTransaction } from '../../../../redux/providers/blockchainActions'; import { IdentityIcon, IdentityName, MethodDecoding } from '../../../../ui'; import { txLink, addressLink } from '../../../../3rdparty/etherscan/links'; import styles from '../transactions.css'; -class Transaction extends Component { +export default class Transaction extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { - transaction: PropTypes.object.isRequired, address: PropTypes.string.isRequired, isTest: PropTypes.bool.isRequired, - - fetchBlock: PropTypes.func.isRequired, - fetchTransaction: PropTypes.func.isRequired, - - block: PropTypes.object, - transactionInfo: PropTypes.object + transaction: PropTypes.object.isRequired } state = { isContract: false, - isReceived: false + isReceived: false, + transaction: null, + block: null } componentDidMount () { - const { address, transaction } = this.props; - - this.lookup(address, transaction); + this.lookup(); } render () { - const { block, transaction } = this.props; + const { block } = this.state; + const { transaction } = this.props; return ( @@ -75,9 +66,10 @@ class Transaction extends Component { } renderMethod () { - const { address, transactionInfo } = this.props; + const { address } = this.props; + const { transaction } = this.state; - if (!transactionInfo) { + if (!transaction) { return null; } @@ -85,12 +77,13 @@ class Transaction extends Component { + transaction={ transaction } /> ); } renderTransaction () { - const { transaction, isTest } = this.props; + const { isTest } = this.props; + const { transaction } = this.props; return ( @@ -138,13 +131,13 @@ class Transaction extends Component { renderEtherValue () { const { api } = this.context; - const { transactionInfo } = this.props; + const { transaction } = this.state; - if (!transactionInfo) { + if (!transaction) { return null; } - const value = api.util.fromWei(transactionInfo.value); + const value = api.util.fromWei(transaction.value); if (value.eq(0)) { return
{ ' ' }
; @@ -177,34 +170,22 @@ class Transaction extends Component { return moment(block.timestamp).fromNow(); } - lookup (address, transaction) { - const { transactionInfo } = this.props; - - if (transactionInfo) { - return; - } + lookup () { + const { api } = this.context; + const { transaction, address } = this.props; this.setState({ isReceived: address === transaction.to }); - const { fetchBlock, fetchTransaction } = this.props; - const { blockNumber, hash } = transaction; - - fetchBlock(blockNumber); - fetchTransaction(hash); + Promise + .all([ + api.eth.getBlockByNumber(transaction.blockNumber), + api.eth.getTransactionByHash(transaction.hash) + ]) + .then(([block, transaction]) => { + this.setState({ block, transaction }); + }) + .catch((error) => { + console.warn('lookup', error); + }); } } - -function mapStateToProps () { - return {}; -} - -function mapDispatchToProps (dispatch) { - return bindActionCreators({ - fetchBlock, fetchTransaction - }, dispatch); -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(Transaction); diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index 77bdf60a3..fcbb2ce41 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -38,9 +38,7 @@ class Transactions extends Component { contracts: PropTypes.object, tokens: PropTypes.object, isTest: PropTypes.bool, - traceMode: PropTypes.bool, - blocks: PropTypes.object, - transactionsInfo: PropTypes.object + traceMode: PropTypes.bool } state = { @@ -121,7 +119,7 @@ class Transactions extends Component { } renderRows () { - const { address, accounts, contacts, contracts, tokens, isTest, blocks, transactionsInfo } = this.props; + const { address, accounts, contacts, contracts, tokens, isTest } = this.props; const { transactions } = this.state; return (transactions || []) @@ -130,16 +128,9 @@ class Transactions extends Component { }) .slice(0, 25) .map((transaction, index) => { - const { blockNumber, hash } = transaction; - - const block = blocks[blockNumber.toString()]; - const transactionInfo = transactionsInfo[hash]; - return ( { - if (traceMode) { - return this.fetchTraceTransactions(address); - } + // if (traceMode) { + // return this.fetchTraceTransactions(address); + // } return this.fetchEtherscanTransactions(isTest, address); } @@ -211,7 +202,6 @@ function mapStateToProps (state) { const { isTest, traceMode } = state.nodeStatus; const { accounts, contacts, contracts } = state.personal; const { tokens } = state.balances; - const { blocks, transactions } = state.blockchain; return { isTest, @@ -219,9 +209,7 @@ function mapStateToProps (state) { accounts, contacts, contracts, - tokens, - blocks, - transactionsInfo: transactions + tokens }; } diff --git a/js/src/views/Application/Status/status.css b/js/src/views/Application/Status/status.css index 3ce33f51b..de043a1ad 100644 --- a/js/src/views/Application/Status/status.css +++ b/js/src/views/Application/Status/status.css @@ -15,12 +15,10 @@ /* along with Parity. If not, see . */ .status { - padding: 1.5em; - text-align: right; - color: #ddd; - display: flex; - flex-direction: column; - align-items: flex-end; + padding: 0.5em; + font-size: x-small; + color: #ccc; + background-color: rgba(0, 0, 0, 0.2) } .title { @@ -28,9 +26,14 @@ } .enode { - width: 45em; word-wrap: break-word; - margin: 0.5em 0 0.25em; + float: right; +} + +.enode > * { + display: inline-block; + margin: 0.25em 0.5em; + vertical-align: top; } .block { @@ -39,9 +42,7 @@ .netinfo { display: flex; align-items: center; - flex-direction: row; - justify-content: flex-end; - margin-top: 0.25em; + color: #ddd; } .netinfo > * { @@ -69,6 +70,8 @@ } .version { + padding: 0.25em 0.5em; + float: left; } .syncing { diff --git a/js/src/views/Application/Status/status.js b/js/src/views/Application/Status/status.js index f86d5e4ff..6417d5d28 100644 --- a/js/src/views/Application/Status/status.js +++ b/js/src/views/Application/Status/status.js @@ -19,6 +19,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { BlockStatus } from '../../../ui'; +import CopyToClipboard from '../../../ui/CopyToClipboard'; import styles from './status.css'; @@ -47,15 +48,13 @@ class Status extends Component { { this.renderEnode() }
-
- -
- { netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers -
-
+
{ isTest ? 'test' : netChain }
+
+ { netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers +
); @@ -68,9 +67,14 @@ class Status extends Component { return null; } + const [protocol, rest] = enode.split('://'); + const [id, host] = rest.split('@'); + const abbreviated = `${protocol}://${id.slice(0, 3)}…${id.slice(-3)}@${host}`; + return (
- { enode } + +
{ abbreviated }
); } diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 057ffd169..f90bc4438 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -149,15 +149,15 @@ class TabBar extends Component { } renderStatusLabel = (label) => { - const { isTest, netChain } = this.props; - const bubble = ( - - ); + // const { isTest, netChain } = this.props; + // const bubble = ( + // + // ); - return this.renderLabel(label, bubble); + return this.renderLabel(label, null); } onActivate = (activeRoute) => { diff --git a/js/src/views/Contract/Events/Event/event.js b/js/src/views/Contract/Events/Event/event.js index ba24c1c8f..f45975f94 100644 --- a/js/src/views/Contract/Events/Event/event.js +++ b/js/src/views/Contract/Events/Event/event.js @@ -17,27 +17,24 @@ import BigNumber from 'bignumber.js'; import moment from 'moment'; import React, { Component, PropTypes } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { fetchBlock, fetchTransaction } from '../../../../redux/providers/blockchainActions'; import { IdentityIcon, IdentityName, Input, InputAddress } from '../../../../ui'; import { txLink } from '../../../../3rdparty/etherscan/links'; import styles from '../../contract.css'; -class Event extends Component { +export default class Event extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { event: PropTypes.object.isRequired, - blocks: PropTypes.object, - transactions: PropTypes.object, - isTest: PropTypes.bool, - fetchBlock: PropTypes.func.isRequired, - fetchTransaction: PropTypes.func.isRequired + isTest: PropTypes.bool + } + + state = { + transaction: {} } componentDidMount () { @@ -45,10 +42,9 @@ class Event extends Component { } render () { - const { event, blocks, transactions, isTest } = this.props; + const { event, isTest } = this.props; + const { block, transaction } = this.state; - const block = blocks[event.blockNumber.toString()]; - const transaction = transactions[event.transactionHash] || {}; const classes = `${styles.event} ${styles[event.state]}`; const url = txLink(event.transactionHash, isTest); const keys = Object.keys(event.params).join(', '); @@ -156,31 +152,19 @@ class Event extends Component { } retrieveTransaction () { - const { event, fetchBlock, fetchTransaction } = this.props; + const { api } = this.context; + const { event } = this.props; - fetchBlock(event.blockNumber); - fetchTransaction(event.transactionHash); + Promise + .all([ + api.eth.getBlockByNumber(event.blockNumber), + api.eth.getTransactionByHash(event.transactionHash) + ]) + .then(([block, transaction]) => { + this.setState({ block, transaction }); + }) + .catch((error) => { + console.warn('retrieveTransaction', error); + }); } } - -function mapStateToProps (state) { - const { isTest } = state.nodeStatus; - const { blocks, transactions } = state.blockchain; - - return { - isTest, - blocks, - transactions - }; -} - -function mapDispatchToProps (dispatch) { - return bindActionCreators({ - fetchBlock, fetchTransaction - }, dispatch); -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(Event); diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index 0428a0ee3..382a1c658 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -27,21 +27,31 @@ export default class Events extends Component { } static propTypes = { - events: PropTypes.array + events: PropTypes.array, + isTest: PropTypes.bool.isRequired } render () { - const { events } = this.props; + const { events, isTest } = this.props; if (!events || !events.length) { return null; } + const list = events.map((event) => { + return ( + + ); + }); + return ( - { events.map((event) => ) } + { list }
); diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index 4e9e0a9f0..5a65a69b5 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -116,6 +116,7 @@ class Contract extends Component { contract={ contract } values={ queryValues } /> diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs index 025ae8475..f8412bb97 100644 --- a/json/src/spec/ethash.rs +++ b/json/src/spec/ethash.rs @@ -70,6 +70,21 @@ pub struct EthashParams { /// See main EthashParams docs. #[serde(rename="eip150Transition")] pub eip150_transition: Option, + + /// See main EthashParams docs. + #[serde(rename="eip155Transition")] + pub eip155_transition: Option, + + /// See main EthashParams docs. + #[serde(rename="eip160Transition")] + pub eip160_transition: Option, + + /// See main EthashParams docs. + #[serde(rename="eip161abcTransition")] + pub eip161abc_transition: Option, + /// See main EthashParams docs. + #[serde(rename="eip161dTransition")] + pub eip161d_transition: Option, } /// Ethash engine deserialization. @@ -122,7 +137,11 @@ mod tests { "difficultyHardforkTransition": "0x59d9", "difficultyHardforkBoundDivisor": "0x0200", "bombDefuseTransition": "0x42", - "eip150Transition": "0x42" + "eip150Transition": "0x42", + "eip155Transition": "0x42", + "eip160Transition": "0x42", + "eip161abcTransition": "0x42", + "eip161dTransition": "0x42" } }"#; diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index ba81fb48a..aa634221d 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -28,15 +28,17 @@ pub struct Params { /// Maximum size of extra data. #[serde(rename="maximumExtraDataSize")] pub maximum_extra_data_size: Uint, + /// Minimum gas limit. + #[serde(rename="minGasLimit")] + pub min_gas_limit: Uint, + /// Network id. #[serde(rename="networkID")] pub network_id: Uint, /// Name of the main ("eth") subprotocol. #[serde(rename="subprotocolName")] pub subprotocol_name: Option, - /// Minimum gas limit. - #[serde(rename="minGasLimit")] - pub min_gas_limit: Uint, + /// Option fork block number to check. #[serde(rename="forkBlock")] pub fork_block: Option, diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 448618bbf..5f7c98f60 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -98,7 +98,8 @@ section "install" # Start Menu createDirectory "$SMPROGRAMS\${COMPANYNAME}" - createShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" "$INSTDIR\parity.exe" "ui" "$INSTDIR\logo.ico" + createShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME} Ethereum.lnk" "$INSTDIR\ptray.exe" "ui" "$INSTDIR\logo.ico" + createShortCut "$DESKTOP\${APPNAME} Ethereum.lnk" "$INSTDIR\ptray.exe" "ui" "$INSTDIR\logo.ico" # Firewall remove rules if exists SimpleFC::AdvRemoveRule "Parity incoming peers (TCP:30303)" diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 84e44ee77..088c9d4b7 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -25,7 +25,7 @@ port = 30303 min_peers = 25 max_peers = 50 nat = "any" -id = "0x1" +id = 1 bootnodes = [] discovery = true warp = true @@ -47,7 +47,7 @@ hosts = ["none"] [ipc] disable = false path = "$HOME/.parity/jsonrpc.ipc" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal_safe"] +apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal", "personal_safe"] [dapps] disable = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index d13d791ad..c0e7241b0 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -46,7 +46,7 @@ usage! { flag_testnet: bool, flag_import_geth_keys: bool, flag_datadir: Option, - flag_networkid: Option, + flag_networkid: Option, flag_peers: Option, flag_nodekey: Option, flag_nodiscover: bool, @@ -122,7 +122,7 @@ usage! { or |c: &Config| otry!(c.network).nat.clone(), flag_allow_ips: String = "all", or |c: &Config| otry!(c.network).allow_ips.clone(), - flag_network_id: Option = None, + flag_network_id: Option = None, or |c: &Config| otry!(c.network).id.clone().map(Some), flag_bootnodes: Option = None, or |c: &Config| otry!(c.network).bootnodes.clone().map(|vec| Some(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,personal_safe", + flag_ipc_apis: String = "web3,eth,net,ethcore,traces,rpc,personal,personal_safe", or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), // DAPPS @@ -323,7 +323,7 @@ struct Network { max_pending_peers: Option, nat: Option, allow_ips: Option, - id: Option, + id: Option, bootnodes: Option>, discovery: Option, node_key: Option, @@ -530,7 +530,7 @@ mod tests { flag_snapshot_peers: 0u16, flag_allow_ips: "all".into(), flag_nat: "any".into(), - flag_network_id: Some("0x1".into()), + flag_network_id: Some(1), flag_bootnodes: Some("".into()), flag_no_discovery: false, flag_node_key: None, @@ -549,7 +549,7 @@ mod tests { // IPC flag_no_ipc: false, flag_ipc_path: "$HOME/.parity/jsonrpc.ipc".into(), - flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc,personal_safe".into(), + flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc,personal,personal_safe".into(), // DAPPS flag_no_dapps: false, diff --git a/parity/cli/version.txt b/parity/cli/version.txt index acb7cd9e6..259efe941 100644 --- a/parity/cli/version.txt +++ b/parity/cli/version.txt @@ -5,5 +5,6 @@ License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. -By Wood/Paronyan/Kotewicz/Drwięga/Volf. +By Wood/Paronyan/Kotewicz/Drwięga/Volf + Habermeier/Czaban/Greeff/Gotchac/Redmann diff --git a/parity/configuration.rs b/parity/configuration.rs index f98225e8d..3484f8a4b 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -86,7 +86,7 @@ impl Configuration { let http_conf = try!(self.http_config()); let ipc_conf = try!(self.ipc_config()); let net_conf = try!(self.net_config()); - let network_id = try!(self.network_id()); + let network_id = self.network_id(); let cache_config = self.cache_config(); let spec = try!(self.chain().parse()); let tracing = try!(self.args.flag_tracing.parse()); @@ -517,12 +517,8 @@ impl Configuration { Ok(ret) } - fn network_id(&self) -> Result, String> { - let net_id = self.args.flag_network_id.as_ref().or(self.args.flag_networkid.as_ref()); - match net_id { - Some(id) => Ok(Some(try!(to_u256(id)))), - None => Ok(None), - } + fn network_id(&self) -> Option { + self.args.flag_network_id.or(self.args.flag_networkid) } fn rpc_apis(&self) -> String { diff --git a/parity/rpc.rs b/parity/rpc.rs index c5a4df18a..59279eaea 100644 --- a/parity/rpc.rs +++ b/parity/rpc.rs @@ -63,7 +63,7 @@ impl Default for IpcConfiguration { IpcConfiguration { enabled: true, socket_addr: parity_ipc_path("$HOME/.parity/jsonrpc.ipc"), - apis: ApiSet::UnsafeContext, + apis: ApiSet::IpcContext, } } } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 1aae27dd2..d725b9b08 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -69,6 +69,7 @@ impl FromStr for Api { pub enum ApiSet { SafeContext, UnsafeContext, + IpcContext, List(HashSet), } @@ -139,6 +140,10 @@ impl ApiSet { vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] .into_iter().collect() }, + ApiSet::IpcContext => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalAccounts, Api::PersonalSafe] + .into_iter().collect() + }, ApiSet::SafeContext => { vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] .into_iter().collect() diff --git a/parity/run.rs b/parity/run.rs index de73ecb45..9c3b00737 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -20,7 +20,7 @@ use ctrlc::CtrlC; use fdlimit::raise_fd_limit; use ethcore_rpc::{NetworkSettings, is_major_importing}; use ethsync::NetworkConfiguration; -use util::{Colour, version, U256, RotatingLogger}; +use util::{Colour, version, RotatingLogger}; use io::{MayPanic, ForwardPanic, PanicHandler}; use ethcore_logger::{Config as LogConfig}; use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, ChainNotify, BlockChainClient}; @@ -70,7 +70,7 @@ pub struct RunCmd { pub http_conf: HttpConfiguration, pub ipc_conf: IpcConfiguration, pub net_conf: NetworkConfiguration, - pub network_id: Option, + pub network_id: Option, pub warp_sync: bool, pub acc_conf: AccountsConfig, pub gas_pricer: GasPricerConfig, diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 1944d9035..457f96552 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -63,15 +63,16 @@ pub fn decrypt(accounts: &AccountProvider, address: Address, password: Option(client: &C, miner: &M, accounts: &AccountProvider, request: TransactionRequest, password: Option) -> Result where C: MiningBlockChainClient, M: MinerService { + let network_id = client.signing_network_id(); let address = request.from; let signed_transaction = { let t = prepare_transaction(client, miner, request); - let hash = t.hash(); + let hash = t.hash(network_id); let signature = try!(signature(accounts, address, password, hash)); - t.with_signature(signature) + t.with_signature(signature, network_id) }; - trace!(target: "miner", "send_transaction: dispatching tx: {}", ::rlp::encode(&signed_transaction).to_vec().pretty()); + trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", ::rlp::encode(&signed_transaction).to_vec().pretty(), network_id); dispatch_transaction(&*client, &*miner, signed_transaction) } diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index fad044866..8cbf26b7c 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -255,6 +255,7 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { SenderBanned => "Sender is banned in local queue.".into(), RecipientBanned => "Recipient is banned in local queue.".into(), CodeBanned => "Code is banned in local queue.".into(), + e => format!("{}", e).into(), }; Error { code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index c4341622e..a458a0570 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -45,7 +45,7 @@ fn account_provider() -> Arc { fn sync_provider() -> Arc { Arc::new(TestSyncProvider::new(Config { - network_id: U256::from(3), + network_id: 3, num_peers: 120, })) } diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index dcabba214..af158d564 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -209,6 +209,8 @@ impl MinerService for TestMinerService { gas_used: r.gas_used.clone(), contract_address: None, logs: r.logs.clone(), + log_bloom: r.log_bloom, + state_root: r.state_root, } ) } diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index 531b9bd2d..e0f811fc0 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -16,13 +16,13 @@ //! Test implementation of SyncProvider. -use util::{RwLock, U256}; +use util::{RwLock}; use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo}; /// TestSyncProvider config. pub struct Config { /// Protocol version. - pub network_id: U256, + pub network_id: usize, /// Number of peers. pub num_peers: usize, } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 24aa5e480..f69e73b62 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -43,7 +43,7 @@ fn accounts_provider() -> Arc { fn sync_provider() -> Arc { Arc::new(TestSyncProvider::new(Config { - network_id: U256::from(3), + network_id: 3, num_peers: 120, })) } @@ -485,7 +485,7 @@ fn rpc_eth_pending_transaction_by_hash() { tester.miner.pending_transactions.lock().insert(H256::zero(), tx); } - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0xa"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":0,"value":"0xa"},"id":1}"#; let request = r#"{ "jsonrpc": "2.0", "method": "eth_getTransactionByHash", @@ -737,8 +737,8 @@ fn rpc_eth_send_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -754,8 +754,8 @@ fn rpc_eth_send_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -819,8 +819,8 @@ fn rpc_eth_send_raw_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts_provider.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); let rlp = ::rlp::encode(&t).to_vec().to_hex(); @@ -862,7 +862,9 @@ fn rpc_eth_transaction_receipt() { transaction_hash: H256::new(), transaction_index: 0, log_index: 1, - }] + }], + log_bloom: 0.into(), + state_root: 0.into(), }; let hash = H256::from_str("b903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").unwrap(); @@ -875,7 +877,7 @@ fn rpc_eth_transaction_receipt() { "params": ["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"], "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","contractAddress":null,"cumulativeGasUsed":"0x20","gasUsed":"0x10","logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","data":"0x","logIndex":"0x1","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","contractAddress":null,"cumulativeGasUsed":"0x20","gasUsed":"0x10","logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","data":"0x","logIndex":"0x1","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","root":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0"},"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 58b5e0546..0f0125f87 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -246,8 +246,8 @@ fn should_dispatch_transaction_if_account_is_unlock() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts.sign(acc, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts.sign(acc, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); // when let request = r#"{ diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index e33f1a8f7..a64f133bf 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, Address}; +use util::{Address}; use ethsync::ManageNetwork; use ethcore::client::{TestBlockChainClient}; use ethstore::ethkey::{Generator, Random}; @@ -46,7 +46,7 @@ impl Dependencies { miner: Arc::new(TestMinerService::default()), client: Arc::new(TestBlockChainClient::default()), sync: Arc::new(TestSyncProvider::new(Config { - network_id: U256::from(3), + network_id: 3, num_peers: 120, })), logger: Arc::new(RotatingLogger::new("rpc=trace".to_owned())), diff --git a/rpc/src/v1/tests/mocked/net.rs b/rpc/src/v1/tests/mocked/net.rs index 3bc341dcb..0a5eb43e7 100644 --- a/rpc/src/v1/tests/mocked/net.rs +++ b/rpc/src/v1/tests/mocked/net.rs @@ -21,7 +21,7 @@ use v1::tests::helpers::{Config, TestSyncProvider}; fn sync_provider() -> Arc { Arc::new(TestSyncProvider::new(Config { - network_id: 3.into(), + network_id: 3, num_peers: 120, })) } diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 3bf4a9fa8..511f23415 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -201,8 +201,8 @@ fn sign_and_send_transaction() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); - let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -219,8 +219,8 @@ fn sign_and_send_transaction() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); - let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index 7b3e18b11..ffcc47432 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -186,8 +186,8 @@ fn should_confirm_transaction_and_dispatch() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); - let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); - let t = t.with_signature(signature); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); assert_eq!(tester.signer.requests().len(), 1); diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 70f39ba73..53d8c3583 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -103,7 +103,7 @@ mod tests { fn test_serialize_block_transactions() { let t = BlockTransactions::Full(vec![Transaction::default()]); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null}]"#); + assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"v":0,"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000"}]"#); let t = BlockTransactions::Hashes(vec![H256::default().into()]); let serialized = serde_json::to_string(&t).unwrap(); diff --git a/rpc/src/v1/types/receipt.rs b/rpc/src/v1/types/receipt.rs index 066195cbf..24170a14c 100644 --- a/rpc/src/v1/types/receipt.rs +++ b/rpc/src/v1/types/receipt.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use v1::types::{Log, H160, H256, U256}; +use v1::types::{Log, H160, H256, H2048, U256}; use ethcore::receipt::{Receipt as EthReceipt, RichReceipt, LocalizedReceipt}; /// Receipt @@ -43,6 +43,12 @@ pub struct Receipt { pub contract_address: Option, /// Logs pub logs: Vec, + /// State Root + #[serde(rename="root")] + pub state_root: H256, + /// Logs bloom + #[serde(rename="logsBloom")] + pub logs_bloom: H2048, } impl From for Receipt { @@ -56,6 +62,8 @@ impl From for Receipt { gas_used: Some(r.gas_used.into()), contract_address: r.contract_address.map(Into::into), logs: r.logs.into_iter().map(Into::into).collect(), + state_root: r.state_root.into(), + logs_bloom: r.log_bloom.into(), } } } @@ -71,6 +79,8 @@ impl From for Receipt { gas_used: Some(r.gas_used.into()), contract_address: r.contract_address.map(Into::into), logs: r.logs.into_iter().map(Into::into).collect(), + state_root: r.state_root.into(), + logs_bloom: r.log_bloom.into(), } } } @@ -86,6 +96,8 @@ impl From for Receipt { gas_used: None, contract_address: None, logs: r.logs.into_iter().map(Into::into).collect(), + state_root: r.state_root.into(), + logs_bloom: r.log_bloom.into(), } } } @@ -93,35 +105,36 @@ impl From for Receipt { #[cfg(test)] mod tests { use serde_json; - use std::str::FromStr; - use v1::types::{Log, Receipt, U256, H256, H160}; + use v1::types::{Log, Receipt}; #[test] fn receipt_serialization() { - let s = r#"{"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","cumulativeGasUsed":"0x20","gasUsed":"0x10","contractAddress":null,"logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","type":"mined"}]}"#; + let s = r#"{"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","cumulativeGasUsed":"0x20","gasUsed":"0x10","contractAddress":null,"logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","type":"mined"}],"root":"0x000000000000000000000000000000000000000000000000000000000000000a","logsBloom":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f"}"#; let receipt = Receipt { - transaction_hash: Some(H256::from(0)), - transaction_index: Some(U256::from(0)), - block_hash: Some(H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap()), - block_number: Some(U256::from(0x4510c)), - cumulative_gas_used: U256::from(0x20), - gas_used: Some(U256::from(0x10)), + transaction_hash: Some(0.into()), + transaction_index: Some(0.into()), + block_hash: Some("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5".parse().unwrap()), + block_number: Some(0x4510c.into()), + cumulative_gas_used: 0x20.into(), + gas_used: Some(0x10.into()), contract_address: None, logs: vec![Log { - address: H160::from_str("33990122638b9132ca29c723bdf037f1a891a70c").unwrap(), + address: "33990122638b9132ca29c723bdf037f1a891a70c".parse().unwrap(), topics: vec![ - H256::from_str("a6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc").unwrap(), - H256::from_str("4861736852656700000000000000000000000000000000000000000000000000").unwrap(), + "a6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc".parse().unwrap(), + "4861736852656700000000000000000000000000000000000000000000000000".parse().unwrap(), ], data: vec![].into(), - block_hash: Some(H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap()), - block_number: Some(U256::from(0x4510c)), - transaction_hash: Some(H256::default()), - transaction_index: Some(U256::default()), - log_index: Some(U256::from(1)), - log_type: "mined".to_owned(), - }] + block_hash: Some("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5".parse().unwrap()), + block_number: Some(0x4510c.into()), + transaction_hash: Some(0.into()), + transaction_index: Some(0.into()), + log_index: Some(1.into()), + log_type: "mined".into(), + }], + logs_bloom: 15.into(), + state_root: 10.into(), }; let serialized = serde_json::to_string(&receipt).unwrap(); diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 6aa9ee899..0982a5ef9 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -54,10 +54,17 @@ pub struct Transaction { /// Public key of the signer. #[serde(rename="publicKey")] pub public_key: Option, + /// The V field of the signature. + pub v: u8, + /// The R field of the signature. + pub r: H256, + /// The S field of the signature. + pub s: H256, } impl From for Transaction { fn from(t: LocalizedTransaction) -> Transaction { + let signature = t.signature(); Transaction { hash: t.hash().into(), nonce: t.nonce.into(), @@ -79,12 +86,16 @@ impl From for Transaction { }, raw: ::rlp::encode(&t.signed).to_vec().into(), public_key: t.public_key().ok().map(Into::into), + v: signature.v(), + r: signature.r().into(), + s: signature.s().into(), } } } impl From for Transaction { fn from(t: SignedTransaction) -> Transaction { + let signature = t.signature(); Transaction { hash: t.hash().into(), nonce: t.nonce.into(), @@ -106,6 +117,9 @@ impl From for Transaction { }, raw: ::rlp::encode(&t).to_vec().into(), public_key: t.public_key().ok().map(Into::into), + v: signature.v(), + r: signature.r().into(), + s: signature.s().into(), } } } @@ -119,7 +133,7 @@ mod tests { fn test_transaction_serialize() { let t = Transaction::default(); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null}"#); + assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"v":0,"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#); } } diff --git a/sync/src/api.rs b/sync/src/api.rs index 2e18fd5cc..1a33bc727 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -42,7 +42,7 @@ pub struct SyncConfig { /// Max blocks to download ahead pub max_download_ahead_blocks: usize, /// Network ID - pub network_id: U256, + pub network_id: usize, /// Main "eth" subprotocol name. pub subprotocol_name: [u8; 3], /// Fork block to check @@ -55,7 +55,7 @@ impl Default for SyncConfig { fn default() -> SyncConfig { SyncConfig { max_download_ahead_blocks: 20000, - network_id: U256::from(1), + network_id: 1, subprotocol_name: *b"eth", fork_block: None, warp_sync: false, diff --git a/sync/src/chain.rs b/sync/src/chain.rs index dc12fc926..063d60e6b 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -188,7 +188,7 @@ pub struct SyncStatus { /// Syncing protocol version. That's the maximum protocol version we connect to. pub protocol_version: u8, /// The underlying p2p network version. - pub network_id: U256, + pub network_id: usize, /// `BlockChain` height for the moment the sync started. pub start_block_number: BlockNumber, /// Last fully downloaded and imported block number (if any). @@ -269,7 +269,7 @@ struct PeerInfo { /// Peer chain genesis hash genesis: H256, /// Peer network id - network_id: U256, + network_id: usize, /// Peer best block hash latest_hash: H256, /// Peer total difficulty if known @@ -328,7 +328,7 @@ pub struct ChainSync { /// Last propagated block number last_sent_block_number: BlockNumber, /// Network ID - network_id: U256, + network_id: usize, /// Optional fork block to check fork_block: Option<(BlockNumber, H256)>, /// Snapshot downloader. @@ -2145,7 +2145,7 @@ mod tests { PeerInfo { protocol_version: 0, genesis: H256::zero(), - network_id: U256::zero(), + network_id: 0, latest_hash: peer_latest_hash, difficulty: None, asking: PeerAsking::Nothing, diff --git a/util/bigint/Cargo.toml b/util/bigint/Cargo.toml index c21b5239f..3b6b852cb 100644 --- a/util/bigint/Cargo.toml +++ b/util/bigint/Cargo.toml @@ -4,7 +4,7 @@ homepage = "http://ethcore.io" repository = "https://github.com/ethcore/parity" license = "GPL-3.0" name = "ethcore-bigint" -version = "0.1.1" +version = "0.1.2" authors = ["Ethcore "] build = "build.rs" diff --git a/util/bigint/src/hash.rs b/util/bigint/src/hash.rs index f782d1f90..20cdbae03 100644 --- a/util/bigint/src/hash.rs +++ b/util/bigint/src/hash.rs @@ -386,6 +386,12 @@ macro_rules! impl_hash { } } } + + impl<'a> From<&'a [u8]> for $from { + fn from(s: &'a [u8]) -> $from { + $from::from_slice(s) + } + } } } diff --git a/util/src/misc.rs b/util/src/misc.rs index b0452e85e..0f45dea66 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -40,7 +40,7 @@ pub fn version() -> String { let date_dash = if commit_date.is_empty() { "" } else { "-" }; let env = Target::env(); let env_dash = if env.is_empty() { "" } else { "-" }; - format!("Parity/v{}-unstable{}{}{}{}/{}-{}{}{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, Target::arch(), Target::os(), env_dash, env, rustc_version()) + format!("Parity/v{}-beta{}{}{}{}/{}-{}{}{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, Target::arch(), Target::os(), env_dash, env, rustc_version()) } /// Get the standard version data for this software. diff --git a/util/src/stats.rs b/util/src/stats.rs index fa73b5bea..a106ba29c 100644 --- a/util/src/stats.rs +++ b/util/src/stats.rs @@ -28,13 +28,17 @@ pub struct Histogram { } impl Histogram { - /// Histogram if a sorted corpus is at least fills the buckets. + /// Histogram of a sorted corpus if it at least spans the buckets. Bounds are left closed. pub fn new(corpus: &[U256], bucket_number: usize) -> Option { - if corpus.len() <= bucket_number { return None; } - let corpus_end = corpus.last().expect("there are at least bucket_number elements; qed").clone(); - // If there are extremely few transactions, go from zero. - let corpus_start = corpus.first().expect("there are at least bucket_number elements; qed").clone(); - let bucket_size = (corpus_end - corpus_start + 1.into()) / bucket_number.into(); + if corpus.len() < 1 { return None; } + let corpus_end = corpus.last().expect("there is at least 1 element; qed").clone(); + let corpus_start = corpus.first().expect("there is at least 1 element; qed").clone(); + // Bucket needs to be at least 1 wide. + let bucket_size = { + // Round up to get the entire corpus included. + let raw_bucket_size = (corpus_end - corpus_start + bucket_number.into()) / bucket_number.into(); + if raw_bucket_size == 0.into() { 1.into() } else { raw_bucket_size } + }; let mut bucket_end = corpus_start + bucket_size; let mut bucket_bounds = vec![corpus_start; bucket_number + 1]; @@ -42,10 +46,12 @@ impl Histogram { let mut corpus_i = 0; // Go through the corpus adding to buckets. for bucket in 0..bucket_number { - while corpus[corpus_i] < bucket_end { + while corpus.get(corpus_i).map_or(false, |v| v < &bucket_end) { + // Initialized to size bucket_number above; iterates up to bucket_number; qed counts[bucket] += 1; corpus_i += 1; } + // Initialized to size bucket_number + 1 above; iterates up to bucket_number; subscript is in range; qed bucket_bounds[bucket + 1] = bucket_end; bucket_end = bucket_end + bucket_size; } @@ -62,14 +68,36 @@ mod tests { #[test] fn check_histogram() { let hist = Histogram::new(&vec_into![643,689,1408,2000,2296,2512,4250,4320,4842,4958,5804,6065,6098,6354,7002,7145,7845,8589,8593,8895], 5).unwrap(); - let correct_bounds: Vec = vec_into![643,2293,3943,5593,7243,8893]; - assert_eq!(Histogram { bucket_bounds: correct_bounds, counts: vec![4,2,4,6,3] }, hist); - - assert!(Histogram::new(&vec_into![1, 2], 5).is_none()); + let correct_bounds: Vec = vec_into![643, 2294, 3945, 5596, 7247, 8898]; + assert_eq!(Histogram { bucket_bounds: correct_bounds, counts: vec![4,2,4,6,4] }, hist); } #[test] - fn should_not_panic_when_asking_for_bucket_too_big() { - assert!(Histogram::new(&vec_into![1, 2], 2).is_none()); + fn smaller_data_range_than_bucket_range() { + assert_eq!( + Histogram::new(&vec_into![1, 2, 2], 3), + Some(Histogram { bucket_bounds: vec_into![1, 2, 3, 4], counts: vec![1, 2, 0] }) + ); + } + + #[test] + fn data_range_is_not_multiple_of_bucket_range() { + assert_eq!( + Histogram::new(&vec_into![1, 2, 5], 2), + Some(Histogram { bucket_bounds: vec_into![1, 4, 7], counts: vec![2, 1] }) + ); + } + + #[test] + fn data_range_is_multiple_of_bucket_range() { + assert_eq!( + Histogram::new(&vec_into![1, 2, 6], 2), + Some(Histogram { bucket_bounds: vec_into![1, 4, 7], counts: vec![2, 1] }) + ); + } + + #[test] + fn none_when_too_few_data() { + assert!(Histogram::new(&vec_into![], 1).is_none()); } } diff --git a/windows/ptray/ptray.cpp b/windows/ptray/ptray.cpp index ac2f197a7..071b8704d 100644 --- a/windows/ptray/ptray.cpp +++ b/windows/ptray/ptray.cpp @@ -69,8 +69,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, UNREFERENCED_PARAMETER(lpCmdLine); CreateMutex(0, FALSE, _T("Local\\ParityTray")); - if (GetLastError() == ERROR_ALREADY_EXISTS) - return -1; + if (GetLastError() == ERROR_ALREADY_EXISTS) { + // open the UI + OpenUI(); + return 0; + } + LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING);