Compare commits

...

57 Commits

Author SHA1 Message Date
Arkadiy Paronyan
e128418147 Backporting to beta (#5657)
* Add CHANGELOG.md (#5513)

* Add CHANGELOG.md

* Add some more verbose CHANGELOG information for beta and stable releases

* Add dates to releases

* reorg into blocks before minimum history (#5558)

* v1.6.7
2017-05-18 16:10:34 +02:00
GitLab Build Bot
314e2764ae [ci skip] js-precompiled 20170518-135504 2017-05-18 13:58:06 +00:00
Tomasz Drwięga
1aea9caf6d [beta] Cancel Transaction (#5656)
* option to disable persistent txqueue (#5544)

* option to disable persistent txqueue

* New option goes with kin

* Remove transaction RPC (#4949)

* Cancel tx JS (#4958)

* Remove transaction RPC

* Bumping multihash and libc

* Updating nanomsg

* bump nanomsg

* cancel tx

* cancel-tx-js

* cancel-tx-js

* cancel-tx-js

* cancel-tx-hs

* cancel-tx-js

* cancel-tx-js

* cancel-tx-js

* small fixes

* edit & time till submit

* edit & time till submit

* updates

* updates

* udpates

* udpates

* grumbles

* step 1

* Wonderful updates

* ready

* small refact

* small refact

* grumbles 1

* ffx2

* good ol' fashioned updates

* latest and greatest

* removeHash

* removeHash

* spec

* fix 1

* fix 1

* fix 2

* fix 2

* ff

* ff

* ff

* updates

* Updating documentation for RPCs (#5392)

* Removing minBlocks occurrencies

* Docs for new RPCs.

* Fixing linting issues, updating *withToken documentatiojn.

* Adding missing RPCs. Fixing tests.

* Fixing lint issues.
2017-05-18 15:19:29 +02:00
Tomasz Drwięga
8dfc10ede9 [beta] Fix ethsign (#5600)
* Fix ethsign in beta.

* Fix tests.

* Fix compilation.

* CI fix

remove kcov
2017-05-18 13:50:17 +01:00
GitLab Build Bot
4d9f13196f [ci skip] js-precompiled 20170518-105555 2017-05-18 10:59:00 +00:00
Jaco Greeff
4b857bf4b0 [beta] Backport #5645 (Recover button) (#5654)
* Backport #5645

* Update property
2017-05-18 12:44:40 +02:00
keorn
0a5c6a9ac7 add monotonic transition to kovan (#5587) (#5630) 2017-05-16 12:06:12 +02:00
Denis S. Soldatov aka General-Beck
6cce5c8c9e fix snap build 2017-05-03 18:13:15 +03:00
Denis S. Soldatov aka General-Beck
ab065ecbd0 backports from master
add docker build for hub.docker.com/r/parity
add snap build
2017-05-03 18:04:35 +03:00
GitLab Build Bot
36015c960a [ci skip] js-precompiled 20170418-102604 2017-04-18 10:28:44 +00:00
Afri
8c4938d9c6 [beta] registry backports (#5445)
* Fixes to the Registry dapp (#4984)

* Don't show fee warning when there is none

* Hide Warning in Registry onclick

* Use the default account in the Registry

* Fix Etherscan links in Regsitry

* Fix references to api outside of `parity.js` (#4981)
2017-04-18 12:18:30 +02:00
Denis S. Soldatov aka General-Beck
2f3cddeea8 update URLs for docker 2017-04-11 18:15:29 +03:00
GitLab Build Bot
8c6e3f314a js-precompiled 20170411-114812 2017-04-11 14:28:38 +02:00
Arkadiy Paronyan
96cbfba916 Fixed repo URLs 2017-04-11 13:37:33 +02:00
Tomasz Drwięga
18b8d6aca6 Fix signerstore 2017-04-11 10:29:19 +02:00
Arkadiy Paronyan
6a44a0cf95 [beta] Backports (#5434)
* v1.6.6

* Strict validation transitions (#4988)

* ability to make validation stricter

* fix consensus

* remove logger

* Fix eth_sign showing as wallet account (#5309)

* defaultProps for account

* Pass signing account

* Update tests for Connect(...)

* Add new seed nodes (#5345)

* Kovan warp sync fixed
2017-04-10 13:35:57 +02:00
Denis S. Soldatov aka General-Beck
b7860e4a3f Update .gitlab-ci.yml
remove EXPERIMENTAL from darwin installer
2017-04-04 21:45:11 +03:00
keorn
4a910a762d [Beta] Aura eip155 validation transition (#5363)
* add eip155 validation

* add transition block
2017-04-01 13:21:29 +02:00
keorn
1626c78ae2 add eip155 validation (#5350) 2017-03-30 20:45:22 +02:00
Nikolay Volf
7f4e700013 Backport syntex update (#5316) 2017-03-29 10:43:13 +02:00
GitLab Build Bot
987390fb7d js-precompiled 20170328-003032 2017-03-28 09:30:58 +02:00
Arkadiy Paronyan
797f23f30b [beta] Backports (#5299)
* Fix FireFox overflows (#5000)

* Max width for container

* Set min-width

* Switching ValidatorSet (#4961)

* add multi validator set

* nicer comment

* validate in constructor

* reporting

* Avoid clogging up tmp when updater dir has bad permissions. (#5024)

* force earliest era set in snapshot restore (#5021)

* v1.6.5

* Fine grained snapshot chunking

* Ropsten revival
2017-03-28 01:45:56 +02:00
keorn
c1da49bbc4 Fix validator contract syncing (#4789) (#5011)
* make validator set aware of various states

* fix updater build

* clean up contract call

* failing sync test

* adjust tests

* nicer indent [ci skip]

* revert bound divisor
2017-03-23 20:39:51 +01:00
Arkadiy Paronyan
1164193019 Backporting to beta (#4995)
* v1.6.4

* ensure sealing work enabled if notifier registed

* fix condition check

* Always send full chunks (#4960)

* Bump nanomsg (#4965)

* Renaming evm binary to avoid conflicts. (#4899)
2017-03-22 13:46:32 +01:00
Jaco Greeff
1c217cbf2b [beta] UI backports (#4993)
* [ci skip] js-precompiled 20170314-121823

* Attach hardware wallets already in addressbook (#4912)

* Attach hardware wallets already in addressbook

* Only set values changed

* Add Vaults logic to First Run (#4894) (#4914)

* Add ability to configure Secure API (for #4885) (#4922)

* Add z-index to small modals as well (#4923)

* eth_sign where account === undefined (#4964)

* Update for case where account === undefined

* Update tests to not mask account === undefined

* default account = {} where undefined (thanks @tomusdrw)

* Fix Password Dialog forms style issue (#4968)
2017-03-22 12:45:55 +01:00
Tomasz Drwięga
d24f71f150 Bump multihash, libc and nanomsg (#4957) 2017-03-18 22:36:03 +01:00
Denis S. Soldatov aka General-Beck
4c66a021ec fix docker-build
[ci skip]
2017-03-14 20:22:36 +03:00
GitLab Build Bot
ccc57328e2 js-precompiled 20170314-121823 2017-03-14 13:32:08 +01:00
arkpar
77240b6e5a Merge branch 'beta' of github.com:ethcore/parity into beta 2017-03-14 13:06:37 +01:00
Jaco Greeff
2e428ddd58 [beta] Safari fixes (#4902)
* Add intitial max-width to sections

* Move background z-index to -1
2017-03-14 13:04:08 +01:00
arkpar
cd4081c149 Additional kovan params 2017-03-14 12:04:18 +01:00
arkpar
428342d69d v1.6.3 2017-03-13 17:54:27 +01:00
arkpar
8ee9b262f9 Recalculate receipt roots in close_and_lock 2017-03-13 17:51:34 +01:00
arkpar
e2bc251ff4 v 1.6.2 2017-03-13 11:57:42 +01:00
Gav Wood
d15372c8a6 Fix auto-updater. (#4868) 2017-03-11 14:46:17 +01:00
arkpar
bcf2245110 Removed libudev-dev dep 2017-03-10 18:18:09 +01:00
Arkadiy Paronyan
36f74fdf0d Supress USB error message (#4839) 2017-03-10 15:00:02 +01:00
GitLab Build Bot
45402544d3 [ci skip] js-precompiled 20170310-131332 2017-03-10 13:19:56 +00:00
Jaco Greeff
ab236690df [beta] UI backports (#4855)
* Added React Hot Reload to dapps + TokenDeplpoy fix (#4846)

* Fix method decoding (#4845)

* Fix contract deployment method decoding in Signer

* Linting

* Fix TxViewer when no `to` (contract deployment) (#4847)

* Added React Hot Reload to dapps + TokenDeplpoy fix

* Fixes to the LocalTx dapp

* Don't send the nonce for mined transactions

* Don't encode empty to values for options

* Pull steps from actual available steps (#4848)

* Wait for the value to have changed in the input (#4844)

* Backport Regsirty changes from #4589

* Test fixes for #4589
2017-03-10 14:03:10 +01:00
keorn
34e81101d7 [beta] Simple score (#4852)
* simple score

* ignore part of a test
2017-03-10 13:38:16 +01:00
GitLab Build Bot
07d4b9bbc9 [ci skip] js-precompiled 20170310-094757 2017-03-10 09:57:26 +00:00
Arkadiy Paronyan
d25a20b274 Backporting to beta (#4840)
* Fixes to the Registry dapp (#4838)

* Fix wrong ABI methods

* Fix

* v1.6.1
2017-03-10 10:08:04 +01:00
arkpar
70d17ce1d8 Reverted build order changes 2017-03-08 23:33:59 +01:00
GitLab Build Bot
3b14bcf29a js-precompiled 20170308-175401 2017-03-08 20:02:33 +01:00
Jaco Greeff
4fc69e11d6 Show token icons on list summary pages (#4826) (#4827)
* Adjust balance overlay margins (no jumps)

* Img only balances, small verifications

* Invalid tests removed

* Always wrap display (Thanks @ngotchac)

* Update tests to reflect reality
2017-03-08 18:35:00 +01:00
GitLab Build Bot
8e4df824b0 [ci skip] js-precompiled 20170308-160101 2017-03-08 16:08:54 +00:00
arkpar
e1f2e840cb Force js update 2017-03-08 16:28:56 +01:00
keorn
2a36a89c36 [beta] Engine backports (#4806)
* calibrate before rejection

* change flag name

* add eip155

* make network_id default
2017-03-08 15:26:26 +01:00
Arkadiy Paronyan
b643f4011d Better windows icon (#4804) 2017-03-08 14:53:25 +01:00
Jaco Greeff
814304bdd7 Better logic for contract deployments (#4821) (#4823) 2017-03-08 14:44:21 +01:00
GitLab Build Bot
f90607302d [ci skip] js-precompiled 20170308-122022 2017-03-08 12:25:16 +00:00
Jaco Greeff
af826f877e [beta] UI backports (#4818)
* Update the key (#4817)

* Adjust selection colours/display (#4811)

* Adjust selection colours to match with mui

* allow -> disable (simplify selections)

* Only use top-border

* Overlay selection line

* Slightly more muted unselected

* Restore address icon

* Fix default values for contract queries
2017-03-08 12:50:44 +01:00
GitLab Build Bot
f9a0aa0022 [ci skip] js-precompiled 20170308-095118 2017-03-08 09:58:51 +00:00
Jaco Greeff
c4196a5de3 [beta] UI backports (#4809)
* Update Wallet to new Wallet Code (#4805)

* Update Wallet Version

* Update Wallet Library

* Update Wallets Bytecodes

* Typo

* Separate Deploy in Contract API

* Use the new Wallet ABI // Update wallet code

* WIP .// Deploy from Wallet

* Update Wallet contract

* Contract Deployment for Wallet

* Working deployments for Single Owned Wallet contracts

* Linting

* Create a Wallet from a Wallet

* Linting

* Fix Signer transactions // Add Gas Used for transactions

* Deploy wallet contract fix

* Fix too high gas estimate for Wallet Contract Deploys

* Final piece ; deploying from Wallet owned by wallet

* Update Wallet Code

* Updated the Wallet Codes

* Fixing Wallet Deployments

* Add Support for older wallets

* Linting

* SMS Faucet (#4774)

* Faucet

* Remove flakey button-index testing

* Only display faucet when sms verified (mainnet)

* simplify availability checks

* WIP

* Resuest from verified -> verified

* Update endpoint, display response text

* Error icon on errors

* Parse hash text response

* Use /api/:address endpoint

* hash -> data

* Adjust sms-certified message

* Fix SectionList hovering issue (#4749)

* Fix SectionList Items hover when <3 items

* Even easier...

* lint (new)
2017-03-08 10:43:59 +01:00
arkpar
3b56e8eded Switch js branch to beta 2017-03-07 17:50:12 +01:00
Tomasz Drwięga
b3ccbbe913 Allow specifying extra cors headers for dapps (#4710) 2017-03-07 17:37:26 +01:00
arkpar
fe0f037f23 beta channel 2017-03-07 17:26:57 +01:00
229 changed files with 10003 additions and 2439 deletions

3
.gitignore vendored
View File

@@ -19,6 +19,9 @@
# mac stuff
.DS_Store
# npm stuff
npm-debug.log
# gdb files
.gdb_history

View File

@@ -1,8 +1,8 @@
stages:
- test
- js-build
- build
- push-release
- build
variables:
GIT_DEPTH: "3"
SIMPLECOV: "true"
@@ -14,7 +14,7 @@ cache:
untracked: true
linux-stable:
stage: build
image: ethcore/rust:stable
image: parity/rust:gitlab-ci
only:
- beta
- tags
@@ -26,14 +26,14 @@ linux-stable:
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- strip target/release/parity
- strip target/release/evm
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/evm deb/usr/bin/evm
- cp target/release/parity-evm deb/usr/bin/parity-evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
@@ -41,7 +41,7 @@ linux-stable:
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
@@ -55,13 +55,30 @@ linux-stable:
artifacts:
paths:
- target/release/parity
- target/release/parity/evmbin
- target/release/parity/ethstore
- target/release/parity/ethkey
- target/release/parity-evm
- target/release/ethstore
- target/release/ethkey
name: "stable-x86_64-unknown-linux-gnu_parity"
linux-snap:
stage: build
image: parity/snapcraft:gitlab-ci
only:
- beta
script:
- rm -rf *snap
- cd scripts
- snapcraft
tags:
- rust
- rust-stable
artifacts:
paths:
- scripts/parity_beta_amd64.snap
name: "stable-x86_64-unknown-linux-gnu_parity-snap"
allow_failure: true
linux-stable-debian:
stage: build
image: ethcore/rust-debian:latest
image: parity/rust-debian:gitlab-ci
only:
- beta
- tags
@@ -73,14 +90,14 @@ linux-stable-debian:
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- strip target/release/parity
- strip target/release/evm
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/evm deb/usr/bin/evm
- cp target/release/parity-evm deb/usr/bin/parity-evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
@@ -88,7 +105,7 @@ linux-stable-debian:
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity.md5 --body parity.md5
@@ -105,7 +122,7 @@ linux-stable-debian:
name: "stable-x86_64-unknown-debian-gnu_parity"
linux-beta:
stage: build
image: ethcore/rust:beta
image: parity/rust:gitlab-ci
only:
- beta
- tags
@@ -124,7 +141,7 @@ linux-beta:
allow_failure: true
linux-nightly:
stage: build
image: ethcore/rust:nightly
image: parity/rust:gitlab-ci
only:
- beta
- tags
@@ -143,7 +160,7 @@ linux-nightly:
allow_failure: true
linux-centos:
stage: build
image: ethcore/rust-centos:latest
image: parity/rust-centos:gitlab-ci
only:
- beta
- tags
@@ -159,7 +176,7 @@ linux-centos:
- export SHA3=$(target/release/parity tools hash target/release/parity)
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
@@ -174,7 +191,7 @@ linux-centos:
name: "x86_64-unknown-centos-gnu_parity"
linux-i686:
stage: build
image: ethcore/rust-i686:latest
image: parity/rust-i686:gitlab-ci
only:
- beta
- tags
@@ -196,7 +213,7 @@ linux-i686:
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
@@ -214,7 +231,7 @@ linux-i686:
allow_failure: true
linux-armv7:
stage: build
image: ethcore/rust-armv7:latest
image: parity/rust-armv7:gitlab-ci
only:
- beta
- tags
@@ -242,7 +259,7 @@ linux-armv7:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
@@ -260,7 +277,7 @@ linux-armv7:
allow_failure: true
linux-arm:
stage: build
image: ethcore/rust-arm:latest
image: parity/rust-arm:gitlab-ci
only:
- beta
- tags
@@ -288,7 +305,7 @@ linux-arm:
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
@@ -306,12 +323,12 @@ linux-arm:
allow_failure: true
linux-armv6:
stage: build
image: ethcore/rust-armv6:latest
image: parity/rust-armv6:gitlab-ci
only:
- beta
# - beta
# - tags
# - stable
# - triggers
- triggers
script:
- export CC=arm-linux-gnueabi-gcc
- export CXX=arm-linux-gnueabi-g++
@@ -329,7 +346,7 @@ linux-armv6:
- md5sum target/$PLATFORM/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
@@ -345,7 +362,7 @@ linux-armv6:
allow_failure: true
linux-aarch64:
stage: build
image: ethcore/rust-aarch64:latest
image: parity/rust-arm64:gitlab-ci
only:
- beta
- tags
@@ -373,7 +390,7 @@ linux-aarch64:
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
@@ -409,16 +426,16 @@ darwin:
packagesbuild -v mac/Parity.pkgproj
productsign --sign 'Developer ID Installer: PARITY TECHNOLOGIES LIMITED (P2PX3JU8FT)' target/release/Parity\ Ethereum.pkg target/release/Parity\ Ethereum-signed.pkg
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
md5sum "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" >> "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-osx-installer.pkg"
md5sum "parity-"$VER"-osx-installer.pkg" >> "parity-"$VER"-osx-installer.pkg.md5"
aws configure set aws_access_key_id $s3_key
aws configure set aws_secret_access_key $s3_secret
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer.pkg" --body "parity-"$VER"-osx-installer.pkg"
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer.pkg.md5" --body "parity-"$VER"-osx-installer.pkg.md5"
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
@@ -460,17 +477,17 @@ windows:
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
- md5sums win-installer.zip > win-installer.zip.md5
- cd ..\target\release\
- md5sums parity.exe parity.pdb > parity.md5
- md5sums parity.exe > parity.exe.md5
- zip parity.zip parity.exe parity.pdb parity.md5
- zip parity.zip parity.exe parity.md5
- md5sums parity.zip > parity.zip.md5
- cd ..\..
- aws configure set aws_access_key_id %s3_key%
- aws configure set aws_secret_access_key %s3_secret%
- echo %CI_BUILD_REF_NAME%
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "nightly" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %S3_BUCKET%
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
@@ -488,7 +505,6 @@ windows:
artifacts:
paths:
- target/release/parity.exe
- target/release/parity.pdb
- nsis/InstallParity.exe
name: "x86_64-pc-windows-msvc_parity"
docker-build:
@@ -502,6 +518,10 @@ docker-build:
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
- sh scripts/docker-build.sh $DOCKER_TAG
- docker logout
- docker login -u $Docker_Hub_User_Parity -p $Docker_Hub_Pass_Parity
- sh scripts/docker-build.sh $DOCKER_TAG
- docker logout
tags:
- docker
test-darwin:
@@ -531,19 +551,19 @@ test-windows:
allow_failure: true
test-rust-stable:
stage: test
image: ethcore/rust:stable
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS&&./scripts/cov.sh "$KCOV_CMD"; fi
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- rust
- rust-stable
js-test:
stage: test
image: ethcore/rust:stable
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
@@ -557,11 +577,12 @@ test-rust-beta:
stage: test
only:
- triggers
image: ethcore/rust:beta
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- rustup default beta
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
@@ -572,11 +593,12 @@ test-rust-nightly:
stage: test
only:
- triggers
image: ethcore/rust:nightly
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- rustup default nightly
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
@@ -590,7 +612,7 @@ js-release:
- beta
- stable
- tags
image: ethcore/rust:stable
image: parity/rust:gitlab-ci
before_script:
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
- echo $JS_FILES_MODIFIED
@@ -604,7 +626,7 @@ push-release:
stage: push-release
only:
- tags
image: ethcore/rust:stable
image: parity/rust:gitlab-ci
script:
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF

3842
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

384
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
description = "Parity Ethereum client"
name = "parity"
version = "1.6.0"
version = "1.6.7"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
@@ -25,7 +25,7 @@ app_dirs = "1.1.1"
fdlimit = "0.1"
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.6" }
ethsync = { path = "sync" }
ethcore = { path = "ethcore" }
ethcore-util = { path = "util" }

View File

@@ -12,8 +12,8 @@ rand = "0.3"
log = "0.3"
env_logger = "0.3"
futures = "0.1"
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.6" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.6" }
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
unicase = "1.3"
url = "1.0"
@@ -22,7 +22,7 @@ serde = "0.9"
serde_json = "0.9"
serde_derive = "0.9"
linked-hash-map = "0.3"
parity-dapps-glue = "1.4"
parity-dapps-glue = "1.7"
base32 = "0.3"
mime = "0.2"
mime_guess = "1.6.1"

View File

@@ -7,17 +7,17 @@ authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[build-dependencies]
quasi_codegen = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
quasi_codegen = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
[dependencies]
glob = { version = "0.2.11" }
mime_guess = { version = "1.6.1" }
aster = { version = "0.17", default-features = false }
quasi = { version = "0.11", default-features = false }
quasi_macros = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
syntex_syntax = { version = "0.33", optional = true }
aster = { version = "0.41", default-features = false }
quasi = { version = "0.32", default-features = false }
quasi_macros = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
syntex_syntax = { version = "0.58", optional = true }
clippy = { version = "0.0.90", optional = true }
[features]

View File

@@ -25,13 +25,11 @@ mod inner {
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let mut registry = syntex::Registry::new();
quasi_codegen::register(&mut registry);
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.rs");
registry.expand("", &src, &dst).unwrap();
quasi_codegen::expand(&src, &dst).unwrap();
}
}

View File

@@ -13,9 +13,8 @@ pub mod inner {
impl fold::Folder for StripAttributeFolder {
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
match attr.node.value.node {
ast::MetaItemKind::List(ref n, _) if n == &"webapp" => { return None; }
_ => {}
if &*attr.value.name.as_str() == "webapp" {
return None;
}
Some(attr)

View File

@@ -22,16 +22,12 @@ use self::mime_guess::guess_mime_type;
use std::path::{self, Path, PathBuf};
use std::ops::Deref;
use syntax::ast::{MetaItem, Item};
use syntax::ast;
use syntax::attr;
use syntax::ast::{self, MetaItem, Item};
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;
use syntax::print::pprust::{lit_to_string};
use syntax::parse::token::{InternedString};
use syntax::print::pprust::lit_to_string;
use syntax::symbol::InternedString;
pub fn expand_webapp_implementation(
cx: &mut ExtCtxt,
@@ -48,7 +44,7 @@ pub fn expand_webapp_implementation(
},
};
let builder = aster::AstBuilder::new().span(span);
implement_webapp(cx, &builder, &item, push);
implement_webapp(cx, &builder, item, push);
}
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
@@ -117,11 +113,12 @@ fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push
}
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
for meta_items in item.attrs().iter().filter_map(webapp_meta_items) {
for meta_items in item.attrs.iter().filter_map(webapp_meta_items) {
for meta_item in meta_items {
let is_path = &*meta_item.name.as_str() == "path";
match meta_item.node {
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"path" => {
if let Some(s) = get_str_from_lit(cx, name, lit) {
ast::MetaItemKind::NameValue(ref lit) if is_path => {
if let Some(s) = get_str_from_lit(cx, lit) {
return s.deref().to_owned();
}
},
@@ -134,14 +131,32 @@ fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
"web".to_owned()
}
fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<InternedString> {
fn webapp_meta_items(attr: &ast::Attribute) -> Option<Vec<ast::MetaItem>> {
let is_webapp = &*attr.value.name.as_str() == "webapp";
match attr.value.node {
ast::MetaItemKind::List(ref items) if is_webapp => {
attr::mark_used(&attr);
Some(
items.iter()
.map(|item| item.node.clone())
.filter_map(|item| match item {
ast::NestedMetaItemKind::MetaItem(item) => Some(item),
_ => None,
})
.collect()
)
}
_ => None
}
}
fn get_str_from_lit(cx: &ExtCtxt, lit: &ast::Lit) -> Option<InternedString> {
match lit.node {
ast::LitKind::Str(ref s, _) => Some(s.clone()),
ast::LitKind::Str(ref s, _) => Some(s.clone().as_str()),
_ => {
cx.span_err(
lit.span,
&format!("webapp annotation `{}` must be a string, not `{}`",
name,
&format!("webapp annotation path must be a string, not `{}`",
lit_to_string(lit)
)
);
@@ -150,16 +165,6 @@ fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<Interned
}
}
fn webapp_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
match attr.node.value.node {
ast::MetaItemKind::List(ref name, ref items) if name == &"webapp" => {
attr::mark_used(&attr);
Some(items)
}
_ => None
}
}
fn as_uri(path: &Path) -> String {
let mut s = String::new();
for component in path.iter() {
@@ -169,7 +174,6 @@ fn as_uri(path: &Path) -> String {
s[0..s.len()-1].into()
}
#[test]
fn should_convert_path_separators_on_all_platforms() {
// given

View File

@@ -22,7 +22,6 @@
extern crate syntex;
#[cfg(feature = "with-syntex")]
#[macro_use]
extern crate syntex_syntax as syntax;
#[cfg(feature = "with-syntex")]

View File

@@ -39,7 +39,11 @@ pub struct RestApi {
impl RestApi {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
Box::new(RestApi {
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
"null" => AccessControlAllowOrigin::Null,
other => AccessControlAllowOrigin::Value(other.into()),
}).collect()),
endpoints: endpoints,
fetcher: fetcher,
})

View File

@@ -111,6 +111,7 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
remote: Remote,
fetch: Option<T>,
}
@@ -126,6 +127,7 @@ impl ServerBuilder {
web_proxy_tokens: Arc::new(|_| false),
signer_address: None,
allowed_hosts: Some(vec![]),
extra_cors: None,
remote: remote,
fetch: None,
}
@@ -143,6 +145,7 @@ impl<T: Fetch> ServerBuilder<T> {
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
extra_cors: self.extra_cors,
remote: self.remote,
fetch: Some(fetch),
}
@@ -174,6 +177,13 @@ impl<T: Fetch> ServerBuilder<T> {
self
}
/// Extra cors headers.
/// `None` - no additional CORS URLs
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
self.extra_cors = cors;
self
}
/// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
@@ -187,6 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
NoAuth,
handler,
self.dapps_path,
@@ -207,6 +218,7 @@ impl<T: Fetch> ServerBuilder<T> {
Server::start_http(
addr,
self.allowed_hosts,
self.extra_cors,
HttpBasicAuth::single_user(username, password),
handler,
self.dapps_path,
@@ -251,8 +263,8 @@ impl Server {
}
/// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
match signer_address {
fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
let basic_cors = match signer_address {
Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
@@ -260,15 +272,20 @@ impl Server {
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
format!("https://{}", address(&signer_address)),
],
None => vec![],
};
match extra_cors {
None => basic_cors,
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
}
}
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
addr: &SocketAddr,
hosts: Option<Vec<String>>,
extra_cors: Option<Vec<String>>,
authorization: A,
handler: RpcHandler<Metadata, T>,
dapps_path: PathBuf,
@@ -297,7 +314,7 @@ impl Server {
remote.clone(),
fetch.clone(),
));
let cors_domains = Self::cors_domains(signer_address.clone());
let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
let special = Arc::new({
let mut special = HashMap::new();
@@ -413,8 +430,9 @@ mod util_tests {
// given
// when
let none = Server::cors_domains(None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
let none = Server::cors_domains(None, None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
// then
assert_eq!(none, Vec::<String>::new());
@@ -425,7 +443,7 @@ mod util_tests {
"https://parity.web3.site".into(),
"https://parity.web3.site:18180".into(),
"https://127.0.0.1:18180".into()
]);
assert_eq!(extra, vec!["all".to_owned()]);
}
}

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers};
#[test]
fn should_return_error() {
@@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
);
}
#[test]
fn should_return_extra_cors_headers() {
// given
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://somedomain.io\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
}

View File

@@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
}
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
}
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
}

View File

@@ -11,7 +11,7 @@ rustc_version = "0.1"
[dependencies]
parity-ui-dev = { path = "../../js", optional = true }
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true }
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
[features]
no-precompiled-js = ["parity-ui-dev"]

View File

@@ -48,7 +48,7 @@ RUN apt-get update && \
# show backtraces
RUST_BACKTRACE=1 && \
# build parity
cd /build&&git clone https://github.com/ethcore/parity && \
cd /build&&git clone https://github.com/paritytech/parity && \
cd parity && \
git pull&& \
git checkout $BUILD_TAG && \

View File

@@ -27,6 +27,7 @@ byteorder = "1.0"
transient-hashmap = "0.1"
linked-hash-map = "0.3.0"
lru-cache = "0.1.0"
itertools = "0.5"
ethabi = "1.0.0"
evmjit = { path = "../evmjit", optional = true }
clippy = { version = "0.0.103", optional = true}

View File

@@ -33,7 +33,7 @@
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@@ -53,7 +53,9 @@
"enode://5fbfb426fbb46f8b8c1bd3dd140f5b511da558cd37d60844b525909ab82e13a25ee722293c829e52cb65c2305b1637fa9a2ea4d6634a224d5f400bfe244ac0de@162.243.55.45:30303",
"enode://42d8f29d1db5f4b2947cd5c3d76c6d0d3697e6b9b3430c3d41e46b4bb77655433aeedc25d4b4ea9d8214b6a43008ba67199374a9b53633301bca0cd20c6928ab@104.155.176.151:30303",
"enode://814920f1ec9510aa9ea1c8f79d8b6e6a462045f09caa2ae4055b0f34f7416fca6facd3dd45f1cf1673c0209e0503f02776b8ff94020e98b6679a0dc561b4eba0@104.154.136.117:30303",
"enode://72e445f4e89c0f476d404bc40478b0df83a5b500d2d2e850e08eb1af0cd464ab86db6160d0fde64bd77d5f0d33507ae19035671b3c74fec126d6e28787669740@104.198.71.200:30303"
"enode://72e445f4e89c0f476d404bc40478b0df83a5b500d2d2e850e08eb1af0cd464ab86db6160d0fde64bd77d5f0d33507ae19035671b3c74fec126d6e28787669740@104.198.71.200:30303",
"enode://39abab9d2a41f53298c0c9dc6bbca57b0840c3ba9dccf42aa27316addc1b7e56ade32a0a9f7f52d6c5db4fe74d8824bcedfeaecf1a4e533cacb71cf8100a9442@144.76.238.49:30303",
"enode://f50e675a34f471af2438b921914b5f06499c7438f3146f6b8936f1faeb50b8a91d0d0c24fb05a66f05865cd58c24da3e664d0def806172ddd0d4c5bdbf37747e@144.76.238.49:30306"
],
"accounts": {
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@@ -23,14 +23,18 @@
"0x00a0a24b9f0e5ec7aa4c7389b8302fd0123194de"
]
}
},
"validateScoreTransition": 1000000,
"eip155Transition": 1000000,
"validateStepTransition": 1500000
}
}
},
"params": {
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2A"
"networkID" : "0x2A",
"validateReceiptsTransition" : 1000000
},
"genesis": {
"seal": {

View File

@@ -25,8 +25,8 @@
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x3",
"forkBlock": 333922,
"forkCanonHash": "0x8737eb141d4f05db57af63fc8d3b4d4d8f9cddb0c4e1ab855de8c288fdc1924f",
"forkBlock": 641350,
"forkCanonHash": "0x8033403e9fe5811a7b6d6b469905915de1c59207ce2172cbcf5d6ff14fa6a2eb",
"eip98Transition": "0x7fffffffffffff"
},
"genesis": {
@@ -44,11 +44,8 @@
"gasLimit": "0x1000000"
},
"nodes": [
"enode://a22f0977ce02653bf95e38730106356342df48b5222e2c2a1a6f9ef34769bf593bae9ca0a888cf60839edd52efc1b6e393c63a57d76f4c4fe14e641f1f9e637e@128.199.55.137:30303",
"enode://012239fccf3ff1d92b036983a430cb6705c6528c96c0354413f8854802138e5135c084ab36e7c54efb621c46728df8c3a6f4c1db9bb48a1330efe3f82f2dd7a6@52.169.94.142:30303",
"enode://1462682e4b7ba2258346d55e25e5b9d264b0db40cee12bdfba4e72b1d7050350ea954c006e9106dd96a128e6e0bd6dffb17eed51f9f99bf7f9cdadfeaf8da4ff@51.15.61.253:30303",
"enode://98fbb020c799ae39a828bd75dc2bd5d4721539faf317076b275f91182a5c8900b592e8abfdddceae674a7c3bb40ea00a6ca9ccb7805ab58c4b7b29c61c8f7239@51.15.62.44:30303",
"enode://d801dd4e3d15a8bf785931add164bd9c313e3f6b5749d9302b311f2b48064cba5c86c32b1302c27cd983fc89ae07d4d306dd1197610835b8782e95dfb1b3f9ea@51.15.43.255:30303"
"enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303",
"enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
],
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@@ -139,6 +139,7 @@
}
},
"params": {
"eip98Transition": "0x7fffffffffffffff",
"accountStartNonce": "0x00",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",

View File

@@ -38,7 +38,7 @@
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@@ -27,7 +27,7 @@
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

View File

@@ -0,0 +1,42 @@
{
"name": "TestMutiValidator",
"engine": {
"basicAuthority": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"validators": {
"multi": {
"0": { "list": ["0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"] },
"2": { "list": ["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"] }
}
}
}
}
},
"params": {
"accountStartNonce": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x69"
},
"genesis": {
"seal": {
"generic": "0xc180"
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
},
"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 } } } },
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "99999999999999999999999" },
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "99999999999999999999999" }
}
}

View File

@@ -152,7 +152,7 @@ impl AccountProvider {
manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum });
hardware_store = Some(manager)
},
Err(e) => warn!("Error initializing hardware wallets: {}", e),
Err(e) => debug!("Error initializing hardware wallets: {}", e),
}
}
AccountProvider {

View File

@@ -493,6 +493,16 @@ impl LockedBlock {
_ => Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes }),
}
}
/// Remove state root from transaction receipts to make them EIP-98 compatible.
pub fn strip_receipts(self) -> LockedBlock {
let mut block = self;
for receipt in &mut block.block.receipts {
receipt.state_root = None;
}
block.block.header.set_receipts_root(ordered_trie_root(block.block.receipts.iter().map(|r| r.rlp_bytes().to_vec())));
block
}
}
impl Drain for LockedBlock {
@@ -553,7 +563,6 @@ pub fn enact(
b.set_extra_data(header.extra_data().clone()).unwrap_or_else(|e| warn!("Couldn't set extradata: {}. Ignoring.", e));
b.set_uncles_hash(header.uncles_hash().clone());
b.set_transactions_root(header.transactions_root().clone());
b.set_receipts_root(header.receipts_root().clone());
push_transactions(&mut b, transactions)?;
for u in uncles {

View File

@@ -253,7 +253,7 @@ impl Client {
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
trace!(target: "client", "Found registrar at {}", reg_addr);
let weak = Arc::downgrade(&client);
let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)));
let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d)));
*client.registrar.lock() = Some(registrar);
}
Ok(client)
@@ -355,7 +355,7 @@ impl Client {
let chain = self.chain.read();
// Check the block isn't so old we won't be able to enact it.
let best_block_number = chain.best_block_number();
if best_block_number >= self.history && header.number() <= best_block_number - self.history {
if self.pruning_info().earliest_state > header.number() {
warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number);
return Err(());
}
@@ -375,10 +375,14 @@ impl Client {
let db = self.state_db.lock().boxed_clone_canon(header.parent_hash());
let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone());
let locked_block = enact_result.map_err(|e| {
let mut locked_block = enact_result.map_err(|e| {
warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
})?;
if header.number() < self.engine().params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() {
locked_block = locked_block.strip_receipts();
}
// Final Verification
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
@@ -673,7 +677,7 @@ impl Client {
let db = self.state_db.lock().boxed_clone();
// early exit for pruned blocks
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + self.history {
if db.is_pruned() && self.pruning_info().earliest_state > block_number {
return None;
}
@@ -774,7 +778,7 @@ impl Client {
let best_block_number = self.chain_info().best_block_number;
let block_number = self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))?;
if best_block_number > self.history + block_number && db.is_pruned() {
if db.is_pruned() && self.pruning_info().earliest_state > block_number {
return Err(snapshot::Error::OldBlockPrunedDB.into());
}
@@ -1445,7 +1449,7 @@ impl BlockChainClient for Client {
}
}
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
let from = Address::default();
let transaction = Transaction {
nonce: self.latest_nonce(&from),
@@ -1456,7 +1460,7 @@ impl BlockChainClient for Client {
data: data,
}.fake_sign(from);
self.call(&transaction, BlockId::Latest, Default::default())
self.call(&transaction, block_id, Default::default())
.map_err(|e| format!("{:?}", e))
.map(|executed| {
executed.output

View File

@@ -731,7 +731,7 @@ impl BlockChainClient for TestBlockChainClient {
}
}
fn call_contract(&self, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }
fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError> {
let transaction = Transaction {

View File

@@ -254,7 +254,7 @@ pub trait BlockChainClient : Sync + Send {
fn pruning_info(&self) -> PruningInfo;
/// Like `call`, but with various defaults. Designed to be used for calling contracts.
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String>;
/// Import a transaction: used for misbehaviour reporting.
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError>;

View File

@@ -27,12 +27,13 @@ use block::*;
use spec::CommonParams;
use engines::{Engine, Seal, EngineError};
use header::Header;
use error::{Error, BlockError};
use error::{Error, TransactionError, BlockError};
use evm::Schedule;
use ethjson;
use io::{IoContext, IoHandler, TimerToken, IoService};
use env_info::EnvInfo;
use builtin::Builtin;
use transaction::UnverifiedTransaction;
use client::{Client, EngineClient};
use state::CleanupMode;
use super::signer::EngineSigner;
@@ -53,6 +54,12 @@ pub struct AuthorityRoundParams {
pub start_step: Option<u64>,
/// Valid validators.
pub validators: ethjson::spec::ValidatorSet,
/// Chain score validation transition block.
pub validate_score_transition: u64,
/// Number of first block where EIP-155 rules are validated.
pub eip155_transition: u64,
/// Monotonic step validation transition block.
pub validate_step_transition: u64,
}
impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
@@ -64,6 +71,9 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
registrar: p.registrar.map_or_else(Address::new, Into::into),
start_step: p.start_step.map(Into::into),
validate_score_transition: p.validate_score_transition.map_or(0, Into::into),
eip155_transition: p.eip155_transition.map_or(0, Into::into),
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
}
}
}
@@ -82,7 +92,12 @@ pub struct AuthorityRound {
proposed: AtomicBool,
client: RwLock<Option<Weak<EngineClient>>>,
signer: EngineSigner,
validators: Box<ValidatorSet + Send + Sync>,
validators: Box<ValidatorSet>,
/// Is this Engine just for testing (prevents step calibration).
calibrate_step: bool,
validate_score_transition: u64,
eip155_transition: u64,
validate_step_transition: u64,
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@@ -122,6 +137,10 @@ impl AuthorityRound {
client: RwLock::new(None),
signer: Default::default(),
validators: new_validator_set(our_params.validators),
calibrate_step: our_params.start_step.is_none(),
validate_score_transition: our_params.validate_score_transition,
eip155_transition: our_params.eip155_transition,
validate_step_transition: our_params.validate_step_transition,
});
// Do not initialize timeouts for tests.
if should_timeout {
@@ -131,6 +150,12 @@ impl AuthorityRound {
Ok(engine)
}
fn calibrate_step(&self) {
if self.calibrate_step {
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
}
}
fn remaining_step_duration(&self) -> Duration {
let now = unix_now();
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
@@ -141,12 +166,22 @@ impl AuthorityRound {
}
}
fn step_proposer(&self, step: usize) -> Address {
self.validators.get(step)
fn step_proposer(&self, bh: &H256, step: usize) -> Address {
self.validators.get(bh, step)
}
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
self.step_proposer(step) == *address
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
self.step_proposer(bh, step) == *address
}
fn is_future_step(&self, step: usize) -> bool {
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
// Make absolutely sure that the step is correct.
self.calibrate_step();
step > self.step.load(AtomicOrdering::SeqCst) + 1
} else {
false
}
}
}
@@ -216,9 +251,8 @@ impl Engine for AuthorityRound {
}
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
// Chain scoring: total weight is sqrt(U256::max_value())*height - step
let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load(AtomicOrdering::SeqCst).into();
header.set_difficulty(new_difficulty);
// Chain scoring: weak height scoring, backported for compatibility.
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.gas_limit_bound_divisor;
@@ -231,7 +265,7 @@ impl Engine for AuthorityRound {
}
fn seals_internally(&self) -> Option<bool> {
Some(self.validators.contains(&self.signer.address()))
Some(self.signer.address() != Address::default())
}
/// Attempt to seal the block internally.
@@ -242,7 +276,7 @@ impl Engine for AuthorityRound {
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
let header = block.header();
let step = self.step.load(AtomicOrdering::SeqCst);
if self.is_step_proposer(step, header.author()) {
if self.is_step_proposer(header.parent_hash(), step, header.author()) {
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst);
@@ -276,40 +310,47 @@ impl Engine for AuthorityRound {
Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: header.seal().len() }
)))
} else if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) {
Err(From::from(BlockError::DifficultyOutOfBounds(
OutOfBounds { min: None, max: Some(U256::from(U128::max_value())), found: *header.difficulty() }
)))
} else {
Ok(())
}
}
/// Check if the signature belongs to the correct proposer.
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let header_step = header_step(header)?;
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
Ok(())
}
/// Do the validator and gas limit validation.
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let step = header_step(header)?;
// Give one step slack if step is lagging, double vote is still not possible.
if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 {
let proposer_signature = header_signature(header)?;
let correct_proposer = self.step_proposer(header_step);
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
Ok(())
} else {
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
}
} else {
if self.is_future_step(step) {
trace!(target: "engine", "verify_block_unordered: block from the future");
self.validators.report_benign(header.author());
Err(BlockError::InvalidSeal)?
} else {
// Check if the signature belongs to a validator, can depend on parent state.
let proposer_signature = header_signature(header)?;
let correct_proposer = self.step_proposer(header.parent_hash(), step);
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step);
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
}
}
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
// Do not calculate difficulty for genesis blocks.
if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
}
let step = header_step(header)?;
// Check if parent is from a previous step.
if step == header_step(parent)? {
trace!(target: "engine", "Multiple blocks proposed for step {}.", step);
let parent_step = header_step(parent)?;
if step == parent_step
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
self.validators.report_malicious(header.author());
Err(EngineError::DoubleVote(header.author().clone()))?;
}
@@ -323,6 +364,18 @@ impl Engine for AuthorityRound {
Ok(())
}
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> {
t.check_low_s()?;
if let Some(n) = t.network_id() {
if header.number() >= self.eip155_transition && n != self.params().chain_id {
return Err(TransactionError::InvalidNetworkId.into());
}
}
Ok(())
}
fn register_client(&self, client: Weak<Client>) {
*self.client.write() = Some(client.clone());
self.validators.register_contract(client);
@@ -394,7 +447,7 @@ mod tests {
let mut header: Header = Header::default();
header.set_seal(vec![encode(&H520::default()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None);
let verify_result = engine.verify_block_family(&header, &Default::default(), None);
assert!(verify_result.is_err());
}
@@ -432,10 +485,14 @@ mod tests {
#[test]
fn proposer_switching() {
let mut header: Header = Header::default();
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(addr);
let engine = Spec::new_test_round().engine;
@@ -444,17 +501,22 @@ mod tests {
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err());
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok());
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
}
#[test]
fn rejects_future_block() {
let mut header: Header = Header::default();
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_author(addr);
let engine = Spec::new_test_round().engine;
@@ -463,8 +525,8 @@ mod tests {
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok());
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err());
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
}
}

View File

@@ -58,7 +58,7 @@ pub struct BasicAuthority {
gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>,
signer: EngineSigner,
validators: Box<ValidatorSet + Send + Sync>,
validators: Box<ValidatorSet>,
}
impl BasicAuthority {
@@ -104,14 +104,14 @@ impl Engine for BasicAuthority {
}
fn seals_internally(&self) -> Option<bool> {
Some(self.validators.contains(&self.signer.address()))
Some(self.signer.address() != Address::default())
}
/// Attempt to seal the block internally.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
let header = block.header();
let author = header.author();
if self.validators.contains(author) {
if self.validators.contains(header.parent_hash(), author) {
// account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]);
@@ -133,20 +133,20 @@ impl Engine for BasicAuthority {
Ok(())
}
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
use rlp::{UntrustedRlp, View};
// check the signature is legit.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.validators.contains(&signer) {
return Err(BlockError::InvalidSeal)?;
}
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
Ok(())
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// we should not calculate difficulty for genesis blocks
use rlp::{UntrustedRlp, View};
// Check if the signature belongs to a validator, can depend on parent state.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.validators.contains(header.parent_hash(), &signer) {
return Err(BlockError::InvalidSeal)?;
}
// Do not calculate difficulty for genesis blocks.
if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
}
@@ -239,7 +239,7 @@ mod tests {
let mut header: Header = Header::default();
header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None);
let verify_result = engine.verify_block_family(&header, &Default::default(), None);
assert!(verify_result.is_err());
}

View File

@@ -39,7 +39,7 @@ use account_provider::AccountProvider;
use block::ExecutedBlock;
use builtin::Builtin;
use env_info::EnvInfo;
use error::Error;
use error::{Error, TransactionError};
use spec::CommonParams;
use evm::Schedule;
use header::Header;
@@ -157,6 +157,13 @@ pub trait Engine : Sync + Send {
// TODO: consider including State in the params.
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, _header: &Header) -> Result<(), Error> {
t.check_low_s()?;
if let Some(n) = t.network_id() {
if n != self.params().chain_id {
return Err(TransactionError::InvalidNetworkId.into());
}
}
Ok(())
}
@@ -166,7 +173,9 @@ pub trait Engine : Sync + Send {
}
/// The network ID that transactions should be signed with.
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> { None }
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> {
Some(self.params().chain_id)
}
/// 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

View File

@@ -95,8 +95,10 @@ pub struct Tendermint {
last_lock: AtomicUsize,
/// Bare hash of the proposed block, used for seal submission.
proposal: RwLock<Option<H256>>,
/// Hash of the proposal parent block.
proposal_parent: RwLock<H256>,
/// Set used to determine the current validators.
validators: Box<ValidatorSet + Send + Sync>,
validators: Box<ValidatorSet>,
}
impl Tendermint {
@@ -114,11 +116,12 @@ impl Tendermint {
height: AtomicUsize::new(1),
view: AtomicUsize::new(0),
step: RwLock::new(Step::Propose),
votes: VoteCollector::default(),
votes: Default::default(),
signer: Default::default(),
lock_change: RwLock::new(None),
last_lock: AtomicUsize::new(0),
proposal: RwLock::new(None),
proposal_parent: Default::default(),
validators: new_validator_set(our_params.validators),
});
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
@@ -232,7 +235,7 @@ impl Tendermint {
let height = self.height.load(AtomicOrdering::SeqCst);
if let Some(block_hash) = *self.proposal.read() {
// Generate seal and remove old votes.
if self.is_signer_proposer() {
if self.is_signer_proposer(&*self.proposal_parent.read()) {
let proposal_step = VoteStep::new(height, view, Step::Propose);
let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit);
if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) {
@@ -254,23 +257,23 @@ impl Tendermint {
}
fn is_authority(&self, address: &Address) -> bool {
self.validators.contains(address)
self.validators.contains(&*self.proposal_parent.read(), address)
}
fn is_above_threshold(&self, n: usize) -> bool {
n > self.validators.count() * 2/3
n > self.validators.count(&*self.proposal_parent.read()) * 2/3
}
/// Find the designated for the given view.
fn view_proposer(&self, height: Height, view: View) -> Address {
fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address {
let proposer_nonce = height + view;
trace!(target: "engine", "Proposer nonce: {}", proposer_nonce);
self.validators.get(proposer_nonce)
self.validators.get(bh, proposer_nonce)
}
/// Check if address is a proposer for given view.
fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
let proposer = self.view_proposer(height, view);
fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
let proposer = self.view_proposer(bh, height, view);
if proposer == *address {
Ok(())
} else {
@@ -279,8 +282,8 @@ impl Tendermint {
}
/// Check if current signer is the current proposer.
fn is_signer_proposer(&self) -> bool {
let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
fn is_signer_proposer(&self, bh: &H256) -> bool {
let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
self.signer.is_address(&proposer)
}
@@ -419,7 +422,7 @@ impl Engine for Tendermint {
/// Should this node participate.
fn seals_internally(&self) -> Option<bool> {
Some(self.is_authority(&self.signer.address()))
Some(self.signer.address() != Address::default())
}
/// Attempt to seal generate a proposal seal.
@@ -427,7 +430,7 @@ impl Engine for Tendermint {
let header = block.header();
let author = header.author();
// Only proposer can generate seal if None was generated.
if !self.is_signer_proposer() || self.proposal.read().is_some() {
if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() {
return Seal::None;
}
@@ -441,6 +444,7 @@ impl Engine for Tendermint {
self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author);
// Remember proposal for later seal submission.
*self.proposal.write() = bh;
*self.proposal_parent.write() = header.parent_hash().clone();
Seal::Proposal(vec![
::rlp::encode(&view).to_vec(),
::rlp::encode(&signature).to_vec(),
@@ -505,7 +509,12 @@ impl Engine for Tendermint {
}
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
Ok(())
}
/// Verify validators and gas limit.
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let proposal = ConsensusMessage::new_proposal(header)?;
let proposer = proposal.verify()?;
if !self.is_authority(&proposer) {
@@ -522,7 +531,7 @@ impl Engine for Tendermint {
Some(a) => a,
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
};
if !self.validators.contains(&address) {
if !self.validators.contains(header.parent_hash(), &address) {
Err(EngineError::NotAuthorized(address.to_owned()))?
}
@@ -545,12 +554,9 @@ impl Engine for Tendermint {
found: signatures_len
}))?;
}
self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
}
Ok(())
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
if header.number() == 0 {
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
}
@@ -595,6 +601,7 @@ impl Engine for Tendermint {
debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
if self.is_view(&proposal) {
*self.proposal.write() = proposal.block_hash.clone();
*self.proposal_parent.write() = header.parent_hash().clone();
}
self.votes.vote(proposal, &proposer);
true
@@ -607,7 +614,7 @@ impl Engine for Tendermint {
trace!(target: "engine", "Propose timeout.");
if self.proposal.read().is_none() {
// Report the proposer if no proposal was received.
let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
self.validators.report_benign(&current_proposer);
}
Step::Prevote
@@ -765,20 +772,25 @@ mod tests {
let (spec, tap) = setup();
let engine = spec.engine;
let mut header = Header::default();
let validator = insert_and_unlock(&tap, "0");
header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Good proposer.
assert!(engine.verify_block_unordered(&header.clone(), None).is_ok());
let mut parent_header: Header = Header::default();
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
let validator = insert_and_unlock(&tap, "1");
header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Good proposer.
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
let validator = insert_and_unlock(&tap, "0");
header.set_author(validator);
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Bad proposer.
match engine.verify_block_unordered(&header, None) {
match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotProposer(_))) => {},
_ => panic!(),
}
@@ -788,7 +800,7 @@ mod tests {
let seal = proposal_seal(&tap, &header, 0);
header.set_seal(seal);
// Not authority.
match engine.verify_block_unordered(&header, None) {
match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
_ => panic!(),
};
@@ -800,19 +812,24 @@ mod tests {
let (spec, tap) = setup();
let engine = spec.engine;
let mut parent_header: Header = Header::default();
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header = Header::default();
header.set_number(2);
header.set_gas_limit(U256::from_str("222222").unwrap());
let proposer = insert_and_unlock(&tap, "1");
header.set_author(proposer);
let mut seal = proposal_seal(&tap, &header, 0);
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
header.set_seal(seal.clone());
// One good signature is not enough.
match engine.verify_block_unordered(&header, None) {
match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {},
_ => panic!(),
}
@@ -823,7 +840,7 @@ mod tests {
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec();
header.set_seal(seal.clone());
assert!(engine.verify_block_unordered(&header, None).is_ok());
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
let bad_voter = insert_and_unlock(&tap, "101");
let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap();
@@ -832,7 +849,7 @@ mod tests {
header.set_seal(seal);
// One good and one bad signature.
match engine.verify_block_unordered(&header, None) {
match engine.verify_block_family(&header, &parent_header, None) {
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
_ => panic!(),
};

View File

@@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract;
/// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorContract {
validators: Arc<ValidatorSafeContract>,
validators: ValidatorSafeContract,
provider: RwLock<Option<provider::Contract>>,
}
impl ValidatorContract {
pub fn new(contract_address: Address) -> Self {
ValidatorContract {
validators: Arc::new(ValidatorSafeContract::new(contract_address)),
validators: ValidatorSafeContract::new(contract_address),
provider: RwLock::new(None),
}
}
}
impl ValidatorSet for Arc<ValidatorContract> {
fn contains(&self, address: &Address) -> bool {
self.validators.contains(address)
impl ValidatorSet for ValidatorContract {
fn contains(&self, bh: &H256, address: &Address) -> bool {
self.validators.contains(bh, address)
}
fn get(&self, nonce: usize) -> Address {
self.validators.get(nonce)
fn get(&self, bh: &H256, nonce: usize) -> Address {
self.validators.get(bh, nonce)
}
fn count(&self) -> usize {
self.validators.count()
fn count(&self, bh: &H256) -> usize {
self.validators.count(bh)
}
fn report_malicious(&self, address: &Address) {
@@ -144,6 +144,7 @@ mod tests {
use header::Header;
use account_provider::AccountProvider;
use miner::MinerService;
use types::ids::BlockId;
use client::BlockChainClient;
use tests::helpers::generate_dummy_client_with_spec_and_accounts;
use super::super::ValidatorSet;
@@ -154,8 +155,9 @@ mod tests {
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None);
let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_contract(Arc::downgrade(&client));
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
let last_hash = client.best_block_header().hash();
assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
}
#[test]
@@ -171,18 +173,21 @@ mod tests {
client.miner().set_engine_signer(v1, "".into()).unwrap();
let mut header = Header::default();
let seal = encode(&vec!(5u8)).to_vec();
header.set_seal(vec!(seal));
let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()];
header.set_seal(seal);
header.set_author(v1);
header.set_number(1);
header.set_number(2);
header.set_parent_hash(client.chain_info().best_block_hash);
// `reportBenign` when the designated proposer releases block from the future (bad clock).
assert!(client.engine().verify_block_unordered(&header, None).is_err());
assert!(client.engine().verify_block_family(&header, &header, None).is_err());
// Seal a block.
client.engine().step();
assert_eq!(client.chain_info().best_block_number, 1);
// Check if the unresponsive validator is `disliked`.
assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e");
assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e");
// Simulate a misbehaving validator by handling a double proposal.
let header = client.best_block_header().decode();
assert!(client.engine().verify_block_family(&header, &header, None).is_err());
// Seal a block.
client.engine().step();

View File

@@ -19,31 +19,36 @@
mod simple_list;
mod safe_contract;
mod contract;
mod multi;
use std::sync::Weak;
use util::{Address, Arc};
use util::{Address, H256};
use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client;
use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))),
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))),
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())),
ValidatorSpec::Multi(sequence) => Box::new(
Multi::new(sequence.into_iter().map(|(block, set)| (block.into(), new_validator_set(set))).collect())
),
}
}
pub trait ValidatorSet {
pub trait ValidatorSet: Send + Sync {
/// Checks if a given address is a validator.
fn contains(&self, address: &Address) -> bool;
fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool;
/// Draws an validator nonce modulo number of validators.
fn get(&self, nonce: usize) -> Address;
fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address;
/// Returns the current number of validators.
fn count(&self) -> usize;
fn count(&self, parent_block_hash: &H256) -> usize;
/// Notifies about malicious behaviour.
fn report_malicious(&self, _validator: &Address) {}
/// Notifies about benign misbehaviour.

View File

@@ -0,0 +1,158 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Validator set changing at fork blocks.
use std::collections::BTreeMap;
use std::sync::Weak;
use util::{H256, Address, RwLock};
use ids::BlockId;
use header::BlockNumber;
use client::{Client, BlockChainClient};
use super::ValidatorSet;
type BlockNumberLookup = Box<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>;
pub struct Multi {
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
block_number: RwLock<BlockNumberLookup>,
}
impl Multi {
pub fn new(set_map: BTreeMap<BlockNumber, Box<ValidatorSet>>) -> Self {
assert!(set_map.get(&0u64).is_some(), "ValidatorSet has to be specified from block 0.");
Multi {
sets: set_map,
block_number: RwLock::new(Box::new(move |_| Err("No client!".into()))),
}
}
fn correct_set(&self, bh: &H256) -> Option<&Box<ValidatorSet>> {
match self
.block_number
.read()(bh)
.map(|parent_block| self
.sets
.iter()
.rev()
.find(|&(block, _)| *block <= parent_block + 1)
.expect("constructor validation ensures that there is at least one validator set for block 0;
block 0 is less than any uint;
qed")
) {
Ok((block, set)) => {
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
Some(set)
},
Err(e) => {
debug!(target: "engine", "ValidatorSet could not be recovered: {}", e);
None
},
}
}
}
impl ValidatorSet for Multi {
fn contains(&self, bh: &H256, address: &Address) -> bool {
self.correct_set(bh).map_or(false, |set| set.contains(bh, address))
}
fn get(&self, bh: &H256, nonce: usize) -> Address {
self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce))
}
fn count(&self, bh: &H256) -> usize {
self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh))
}
fn report_malicious(&self, validator: &Address) {
for set in self.sets.values() {
set.report_malicious(validator);
}
}
fn report_benign(&self, validator: &Address) {
for set in self.sets.values() {
set.report_benign(validator);
}
}
fn register_contract(&self, client: Weak<Client>) {
for set in self.sets.values() {
set.register_contract(client.clone());
}
*self.block_number.write() = Box::new(move |hash| client
.upgrade()
.ok_or("No client!".into())
.and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into())));
}
}
#[cfg(test)]
mod tests {
use util::*;
use types::ids::BlockId;
use spec::Spec;
use account_provider::AccountProvider;
use client::{BlockChainClient, EngineClient};
use ethkey::Secret;
use miner::MinerService;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
#[test]
fn uses_current_set() {
::env_logger::init().unwrap();
let tap = Arc::new(AccountProvider::transient_provider());
let s0 = Secret::from_slice(&"0".sha3()).unwrap();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
client.engine().register_client(Arc::downgrade(&client));
// Make sure txs go through.
client.miner().set_gas_floor_target(1_000_000.into());
// Wrong signer for the first block.
client.miner().set_engine_signer(v1, "".into()).unwrap();
client.transact_contract(Default::default(), Default::default()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 0);
// Right signer for the first block.
client.miner().set_engine_signer(v0, "".into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 1);
// This time v0 is wrong.
client.transact_contract(Default::default(), Default::default()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 1);
client.miner().set_engine_signer(v1, "".into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 2);
// v1 is still good.
client.transact_contract(Default::default(), Default::default()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 3);
// Check syncing.
let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]);
sync_client.engine().register_client(Arc::downgrade(&sync_client));
for i in 1..4 {
sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap();
}
sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3);
}
}

View File

@@ -17,17 +17,23 @@
/// Validator set maintained in a contract, updated using `getValidators` method.
use std::sync::Weak;
use ethabi;
use util::*;
use util::cache::MemoryLruCache;
use types::ids::BlockId;
use client::{Client, BlockChainClient};
use client::chain_notify::ChainNotify;
use super::ValidatorSet;
use super::simple_list::SimpleList;
const MEMOIZE_CAPACITY: usize = 500;
const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]";
const GET_VALIDATORS: &'static str = "getValidators";
/// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorSafeContract {
pub address: Address,
validators: RwLock<SimpleList>,
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
provider: RwLock<Option<provider::Contract>>,
}
@@ -35,102 +41,127 @@ impl ValidatorSafeContract {
pub fn new(contract_address: Address) -> Self {
ValidatorSafeContract {
address: contract_address,
validators: Default::default(),
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
provider: RwLock::new(None),
}
}
/// Queries the state and updates the set of validators.
pub fn update(&self) {
/// Queries the state and gets the set of validators.
fn get_list(&self, block_hash: H256) -> Option<SimpleList> {
if let Some(ref provider) = *self.provider.read() {
match provider.get_validators() {
match provider.get_validators(BlockId::Hash(block_hash)) {
Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new);
*self.validators.write() = SimpleList::new(new);
Some(SimpleList::new(new))
},
Err(s) => {
debug!(target: "engine", "Set of validators could not be updated: {}", s);
None
},
Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s),
}
} else {
warn!(target: "engine", "Set of validators could not be updated: no provider contract.")
warn!(target: "engine", "Set of validators could not be updated: no provider contract.");
None
}
}
}
/// Checks validators on every block.
impl ChainNotify for ValidatorSafeContract {
fn new_blocks(
&self,
_: Vec<H256>,
_: Vec<H256>,
enacted: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<Bytes>,
_duration: u64) {
if !enacted.is_empty() {
self.update();
}
}
}
impl ValidatorSet for Arc<ValidatorSafeContract> {
fn contains(&self, address: &Address) -> bool {
self.validators.read().contains(address)
impl ValidatorSet for ValidatorSafeContract {
fn contains(&self, block_hash: &H256, address: &Address) -> bool {
let mut guard = self.validators.write();
let maybe_existing = guard
.get_mut(block_hash)
.map(|list| list.contains(block_hash, address));
maybe_existing
.unwrap_or_else(|| self
.get_list(block_hash.clone())
.map_or(false, |list| {
let contains = list.contains(block_hash, address);
guard.insert(block_hash.clone(), list);
contains
}))
}
fn get(&self, nonce: usize) -> Address {
self.validators.read().get(nonce)
fn get(&self, block_hash: &H256, nonce: usize) -> Address {
let mut guard = self.validators.write();
let maybe_existing = guard
.get_mut(block_hash)
.map(|list| list.get(block_hash, nonce));
maybe_existing
.unwrap_or_else(|| self
.get_list(block_hash.clone())
.map_or_else(Default::default, |list| {
let address = list.get(block_hash, nonce);
guard.insert(block_hash.clone(), list);
address
}))
}
fn count(&self) -> usize {
self.validators.read().count()
fn count(&self, block_hash: &H256) -> usize {
let mut guard = self.validators.write();
let maybe_existing = guard
.get_mut(block_hash)
.map(|list| list.count(block_hash));
maybe_existing
.unwrap_or_else(|| self
.get_list(block_hash.clone())
.map_or_else(usize::max_value, |list| {
let address = list.count(block_hash);
guard.insert(block_hash.clone(), list);
address
}))
}
fn register_contract(&self, client: Weak<Client>) {
if let Some(c) = client.upgrade() {
c.add_notify(self.clone());
}
{
*self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))));
}
self.update();
trace!(target: "engine", "Setting up contract caller.");
let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed"));
let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed");
let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed");
let contract_address = self.address.clone();
let do_call = move |id| client
.upgrade()
.ok_or("No client!".into())
.and_then(|c| c.call_contract(id, contract_address.clone(), data.clone()))
.map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed"));
*self.provider.write() = Some(provider::Contract::new(do_call));
}
}
mod provider {
// Autogenerated from JSON contract definition using Rust contract convertor.
#![allow(unused_imports)]
use std::string::String;
use std::result::Result;
use std::fmt;
use {util, ethabi};
use util::{FixedHash, Uint};
use types::ids::BlockId;
pub struct Contract {
contract: ethabi::Contract,
address: util::Address,
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
do_call: Box<Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static>,
}
impl Contract {
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
pub fn new<F>(do_call: F) -> Self where F: Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static {
Contract {
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
address: address,
do_call: Box::new(do_call),
}
}
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
#[allow(dead_code)]
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
let data = call.encode_call(
vec![]
).map_err(Self::as_string)?;
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
let mut result = output.into_iter().rev().collect::<Vec<_>>();
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
/// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
pub fn get_validators(&self, id: BlockId) -> Result<Vec<util::Address>, String> {
Ok((self.do_call)(id)?
.into_iter()
.rev()
.collect::<Vec<_>>()
.pop()
.expect("get_validators returns one argument; qed")
.to_array()
.and_then(|v| v
.into_iter()
.map(|a| a.to_address())
.collect::<Option<Vec<[u8; 20]>>>())
.expect("get_validators returns a list of addresses; qed")
.into_iter()
.map(util::Address::from)
.collect::<Vec<_>>()
)
}
}
}
@@ -138,13 +169,14 @@ mod provider {
#[cfg(test)]
mod tests {
use util::*;
use types::ids::BlockId;
use spec::Spec;
use account_provider::AccountProvider;
use transaction::{Transaction, Action};
use client::{BlockChainClient, EngineClient};
use ethkey::Secret;
use miner::MinerService;
use tests::helpers::generate_dummy_client_with_spec_and_accounts;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use super::super::ValidatorSet;
use super::ValidatorSafeContract;
@@ -153,12 +185,13 @@ mod tests {
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_contract(Arc::downgrade(&client));
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
let last_hash = client.best_block_header().hash();
assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
}
#[test]
fn updates_validators() {
fn knows_validators() {
let tap = Arc::new(AccountProvider::transient_provider());
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
@@ -212,5 +245,14 @@ mod tests {
client.update_sealing();
// Able to seal again.
assert_eq!(client.chain_info().best_block_number, 3);
// Check syncing.
let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]);
sync_client.engine().register_client(Arc::downgrade(&sync_client));
for i in 1..4 {
sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap();
}
sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3);
}
}

View File

@@ -16,7 +16,7 @@
/// Preconfigured validator list.
use util::Address;
use util::{H256, Address, HeapSizeOf};
use super::ValidatorSet;
#[derive(Debug, PartialEq, Eq, Default)]
@@ -34,16 +34,22 @@ impl SimpleList {
}
}
impl HeapSizeOf for SimpleList {
fn heap_size_of_children(&self) -> usize {
self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children()
}
}
impl ValidatorSet for SimpleList {
fn contains(&self, address: &Address) -> bool {
fn contains(&self, _bh: &H256, address: &Address) -> bool {
self.validators.contains(address)
}
fn get(&self, nonce: usize) -> Address {
fn get(&self, _bh: &H256, nonce: usize) -> Address {
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
}
fn count(&self) -> usize {
fn count(&self, _bh: &H256) -> usize {
self.validator_n
}
}
@@ -60,9 +66,9 @@ mod tests {
let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
let list = SimpleList::new(vec![a1.clone(), a2.clone()]);
assert!(list.contains(&a1));
assert_eq!(list.get(0), a1);
assert_eq!(list.get(1), a2);
assert_eq!(list.get(2), a1);
assert!(list.contains(&Default::default(), &a1));
assert_eq!(list.get(&Default::default(), 0), a1);
assert_eq!(list.get(&Default::default(), 1), a2);
assert_eq!(list.get(&Default::default(), 2), a1);
}
}

View File

@@ -107,6 +107,7 @@ extern crate ethcore_stratum;
extern crate ethabi;
extern crate hardware_wallet;
extern crate stats;
extern crate itertools;
#[macro_use]
extern crate log;

View File

@@ -230,7 +230,8 @@ pub struct Miner {
impl Miner {
/// Push notifier that will handle new jobs
pub fn push_notifier(&self, notifier: Box<NotifyWork>) {
self.notifiers.write().push(notifier)
self.notifiers.write().push(notifier);
self.sealing_work.lock().enabled = true;
}
/// Creates new instance of miner Arc.
@@ -298,7 +299,7 @@ impl Miner {
}
fn forced_sealing(&self) -> bool {
self.options.force_sealing || !self.options.new_work_notify.is_empty()
self.options.force_sealing || !self.notifiers.read().is_empty()
}
/// Clear all pending block states
@@ -1007,6 +1008,16 @@ impl MinerService for Miner {
}
}
fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option<PendingTransaction> {
let mut queue = self.transaction_queue.lock();
let tx = queue.find(hash);
if tx.is_some() {
let fetch_nonce = |a: &Address| chain.latest_nonce(a);
queue.remove_invalid(hash, &fetch_nonce);
}
tx
}
fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> {
self.from_pending_block(
best_block,

View File

@@ -150,6 +150,10 @@ pub trait MinerService : Send + Sync {
/// Query pending transactions for hash.
fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option<PendingTransaction>;
/// Removes transaction from the queue.
/// NOTE: The transaction is not removed from pending block if mining.
fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option<PendingTransaction>;
/// Get a list of all pending transactions in the queue.
fn pending_transactions(&self) -> Vec<PendingTransaction>;

View File

@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use types::ids::BlockId;
use client::MiningBlockChainClient;
use transaction::SignedTransaction;
use util::{U256, Uint, Mutex};
@@ -45,7 +46,7 @@ impl ServiceTransactionChecker {
debug_assert_eq!(tx.gas_price, U256::zero());
if let Some(ref contract) = *self.contract.lock() {
let do_call = |a, d| client.call_contract(a, d);
let do_call = |a, d| client.call_contract(BlockId::Latest, a, d);
contract.certified(&do_call, &tx.sender())
} else {
Err("contract is not configured".to_owned())

View File

@@ -211,6 +211,8 @@ impl From<AddrParseError> for Error {
impl super::work_notify::NotifyWork for Stratum {
fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) {
trace!(target: "stratum", "Notify work");
self.service.push_work_all(
self.dispatcher.payload(pow_hash, difficulty, number)
).unwrap_or_else(

View File

@@ -23,6 +23,7 @@ use snapshot::Error;
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP};
use util::trie::{TrieDB, Trie};
use rlp::{RlpStream, Stream, UntrustedRlp, View};
use itertools::Itertools;
use std::collections::HashSet;
@@ -60,55 +61,53 @@ impl CodeState {
}
}
// walk the account's storage trie, returning an RLP item containing the
// account properties and the storage.
pub fn to_fat_rlp(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
// walk the account's storage trie, returning a vector of RLP items containing the
// account properties and the storage. Each item contains at most `max_storage_items`
// storage records split according to snapshot format definition.
pub fn to_fat_rlps(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashSet<H256>, max_storage_items: usize) -> Result<Vec<Bytes>, Error> {
if acc == &ACC_EMPTY {
return Ok(::rlp::NULL_RLP.to_vec());
return Ok(vec![::rlp::NULL_RLP.to_vec()]);
}
let db = TrieDB::new(acct_db, &acc.storage_root)?;
let mut pairs = Vec::new();
let chunks = db.iter()?.chunks(max_storage_items);
let pair_chunks = chunks.into_iter().map(|chunk| chunk.collect());
pair_chunks.pad_using(1, |_| Vec::new(), ).map(|pairs| {
let mut stream = RlpStream::new_list(pairs.len());
for item in db.iter()? {
let (k, v) = item?;
pairs.push((k, v));
}
for r in pairs {
let (k, v) = r?;
stream.begin_list(2).append(&k).append(&&*v);
}
let mut stream = RlpStream::new_list(pairs.len());
let pairs_rlp = stream.out();
for (k, v) in pairs {
stream.begin_list(2).append(&k).append(&&*v);
}
let mut account_stream = RlpStream::new_list(5);
account_stream.append(&acc.nonce)
.append(&acc.balance);
let pairs_rlp = stream.out();
let mut account_stream = RlpStream::new_list(5);
account_stream.append(&acc.nonce)
.append(&acc.balance);
// [has_code, code_hash].
if acc.code_hash == SHA3_EMPTY {
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
} else if used_code.contains(&acc.code_hash) {
account_stream.append(&CodeState::Hash.raw()).append(&acc.code_hash);
} else {
match acct_db.get(&acc.code_hash) {
Some(c) => {
used_code.insert(acc.code_hash.clone());
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
}
None => {
warn!("code lookup failed during snapshot");
account_stream.append(&false).append_empty_data();
// [has_code, code_hash].
if acc.code_hash == SHA3_EMPTY {
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
} else if used_code.contains(&acc.code_hash) {
account_stream.append(&CodeState::Hash.raw()).append(&acc.code_hash);
} else {
match acct_db.get(&acc.code_hash) {
Some(c) => {
used_code.insert(acc.code_hash.clone());
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
}
None => {
warn!("code lookup failed during snapshot");
account_stream.append(&false).append_empty_data();
}
}
}
}
account_stream.append_raw(&pairs_rlp, 1);
Ok(account_stream.out())
account_stream.append_raw(&pairs_rlp, 1);
Ok(account_stream.out())
}).collect()
}
// decode a fat rlp, and rebuild the storage trie as we go.
@@ -117,6 +116,7 @@ pub fn to_fat_rlp(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashS
pub fn from_fat_rlp(
acct_db: &mut AccountDBMut,
rlp: UntrustedRlp,
mut storage_root: H256,
) -> Result<(BasicAccount, Option<Bytes>), Error> {
use util::{TrieDBMut, TrieMut};
@@ -148,10 +148,12 @@ pub fn from_fat_rlp(
}
};
let mut storage_root = H256::zero();
{
let mut storage_trie = TrieDBMut::new(acct_db, &mut storage_root);
let mut storage_trie = if storage_root.is_zero() {
TrieDBMut::new(acct_db, &mut storage_root)
} else {
TrieDBMut::from_existing(acct_db, &mut storage_root)?
};
let pairs = rlp.at(4)?;
for pair_rlp in pairs.iter() {
let k: Bytes = pair_rlp.val_at(0)?;
@@ -184,7 +186,7 @@ mod tests {
use std::collections::HashSet;
use super::{ACC_EMPTY, to_fat_rlp, from_fat_rlp};
use super::{ACC_EMPTY, to_fat_rlps, from_fat_rlp};
#[test]
fn encoding_basic() {
@@ -201,9 +203,9 @@ mod tests {
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlp);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
let fat_rlps = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), usize::max_value()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlps[0]);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, H256::zero()).unwrap().0, account);
}
#[test]
@@ -226,9 +228,40 @@ mod tests {
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlp);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
let fat_rlp = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), usize::max_value()).unwrap();
let fat_rlp = UntrustedRlp::new(&fat_rlp[0]);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, H256::zero()).unwrap().0, account);
}
#[test]
fn encoding_storage_split() {
let mut db = get_temp_state_db();
let addr = Address::random();
let account = {
let acct_db = AccountDBMut::new(db.as_hashdb_mut(), &addr);
let mut root = SHA3_NULL_RLP;
fill_storage(acct_db, &mut root, &mut H256::zero());
BasicAccount {
nonce: 25.into(),
balance: 987654321.into(),
storage_root: root,
code_hash: SHA3_EMPTY,
}
};
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
let fat_rlps = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), 100).unwrap();
let mut root = SHA3_NULL_RLP;
let mut restored_account = None;
for rlp in fat_rlps {
let fat_rlp = UntrustedRlp::new(&rlp);
restored_account = Some(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, root).unwrap().0);
root = restored_account.as_ref().unwrap().storage_root.clone();
}
assert_eq!(restored_account, Some(account));
}
#[test]
@@ -264,18 +297,18 @@ mod tests {
let mut used_code = HashSet::new();
let fat_rlp1 = to_fat_rlp(&account1, &AccountDB::new(db.as_hashdb(), &addr1), &mut used_code).unwrap();
let fat_rlp2 = to_fat_rlp(&account2, &AccountDB::new(db.as_hashdb(), &addr2), &mut used_code).unwrap();
let fat_rlp1 = to_fat_rlps(&account1, &AccountDB::new(db.as_hashdb(), &addr1), &mut used_code, usize::max_value()).unwrap();
let fat_rlp2 = to_fat_rlps(&account2, &AccountDB::new(db.as_hashdb(), &addr2), &mut used_code, usize::max_value()).unwrap();
assert_eq!(used_code.len(), 1);
let fat_rlp1 = UntrustedRlp::new(&fat_rlp1);
let fat_rlp2 = UntrustedRlp::new(&fat_rlp2);
let fat_rlp1 = UntrustedRlp::new(&fat_rlp1[0]);
let fat_rlp2 = UntrustedRlp::new(&fat_rlp2[0]);
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2).unwrap();
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2, H256::zero()).unwrap();
assert!(maybe_code.is_none());
assert_eq!(acc, account2);
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1).unwrap();
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1, H256::zero()).unwrap();
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
assert_eq!(acc, account1);
}
@@ -285,7 +318,7 @@ mod tests {
let mut db = get_temp_state_db();
let mut used_code = HashSet::new();
assert_eq!(to_fat_rlp(&ACC_EMPTY, &AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP)).unwrap(), (ACC_EMPTY, None));
assert_eq!(to_fat_rlps(&ACC_EMPTY, &AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code, usize::max_value()).unwrap(), vec![::rlp::NULL_RLP.to_vec()]);
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP), H256::zero()).unwrap(), (ACC_EMPTY, None));
}
}

View File

@@ -53,6 +53,8 @@ pub enum Error {
Decoder(DecoderError),
/// Io error.
Io(::std::io::Error),
/// Snapshot version is not supported.
VersionNotSupported(u64),
}
impl fmt::Display for Error {
@@ -73,6 +75,7 @@ impl fmt::Display for Error {
Error::Io(ref err) => err.fmt(f),
Error::Decoder(ref err) => err.fmt(f),
Error::Trie(ref err) => err.fmt(f),
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
}
}
}

View File

@@ -31,6 +31,8 @@ use rlp::{self, Encodable, RlpStream, UntrustedRlp, Stream, View};
use super::ManifestData;
const SNAPSHOT_VERSION: u64 = 2;
/// Something which can write snapshots.
/// Writing the same chunk multiple times will lead to implementation-defined
/// behavior, and is not advised.
@@ -120,8 +122,9 @@ impl SnapshotWriter for PackedWriter {
fn finish(mut self, manifest: ManifestData) -> io::Result<()> {
// we ignore the hashes fields of the manifest under the assumption that
// they are consistent with ours.
let mut stream = RlpStream::new_list(5);
let mut stream = RlpStream::new_list(6);
stream
.append(&SNAPSHOT_VERSION)
.append(&self.state_hashes)
.append(&self.block_hashes)
.append(&manifest.state_root)
@@ -223,7 +226,7 @@ impl PackedReader {
/// Create a new `PackedReader` for the file at the given path.
/// This will fail if any io errors are encountered or the file
/// is not a valid packed snapshot.
pub fn new(path: &Path) -> Result<Option<Self>, ::error::Error> {
pub fn new(path: &Path) -> Result<Option<Self>, ::snapshot::error::Error> {
let mut file = File::open(path)?;
let file_len = file.metadata()?.len();
if file_len < 8 {
@@ -257,15 +260,26 @@ impl PackedReader {
let rlp = UntrustedRlp::new(&manifest_buf);
let state: Vec<ChunkInfo> = rlp.val_at(0)?;
let blocks: Vec<ChunkInfo> = rlp.val_at(1)?;
let (start, version) = if rlp.item_count() == 5 {
(0, 1)
} else {
(1, rlp.val_at(0)?)
};
if version > SNAPSHOT_VERSION {
return Err(::snapshot::error::Error::VersionNotSupported(version));
}
let state: Vec<ChunkInfo> = rlp.val_at(0 + start)?;
let blocks: Vec<ChunkInfo> = rlp.val_at(1 + start)?;
let manifest = ManifestData {
version: version,
state_hashes: state.iter().map(|c| c.0).collect(),
block_hashes: blocks.iter().map(|c| c.0).collect(),
state_root: rlp.val_at(2)?,
block_number: rlp.val_at(3)?,
block_hash: rlp.val_at(4)?,
state_root: rlp.val_at(2 + start)?,
block_number: rlp.val_at(3 + start)?,
block_hash: rlp.val_at(4 + start)?,
};
Ok(Some(PackedReader {
@@ -348,7 +362,7 @@ mod tests {
use util::sha3::Hashable;
use snapshot::ManifestData;
use super::{SnapshotWriter, SnapshotReader, PackedWriter, PackedReader, LooseWriter, LooseReader};
use super::{SnapshotWriter, SnapshotReader, PackedWriter, PackedReader, LooseWriter, LooseReader, SNAPSHOT_VERSION};
const STATE_CHUNKS: &'static [&'static [u8]] = &[b"dog", b"cat", b"hello world", b"hi", b"notarealchunk"];
const BLOCK_CHUNKS: &'static [&'static [u8]] = &[b"hello!", b"goodbye!", b"abcdefg", b"hijklmnop", b"qrstuvwxy", b"and", b"z"];
@@ -374,6 +388,7 @@ mod tests {
}
let manifest = ManifestData {
version: SNAPSHOT_VERSION,
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: b"notarealroot".sha3(),
@@ -412,6 +427,7 @@ mod tests {
}
let manifest = ManifestData {
version: SNAPSHOT_VERSION,
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: b"notarealroot".sha3(),
@@ -428,4 +444,4 @@ mod tests {
reader.chunk(hash.clone()).unwrap();
}
}
}
}

View File

@@ -56,6 +56,7 @@ pub use self::traits::SnapshotService;
pub use self::watcher::Watcher;
pub use types::snapshot_manifest::ManifestData;
pub use types::restoration_status::RestorationStatus;
pub use types::basic_account::BasicAccount;
pub mod io;
pub mod service;
@@ -82,6 +83,9 @@ mod traits {
// Try to have chunks be around 4MB (before compression)
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
// Try to have chunks be around 4MB (before compression)
const MAX_STORAGE_ENTRIES_PER_ACCOUNT_RECORD: usize = 80_000;
// How many blocks to include in a snapshot, starting from the head of the chain.
const SNAPSHOT_BLOCKS: u64 = 30000;
@@ -147,6 +151,7 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
info!("produced {} state chunks and {} block chunks.", state_hashes.len(), block_hashes.len());
let manifest_data = ManifestData {
version: 2,
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: *state_root,
@@ -300,14 +305,14 @@ impl<'a> StateChunker<'a> {
//
// If the buffer is greater than the desired chunk size,
// this will write out the data to disk.
fn push(&mut self, account_hash: Bytes, data: Bytes) -> Result<(), Error> {
fn push(&mut self, account_hash: Bytes, data: Bytes, force_chunk: bool) -> Result<(), Error> {
let pair = {
let mut stream = RlpStream::new_list(2);
stream.append(&account_hash).append_raw(&data, 1);
stream.out()
};
if self.cur_size + pair.len() >= PREFERRED_CHUNK_SIZE {
if force_chunk || self.cur_size + pair.len() >= PREFERRED_CHUNK_SIZE {
self.write_chunk()?;
}
@@ -372,8 +377,10 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex<SnapshotWriter +
let account_db = AccountDB::from_hash(db, account_key_hash);
let fat_rlp = account::to_fat_rlp(&account, &account_db, &mut used_code)?;
chunker.push(account_key, fat_rlp)?;
let fat_rlps = account::to_fat_rlps(&account, &account_db, &mut used_code, MAX_STORAGE_ENTRIES_PER_ACCOUNT_RECORD)?;
for (i, fat_rlp) in fat_rlps.into_iter().enumerate() {
chunker.push(account_key.clone(), fat_rlp, i > 0)?;
}
}
if chunker.cur_size != 0 {
@@ -390,6 +397,7 @@ pub struct StateRebuilder {
known_code: HashMap<H256, H256>, // code hashes mapped to first account with this code.
missing_code: HashMap<H256, Vec<H256>>, // maps code hashes to lists of accounts missing that code.
bloom: Bloom,
known_storage_roots: HashMap<H256, H256>, // maps account hashes to last known storage root. Only filled for last account per chunk.
}
impl StateRebuilder {
@@ -401,6 +409,7 @@ impl StateRebuilder {
known_code: HashMap::new(),
missing_code: HashMap::new(),
bloom: StateDB::load_bloom(&*db),
known_storage_roots: HashMap::new(),
}
}
@@ -418,6 +427,7 @@ impl StateRebuilder {
rlp,
&mut pairs,
&self.known_code,
&mut self.known_storage_roots,
flag
)?;
@@ -464,14 +474,18 @@ impl StateRebuilder {
Ok(())
}
/// Check for accounts missing code. Once all chunks have been fed, there should
/// be none.
pub fn check_missing(self) -> Result<(), Error> {
/// Finalize the restoration. Check for accounts missing code and make a dummy
/// journal entry.
/// Once all chunks have been fed, there should be nothing missing.
pub fn finalize(mut self, era: u64, id: H256) -> Result<(), ::error::Error> {
let missing = self.missing_code.keys().cloned().collect::<Vec<_>>();
match missing.is_empty() {
true => Ok(()),
false => Err(Error::MissingCode(missing)),
}
if !missing.is_empty() { return Err(Error::MissingCode(missing).into()) }
let mut batch = self.db.backing().transaction();
self.db.journal_under(&mut batch, era, &id)?;
self.db.backing().write_buffered(batch);
Ok(())
}
/// Get the state root of the rebuilder.
@@ -492,10 +506,11 @@ fn rebuild_accounts(
account_fat_rlps: UntrustedRlp,
out_chunk: &mut [(H256, Bytes)],
known_code: &HashMap<H256, H256>,
known_storage_roots: &mut HashMap<H256, H256>,
abort_flag: &AtomicBool,
) -> Result<RebuiltStatus, ::error::Error> {
let mut status = RebuiltStatus::default();
for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk) {
for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk.iter_mut()) {
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
let hash: H256 = account_rlp.val_at(0)?;
@@ -506,7 +521,8 @@ fn rebuild_accounts(
// fill out the storage trie and code while decoding.
let (acc, maybe_code) = {
let mut acct_db = AccountDBMut::from_hash(db, hash);
account::from_fat_rlp(&mut acct_db, fat_rlp)?
let storage_root = known_storage_roots.get(&hash).cloned().unwrap_or(H256::zero());
account::from_fat_rlp(&mut acct_db, fat_rlp, storage_root)?
};
let code_hash = acc.code_hash.clone();
@@ -538,6 +554,12 @@ fn rebuild_accounts(
*out = (hash, thin_rlp);
}
if let Some(&(ref hash, ref rlp)) = out_chunk.iter().last() {
known_storage_roots.insert(*hash, ::rlp::decode::<BasicAccount>(rlp).storage_root);
}
if let Some(&(ref hash, ref rlp)) = out_chunk.iter().next() {
known_storage_roots.insert(*hash, ::rlp::decode::<BasicAccount>(rlp).storage_root);
}
Ok(status)
}

View File

@@ -166,7 +166,7 @@ impl Restoration {
}
// check for missing code.
self.state.check_missing()?;
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
// connect out-of-order chunks and verify chain integrity.
self.blocks.finalize(self.canonical_hashes)?;
@@ -656,6 +656,7 @@ mod tests {
assert_eq!(service.status(), RestorationStatus::Inactive);
let manifest = ManifestData {
version: 2,
state_hashes: vec![],
block_hashes: vec![],
state_root: Default::default(),

View File

@@ -63,6 +63,7 @@ fn chunk_and_restore(amount: u64) {
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap();
let manifest = ::snapshot::ManifestData {
version: 2,
state_hashes: Vec::new(),
block_hashes: block_hashes,
state_root: ::util::sha3::SHA3_NULL_RLP,
@@ -125,6 +126,7 @@ fn checks_flag() {
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
let manifest = ::snapshot::ManifestData {
version: 2,
state_hashes: Vec::new(),
block_hashes: Vec::new(),
state_root: ::util::sha3::SHA3_NULL_RLP,

View File

@@ -27,6 +27,7 @@ use super::ManifestData;
#[test]
fn manifest_rlp() {
let manifest = ManifestData {
version: 2,
block_hashes: Vec::new(),
state_hashes: Vec::new(),
block_number: 1234567,
@@ -35,4 +36,4 @@ fn manifest_rlp() {
};
let raw = manifest.clone().into_rlp();
assert_eq!(ManifestData::from_rlp(&raw).unwrap(), manifest);
}
}

View File

@@ -122,6 +122,7 @@ fn guards_delete_folders() {
path.push("restoration");
let manifest = ManifestData {
version: 2,
state_hashes: vec![],
block_hashes: vec![],
block_number: 0,

View File

@@ -58,10 +58,11 @@ fn snap_and_restore() {
let state_hashes = chunk_state(&old_db, &state_root, &writer, &Progress::default()).unwrap();
writer.into_inner().finish(::snapshot::ManifestData {
version: 2,
state_hashes: state_hashes,
block_hashes: Vec::new(),
state_root: state_root,
block_number: 0,
block_number: 1000,
block_hash: H256::default(),
}).unwrap();
@@ -69,7 +70,7 @@ fn snap_and_restore() {
db_path.push("db");
let db = {
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
let reader = PackedReader::new(&snap_file).unwrap().unwrap();
let flag = AtomicBool::new(true);
@@ -82,12 +83,13 @@ fn snap_and_restore() {
}
assert_eq!(rebuilder.state_root(), state_root);
rebuilder.check_missing().unwrap();
rebuilder.finalize(1000, H256::default()).unwrap();
new_db
};
let new_db = journaldb::new(db, Algorithm::Archive, ::db::COL_STATE);
let new_db = journaldb::new(db, Algorithm::OverlayRecent, ::db::COL_STATE);
assert_eq!(new_db.earliest_era(), Some(1000));
compare_dbs(&old_db, new_db.as_hashdb());
}
@@ -120,10 +122,10 @@ fn get_code_from_prev_chunk() {
let mut db = MemoryDB::new();
AccountDBMut::from_hash(&mut db, hash).insert(&code[..]);
let fat_rlp = account::to_fat_rlp(&acc, &AccountDB::from_hash(&db, hash), &mut used_code).unwrap();
let fat_rlp = account::to_fat_rlps(&acc, &AccountDB::from_hash(&db, hash), &mut used_code, usize::max_value()).unwrap();
let mut stream = RlpStream::new_list(1);
stream.begin_list(2).append(&hash).append_raw(&fat_rlp, 1);
stream.begin_list(2).append(&hash).append_raw(&fat_rlp[0], 1);
stream.out()
};
@@ -134,13 +136,18 @@ fn get_code_from_prev_chunk() {
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
let mut rebuilder = StateRebuilder::new(new_db, Algorithm::Archive);
let flag = AtomicBool::new(true);
{
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
let flag = AtomicBool::new(true);
rebuilder.feed(&chunk1, &flag).unwrap();
rebuilder.feed(&chunk2, &flag).unwrap();
rebuilder.feed(&chunk1, &flag).unwrap();
rebuilder.feed(&chunk2, &flag).unwrap();
rebuilder.check_missing().unwrap();
rebuilder.finalize(1000, H256::random()).unwrap();
}
let state_db = journaldb::new(new_db, Algorithm::OverlayRecent, ::db::COL_STATE);
assert_eq!(state_db.earliest_era(), Some(1000));
}
#[test]
@@ -164,6 +171,7 @@ fn checks_flag() {
let state_hashes = chunk_state(&old_db, &state_root, &writer, &Progress::default()).unwrap();
writer.into_inner().finish(::snapshot::ManifestData {
version: 2,
state_hashes: state_hashes,
block_hashes: Vec::new(),
state_root: state_root,
@@ -175,7 +183,7 @@ fn checks_flag() {
db_path.push("db");
{
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
let reader = PackedReader::new(&snap_file).unwrap().unwrap();
let flag = AtomicBool::new(false);

View File

@@ -55,6 +55,8 @@ pub struct CommonParams {
pub fork_block: Option<(BlockNumber, H256)>,
/// Number of first block where EIP-98 rules begin.
pub eip98_transition: BlockNumber,
/// Validate block receipts root.
pub validate_receipts_transition: u64,
}
impl From<ethjson::spec::Params> for CommonParams {
@@ -68,6 +70,7 @@ impl From<ethjson::spec::Params> for CommonParams {
min_gas_limit: p.min_gas_limit.into(),
fork_block: if let (Some(n), Some(h)) = (p.fork_block, p.fork_hash) { Some((n.into(), h.into())) } else { None },
eip98_transition: p.eip98_transition.map_or(0, Into::into),
validate_receipts_transition: p.validate_receipts_transition.map_or(0, Into::into),
}
}
}
@@ -357,6 +360,10 @@ impl Spec {
/// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf".
/// Validator can be removed with `reportMalicious`.
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
/// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height.
/// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards.
pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") }
}
#[cfg(test)]

View File

@@ -546,7 +546,7 @@ impl<B: Backend> State<B> {
// TODO uncomment once to_pod() works correctly.
// trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod()));
let state_root = if env_info.number < engine.params().eip98_transition {
let state_root = if env_info.number < engine.params().eip98_transition || env_info.number < engine.params().validate_receipts_transition {
self.commit()?;
Some(self.root().clone())
} else {

View File

@@ -24,6 +24,8 @@ use util::Bytes;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "ipc", binary)]
pub struct ManifestData {
/// Snapshot format version.
pub version: u64,
/// List of state chunk hashes.
pub state_hashes: Vec<H256>,
/// List of block chunk hashes.
@@ -39,7 +41,8 @@ pub struct ManifestData {
impl ManifestData {
/// Encode the manifest data to rlp.
pub fn into_rlp(self) -> Bytes {
let mut stream = RlpStream::new_list(5);
let mut stream = RlpStream::new_list(6);
stream.append(&self.version);
stream.append(&self.state_hashes);
stream.append(&self.block_hashes);
stream.append(&self.state_root);
@@ -52,14 +55,20 @@ impl ManifestData {
/// Try to restore manifest data from raw bytes, interpreted as RLP.
pub fn from_rlp(raw: &[u8]) -> Result<Self, DecoderError> {
let decoder = UntrustedRlp::new(raw);
let (start, version) = if decoder.item_count() == 5 {
(0, 1)
} else {
(1, decoder.val_at(0)?)
};
let state_hashes: Vec<H256> = decoder.val_at(0)?;
let block_hashes: Vec<H256> = decoder.val_at(1)?;
let state_root: H256 = decoder.val_at(2)?;
let block_number: u64 = decoder.val_at(3)?;
let block_hash: H256 = decoder.val_at(4)?;
let state_hashes: Vec<H256> = decoder.val_at(start + 0)?;
let block_hashes: Vec<H256> = decoder.val_at(start + 1)?;
let state_root: H256 = decoder.val_at(start + 2)?;
let block_number: u64 = decoder.val_at(start + 3)?;
let block_hash: H256 = decoder.val_at(start + 4)?;
Ok(ManifestData {
version: version,
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: state_root,

View File

@@ -57,8 +57,8 @@ mod tests {
#[test]
fn test_brain() {
let words = "this is sparta!".to_owned();
let first_keypair = Brain(words.clone()).generate().unwrap();
let second_keypair = Brain(words.clone()).generate().unwrap();
let first_keypair = Brain::new(words.clone()).generate().unwrap();
let second_keypair = Brain::new(words.clone()).generate().unwrap();
assert_eq!(first_keypair.secret(), second_keypair.secret());
}
}

View File

@@ -5,11 +5,11 @@ version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[lib]
name = "evm"
name = "evmbin"
path = "./src/main.rs"
[[bin]]
name = "evm"
name = "parity-evm"
path = "./src/main.rs"
[dependencies]

View File

@@ -271,7 +271,7 @@ impl Manager {
chunk_size += size;
}
trace!("writing {:?}", &hid_chunk[..]);
let n = handle.write(&hid_chunk[0..chunk_size])?;
let n = handle.write(&hid_chunk[..])?;
if n < chunk_size {
return Err(Error::Protocol("Write data size mismatch"));
}

View File

@@ -14,13 +14,13 @@ nightly-testing = ["clippy"]
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
[build-dependencies]
quasi_codegen = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
quasi_codegen = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
[dependencies]
aster = { version = "0.17", default-features = false }
aster = { version = "0.41", default-features = false }
clippy = { version = "^0.*", optional = true }
quasi = { version = "0.11", default-features = false }
quasi_macros = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
syntex_syntax = { version = "0.33", optional = true }
quasi = { version = "0.32", default-features = false }
quasi_macros = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
syntex_syntax = { version = "0.58", optional = true }

View File

@@ -25,13 +25,11 @@ mod inner {
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let mut registry = syntex::Registry::new();
quasi_codegen::register(&mut registry);
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.rs");
registry.expand("", &src, &dst).unwrap();
quasi_codegen::expand(&src, &dst).unwrap();
}
}

View File

@@ -56,7 +56,7 @@ pub fn expand_ipc_implementation(
let builder = aster::AstBuilder::new().span(span);
let interface_map = match implement_interface(cx, &builder, &item, push) {
let interface_map = match implement_interface(cx, &builder, &item, push, meta_item) {
Ok(interface_map) => interface_map,
Err(Error) => { return; },
};
@@ -99,9 +99,9 @@ fn push_invoke_signature_aster(
let inputs = &named_signature.sig.decl.inputs;
let (input_type_name, input_arg_names, input_arg_tys) = if inputs.len() > 0 {
let first_field_name = field_name(builder, &inputs[0]).name.as_str();
if first_field_name == "self" && inputs.len() == 1 { (None, vec![], vec![]) }
if &*first_field_name == "self" && inputs.len() == 1 { (None, vec![], vec![]) }
else {
let skip = if first_field_name == "self" { 2 } else { 1 };
let skip = if &*first_field_name == "self" { 2 } else { 1 };
let name_str = format!("{}_input", named_signature.ident.name.as_str());
let mut arg_names = Vec::new();
@@ -181,6 +181,7 @@ fn implement_dispatch_arm_invoke_stmt(
dispatch: &Dispatch,
) -> ast::Stmt
{
use ::syntax::tokenstream::TokenTree::Token;
let function_name = builder.id(dispatch.function_name.as_str());
let input_args_exprs = dispatch.input_arg_names.iter().enumerate().map(|(arg_index, arg_name)| {
@@ -193,56 +194,56 @@ fn implement_dispatch_arm_invoke_stmt(
let ext_cx = &*cx;
::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
if dispatch.return_type_ty.is_some() {
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::ModSep));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("ipc"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::ModSep));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("binary"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::ModSep));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("serialize"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
tt.push(Token(_sp, ::syntax::parse::token::ModSep));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("ipc"))));
tt.push(Token(_sp, ::syntax::parse::token::ModSep));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("binary"))));
tt.push(Token(_sp, ::syntax::parse::token::ModSep));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("serialize"))));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Dot));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self"))));
tt.push(Token(_sp, ::syntax::parse::token::Dot));
tt.extend(::quasi::ToTokens::to_tokens(&function_name, ext_cx).into_iter());
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
for arg_expr in input_args_exprs {
tt.extend(::quasi::ToTokens::to_tokens(&arg_expr, ext_cx).into_iter());
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma));
tt.push(Token(_sp, ::syntax::parse::token::Comma));
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
if dispatch.return_type_ty.is_some() {
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Dot));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("unwrap"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::Dot));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("unwrap"))));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
}
else {
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Semi));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Vec"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::ModSep));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("new"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::Semi));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Vec"))));
tt.push(Token(_sp, ::syntax::parse::token::ModSep));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("new"))));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt
})).unwrap()
}
)).unwrap()
}
fn implement_dispatch_arm_invoke(
@@ -344,6 +345,8 @@ fn implement_client_method_body(
interface_map: &InterfaceMap,
) -> P<ast::Expr>
{
use ::syntax::tokenstream::TokenTree::Token;
let dispatch = &interface_map.dispatches[index as usize];
let index_ident = builder.id(format!("{}", index + RESERVED_MESSAGE_IDS).as_str());
@@ -391,26 +394,25 @@ fn implement_client_method_body(
let ext_cx = &*cx;
::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("let"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("payload"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Eq));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Request"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("let"))));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("payload"))));
tt.push(Token(_sp, ::syntax::parse::token::Eq));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Request"))));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
for arg in dispatch.input_arg_names.iter() {
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Colon));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()))));
tt.push(Token(_sp, ::syntax::parse::token::Colon));
tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()))));
tt.push(Token(_sp, ::syntax::parse::token::Comma));
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt
}))
});
@@ -465,6 +467,8 @@ fn implement_client_method(
)
-> ast::ImplItem
{
use ::syntax::tokenstream::TokenTree::Token;
let dispatch = &interface_map.dispatches[index as usize];
let method_name = builder.id(dispatch.function_name.as_str());
let body = implement_client_method_body(cx, builder, index, interface_map);
@@ -476,36 +480,35 @@ fn implement_client_method(
let signature = ::syntax::parse::parser::Parser::parse_impl_item(
&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("fn"))));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("fn"))));
tt.extend(::quasi::ToTokens::to_tokens(&method_name, ext_cx).into_iter());
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self"))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And)));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self"))));
tt.push(Token(_sp, ::syntax::parse::token::Comma));
for arg_idx in 0..dispatch.input_arg_names.len() {
let arg_name = dispatch.input_arg_names[arg_idx].as_str();
let arg_ty = dispatch.input_arg_tys[arg_idx].clone();
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg_name))));
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Colon));
tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg_name))));
tt.push(Token(_sp, ::syntax::parse::token::Colon));
tt.extend(::quasi::ToTokens::to_tokens(&arg_ty, ext_cx).into_iter());
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma));
tt.push(Token(_sp, ::syntax::parse::token::Comma));
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren)));
if let Some(ref return_ty) = dispatch.return_type_ty {
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::RArrow));
tt.push(Token(_sp, ::syntax::parse::token::RArrow));
tt.extend(::quasi::ToTokens::to_tokens(return_ty, ext_cx).into_iter());
}
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace)));
tt.extend(::quasi::ToTokens::to_tokens(&body, ext_cx).into_iter());
tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace)));
tt
}));
@@ -526,7 +529,7 @@ fn client_generics(builder: &aster::AstBuilder, interface_map: &InterfaceMap) ->
fn client_qualified_ident(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> P<Ty> {
let generics = client_generics(builder, interface_map);
aster::ty::TyBuilder::new().path().segment(interface_map.ident_map.client_ident(cx, builder, &interface_map.original_item))
aster::ty::TyBuilder::new().path().segment(interface_map.ident_map.client_ident(cx, builder))
.with_generics(generics).build()
.build()
}
@@ -542,7 +545,7 @@ fn client_phantom_ident(builder: &aster::AstBuilder, interface_map: &InterfaceMa
/// for say `Service` it generates `ServiceClient`
fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: &InterfaceMap, push: &mut FnMut(Annotatable)) {
let generics = client_generics(builder, interface_map);
let client_short_ident = interface_map.ident_map.client_ident(cx, builder, &interface_map.original_item);
let client_short_ident = interface_map.ident_map.client_ident(cx, builder);
let phantom = client_phantom_ident(builder, interface_map);
let client_struct_item = quote_item!(cx,
@@ -575,7 +578,7 @@ fn push_with_socket_client_implementation(
let generics = client_generics(builder, interface_map);
let client_ident = client_qualified_ident(cx, builder, interface_map);
let where_clause = &generics.where_clause;
let client_short_ident = interface_map.ident_map.client_ident(cx, builder, &interface_map.original_item);
let client_short_ident = interface_map.ident_map.client_ident(cx, builder);
let implement = quote_item!(cx,
impl $generics ::ipc::WithSocket<S> for $client_ident $where_clause {
@@ -717,33 +720,31 @@ fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<String,
}
}
pub fn get_ipc_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
match attr.node.value.node {
ast::MetaItemKind::List(ref name, ref items) if name == &"ipc" => {
Some(items)
}
_ => None
}
}
fn client_ident_renamed(cx: &ExtCtxt, item: &ast::Item) -> Option<String> {
for meta_items in item.attrs().iter().filter_map(get_ipc_meta_items) {
for meta_item in meta_items {
match meta_item.node {
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"client_ident" => {
if let Ok(s) = get_str_from_lit(cx, name, lit) {
return Some(s);
fn client_ident_renamed(cx: &ExtCtxt, meta_item: &MetaItem) -> Option<String> {
if let ast::MetaItemKind::List(ref list) = meta_item.node {
for nested in list {
match nested.node {
ast::NestedMetaItemKind::MetaItem(ref meta_item) => {
let is_client_ident = &*meta_item.name.as_str() == "client_ident";
match meta_item.node {
ast::MetaItemKind::NameValue(ref lit) if is_client_ident => {
if let Ok(s) = get_str_from_lit(cx, "client_ident", lit) {
return Some(s);
}
}
_ => {
cx.span_err(
meta_item.span,
&format!("unknown client_ident container attribute `{}`",
::syntax::print::pprust::meta_item_to_string(&meta_item)));
}
}
}
_ => {
cx.span_err(
meta_item.span,
&format!("unknown client_ident container attribute `{}`",
::syntax::print::pprust::meta_item_to_string(meta_item)));
}
},
_ => {},
}
}
}
None
}
@@ -759,6 +760,7 @@ struct InterfaceMap {
struct IdentMap {
original_path: ast::Path,
meta_item: MetaItem,
}
impl IdentMap {
@@ -766,8 +768,8 @@ impl IdentMap {
builder.id(format!("{}", ::syntax::print::pprust::path_to_string(&self.original_path)))
}
fn client_ident(&self, cx: &ExtCtxt, builder: &aster::AstBuilder, item: &ast::Item) -> Ident {
if let Some(new_name) = client_ident_renamed(cx, item) {
fn client_ident(&self, cx: &ExtCtxt, builder: &aster::AstBuilder) -> Ident {
if let Some(new_name) = client_ident_renamed(cx, &self.meta_item) {
builder.id(new_name)
}
else {
@@ -776,12 +778,12 @@ impl IdentMap {
}
}
fn ty_ident_map(original_ty: &P<Ty>) -> IdentMap {
fn ty_ident_map(original_ty: &P<Ty>, meta_item: &MetaItem) -> IdentMap {
let original_path = match original_ty.node {
::syntax::ast::TyKind::Path(_, ref path) => path.clone(),
_ => { panic!("incompatible implementation"); }
};
let ident_map = IdentMap { original_path: original_path };
let ident_map = IdentMap { original_path: original_path, meta_item: meta_item.clone() };
ident_map
}
@@ -791,6 +793,7 @@ fn implement_interface(
builder: &aster::AstBuilder,
item: &Item,
push: &mut FnMut(Annotatable),
meta_item: &MetaItem,
) -> Result<InterfaceMap, Error> {
let (generics, impl_trait, original_ty, dispatch_table) = match item.node {
ast::ItemKind::Impl(_, _, ref generics, ref impl_trait, ref ty, ref impl_items) => {
@@ -844,7 +847,7 @@ fn implement_interface(
let (handshake_arm, handshake_arm_buf) = implement_handshake_arm(cx);
let ty = ty_ident_map(&original_ty).ident(builder);
let ty = ty_ident_map(&original_ty, meta_item).ident(builder);
let (interface_endpoint, host_generics) = match impl_trait {
Some(ref trait_) => (builder.id(::syntax::print::pprust::path_to_string(&trait_.path)), None),
None => (ty, Some(&impl_generics)),
@@ -884,7 +887,7 @@ fn implement_interface(
).unwrap();
Ok(InterfaceMap {
ident_map: ty_ident_map(&original_ty),
ident_map: ty_ident_map(&original_ty, meta_item),
original_item: item.clone(),
item: ipc_item,
dispatches: dispatch_table,

View File

@@ -66,9 +66,11 @@ struct StripAttributeFolder<'a> {
#[cfg(feature = "with-syntex")]
impl<'a> fold::Folder for StripAttributeFolder<'a> {
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
match attr.node.value.node {
ast::MetaItemKind::List(ref n, _) if n == self.attr_title => { return None; }
ast::MetaItemKind::Word(ref n) if n == self.attr_title => { return None; }
let is_self = &*attr.value.name.as_str() == self.attr_title;
match attr.value.node {
ast::MetaItemKind::List(_) if is_self => { return None; }
ast::MetaItemKind::Word if is_self => { return None; }
_ => {}
}

View File

@@ -151,7 +151,7 @@ fn binary_expr(
_ => {
cx.span_bug(item.span,
"expected ItemStruct or ItemEnum in #[derive(Binary)]");
Err(Error)
Err(Error) as Result<BinaryExpressions, Error>
},
}
}
@@ -184,13 +184,17 @@ fn binary_expr_struct(
let size_exprs: Vec<P<ast::Expr>> = fields.iter().enumerate().map(|(index, field)| {
let raw_ident = ::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty));
let index_ident = builder.id(format!("__field{}", index));
let field_id = match field.ident {
Some(ident) => builder.id(ident),
None => builder.id(format!("{}", index)),
};
match raw_ident.as_ref() {
"u8" => {
quote_expr!(cx, 1)
},
"[u8]" => {
value_ident.and_then(|x| {
let field_id = builder.id(field.ident.unwrap());
Some(quote_expr!(cx, $x. $field_id .len()))
})
.unwrap_or_else(|| {
@@ -207,7 +211,6 @@ fn binary_expr_struct(
value_ident.and_then(|x|
{
let field_id = builder.id(field.ident.unwrap());
Some(quote_expr!(cx,
match $field_type_ident_qualified::len_params() {
0 => ::std::mem::size_of::<$field_type_ident>(),
@@ -232,12 +235,12 @@ fn binary_expr_struct(
}
let mut write_stmts = Vec::<ast::Stmt>::new();
write_stmts.push(quote_stmt!(cx, let mut offset = 0usize;).unwrap());
write_stmts.push(quote_stmt!(cx, let mut offset = 0usize;).expect("stmt1"));
let mut map_stmts = Vec::<ast::Stmt>::new();
let field_amount = builder.id(&format!("{}",fields.len()));
map_stmts.push(quote_stmt!(cx, let mut map = vec![0usize; $field_amount];).unwrap());
map_stmts.push(quote_stmt!(cx, let mut total = 0usize;).unwrap());
map_stmts.push(quote_stmt!(cx, let mut map = vec![0usize; $field_amount];).expect("stmt2"));
map_stmts.push(quote_stmt!(cx, let mut total = 0usize;).expect("stmt3"));
let mut post_write_stmts = Vec::<ast::Stmt>::new();
@@ -248,9 +251,12 @@ fn binary_expr_struct(
let field_type_ident_qualified = builder.id(
replace_qualified(&::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty))));
let field_id = match field.ident {
Some(ident) => builder.id(ident),
None => builder.id(format!("{}", index)),
};
let member_expr = match value_ident {
Some(x) => {
let field_id = builder.id(field.ident.unwrap());
quote_expr!(cx, $x . $field_id)
},
None => {
@@ -267,8 +273,8 @@ fn binary_expr_struct(
match raw_ident.as_ref() {
"u8" => {
write_stmts.push(quote_stmt!(cx, let next_line = offset + 1;).unwrap());
write_stmts.push(quote_stmt!(cx, buffer[offset] = $member_expr; ).unwrap());
write_stmts.push(quote_stmt!(cx, let next_line = offset + 1;).expect("stmt4"));
write_stmts.push(quote_stmt!(cx, buffer[offset] = $member_expr; ).expect("stm5"));
},
"[u8]" => {
write_stmts.push(quote_stmt!(cx, let size = $member_expr .len();).unwrap());
@@ -374,7 +380,7 @@ fn binary_expr_item_struct(
cx.span_bug(span,
&format!("#[derive(Binary)] Unsupported struct content, expected tuple/struct, found: {:?}",
variant_data));
Err(Error)
Err(Error) as Result<BinaryExpressions, Error>
},
}
}
@@ -431,13 +437,12 @@ fn fields_sequence(
variant_ident: &ast::Ident,
) -> ast::Expr {
use syntax::parse::token;
use syntax::ast::TokenTree::Token;
use syntax::tokenstream::TokenTree::Token;
let named_members = fields.iter().any(|f| f.ident.is_some());
::quasi::parse_expr_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
@@ -569,11 +574,10 @@ fn named_fields_sequence(
fields: &[ast::StructField],
) -> ast::Stmt {
use syntax::parse::token;
use syntax::ast::TokenTree::Token;
use syntax::tokenstream::TokenTree::Token;
::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
@@ -590,7 +594,10 @@ fn named_fields_sequence(
tt.push(Token(_sp, token::OpenDelim(token::Brace)));
for (idx, field) in fields.iter().enumerate() {
tt.push(Token(_sp, token::Ident(field.ident.clone().expect("function is called for named fields"))));
tt.push(Token(_sp, match field.ident {
Some(ident) => token::Ident(ident),
None => token::Ident(ext_cx.ident_of(&format!("{}", idx))),
}));
tt.push(Token(_sp, token::Colon));
// special case for u8, it just takes byte form sequence

View File

@@ -9,7 +9,7 @@ build = "build.rs"
[dependencies]
ethcore-ipc = { path = "../rpc" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
ethcore-ipc-nano = { path = "../nano" }
semver = "0.5"
log = "0.3"

View File

@@ -8,6 +8,6 @@ license = "GPL-3.0"
[dependencies]
ethcore-ipc = { path = "../rpc" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
log = "0.3"
lazy_static = "0.2"

View File

@@ -8,6 +8,6 @@ license = "GPL-3.0"
[dependencies]
ethcore-devtools = { path = "../../devtools" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
ethcore-util = { path = "../../util" }
semver = "0.5"

View File

@@ -11,7 +11,7 @@ path = "run.rs"
ethcore-ipc = { path = "../rpc" }
ethcore-devtools = { path = "../../devtools" }
semver = "0.5"
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
ethcore-ipc-nano = { path = "../nano" }
ethcore-util = { path = "../../util" }
log = "0.3"

View File

@@ -8,7 +8,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
ethcore = { path = "../ethcore" }
ethcore-util = { path = "../util" }
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.6" }
rlp = { path = "../util/rlp" }
mime = "0.2"
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }

View File

@@ -12,8 +12,8 @@ use-precompiled-js = ["parity-dapps-glue/use-precompiled-js"]
with-syntex = ["parity-dapps-glue/with-syntex"]
[build-dependencies]
parity-dapps-glue = "1.4"
parity-dapps-glue = "1.7"
[dependencies]
parity-dapps-glue = "1.4"
parity-dapps-glue = "1.7"

View File

@@ -1,7 +1,7 @@
[package]
description = "Parity built-in dapps."
name = "parity-ui-dev"
version = "1.4.0"
version = "1.7.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
@@ -11,8 +11,8 @@ default = ["with-syntex"]
with-syntex = ["parity-dapps-glue/with-syntex"]
[build-dependencies]
parity-dapps-glue = "1.4"
parity-dapps-glue = "1.7"
[dependencies]
parity-dapps-glue = "1.4"
parity-dapps-glue = "1.7"

View File

@@ -150,6 +150,7 @@
"blockies": "0.0.2",
"brace": "0.9.0",
"bytes": "2.4.0",
"date-difference": "1.0.0",
"debounce": "1.0.0",
"es6-error": "4.0.0",
"es6-promise": "4.0.5",

View File

@@ -37,7 +37,11 @@ Object.keys(rustMethods).forEach((group) => {
});
});
function printType (type) {
function printType (type, obj) {
if (!type) {
throw new Error(`Invalid type in ${JSON.stringify(obj)}`);
}
return type.print || `\`${type.name}\``;
}
@@ -45,7 +49,7 @@ function formatDescription (obj, prefix = '', indent = '') {
const optional = obj.optional ? '(optional) ' : '';
const defaults = obj.default ? `(default: \`${obj.default}\`) ` : '';
return `${indent}${prefix}${printType(obj.type)} - ${optional}${defaults}${obj.desc}`;
return `${indent}${prefix}${printType(obj.type, obj)} - ${optional}${defaults}${obj.desc}`;
}
function formatType (obj) {

View File

@@ -5,8 +5,8 @@ set -e
UTCDATE=`date -u "+%Y%m%d-%H%M%S"`
PACKAGES=( "parity" "etherscan" "shapeshift" "jsonrpc" )
BRANCH=$CI_BUILD_REF_NAME
GIT_JS_PRECOMPILED="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git"
GIT_PARITY="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git"
GIT_JS_PRECOMPILED="https://${GITHUB_JS_PRECOMPILED}:@github.com/paritytech/js-precompiled.git"
GIT_PARITY="https://${GITHUB_JS_PRECOMPILED}:@github.com/paritytech/parity.git"
# setup the git user defaults for the current repo
function setup_git_user {

View File

@@ -107,34 +107,26 @@ export default class Contract {
});
}
deploy (options, values, statecb) {
const setState = (state) => {
if (!statecb) {
return;
}
return statecb(null, state);
};
setState({ state: 'estimateGas' });
deploy (options, values, statecb = () => {}) {
statecb(null, { state: 'estimateGas' });
return this
.deployEstimateGas(options, values)
.then(([gasEst, gas]) => {
options.gas = gas.toFixed(0);
setState({ state: 'postTransaction', gas });
statecb(null, { state: 'postTransaction', gas });
const _options = this._encodeOptions(this.constructors[0], options, values);
const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
return this._api.parity
.postTransaction(_options)
.postTransaction(encodedOptions)
.then((requestId) => {
setState({ state: 'checkRequest', requestId });
statecb(null, { state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId);
})
.then((txhash) => {
setState({ state: 'getTransactionReceipt', txhash });
statecb(null, { state: 'getTransactionReceipt', txhash });
return this._pollTransactionReceipt(txhash, gas);
})
.then((receipt) => {
@@ -142,23 +134,23 @@ export default class Contract {
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
}
setState({ state: 'hasReceipt', receipt });
statecb(null, { state: 'hasReceipt', receipt });
this._receipt = receipt;
this._address = receipt.contractAddress;
return this._address;
});
})
.then((address) => {
setState({ state: 'getCode' });
return this._api.eth.getCode(this._address);
})
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
})
.then((address) => {
statecb(null, { state: 'getCode' });
return this._api.eth.getCode(this._address);
})
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
setState({ state: 'completed' });
return this._address;
statecb(null, { state: 'completed' });
return this._address;
});
});
}

View File

@@ -143,8 +143,15 @@ export function inOptions (options) {
if (options) {
Object.keys(options).forEach((key) => {
switch (key) {
case 'from':
case 'to':
// Don't encode the `to` option if it's empty
// (eg. contract deployments)
if (options[key]) {
options[key] = inAddress(options[key]);
}
break;
case 'from':
options[key] = inAddress(options[key]);
break;
@@ -204,3 +211,36 @@ export function inTraceType (whatTrace) {
return whatTrace;
}
function inDeriveType (derive) {
return derive && derive.type === 'hard' ? 'hard' : 'soft';
}
export function inDeriveHash (derive) {
const hash = derive && derive.hash ? derive.hash : derive;
const type = inDeriveType(derive);
return {
hash: inHex(hash),
type
};
}
export function inDeriveIndex (derive) {
if (!derive) {
return [];
}
if (!isArray(derive)) {
derive = [derive];
}
return derive.map(item => {
const index = inNumber10(item && item.index ? item.index : item);
return {
index,
type: inDeriveType(item)
};
});
}

View File

@@ -16,7 +16,11 @@
import BigNumber from 'bignumber.js';
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions, inTraceType } from './input';
import {
inAddress, inBlockNumber, inData, inFilter, inHex,
inNumber10, inNumber16, inOptions, inTraceType,
inDeriveHash, inDeriveIndex
} from './input';
import { isAddress } from '../../../test/types';
describe('api/format/input', () => {
@@ -208,7 +212,14 @@ describe('api/format/input', () => {
});
});
['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => {
it('does not encode an empty `to` value', () => {
const options = { to: '' };
const formatted = inOptions(options);
expect(formatted.to).to.equal('');
});
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
@@ -219,8 +230,8 @@ describe('api/format/input', () => {
});
});
it('passes minBlock as null when specified as such', () => {
expect(inOptions({ minBlock: null })).to.deep.equal({ minBlock: null });
it('passes condition as null when specified as such', () => {
expect(inOptions({ condition: null })).to.deep.equal({ condition: null });
});
it('ignores and passes through unknown keys', () => {
@@ -265,4 +276,66 @@ describe('api/format/input', () => {
expect(inTraceType(type)).to.deep.equal([type]);
});
});
describe('inDeriveHash', () => {
it('returns derive hash', () => {
expect(inDeriveHash(1)).to.deep.equal({
hash: '0x1',
type: 'soft'
});
expect(inDeriveHash(null)).to.deep.equal({
hash: '0x',
type: 'soft'
});
expect(inDeriveHash({
hash: 5
})).to.deep.equal({
hash: '0x5',
type: 'soft'
});
expect(inDeriveHash({
hash: 5,
type: 'hard'
})).to.deep.equal({
hash: '0x5',
type: 'hard'
});
});
});
describe('inDeriveIndex', () => {
it('returns derive hash', () => {
expect(inDeriveIndex(null)).to.deep.equal([]);
expect(inDeriveIndex([])).to.deep.equal([]);
expect(inDeriveIndex([1])).to.deep.equal([{
index: 1,
type: 'soft'
}]);
expect(inDeriveIndex({
index: 1
})).to.deep.equal([{
index: 1,
type: 'soft'
}]);
expect(inDeriveIndex([{
index: 1,
type: 'hard'
}, 5])).to.deep.equal([
{
index: 1,
type: 'hard'
},
{
index: 5,
type: 'soft'
}
]);
});
});
});

View File

@@ -280,12 +280,6 @@ export function outTransaction (tx) {
tx[key] = outTransactionCondition(tx[key]);
break;
case 'minBlock':
tx[key] = tx[key]
? outNumber(tx[key])
: null;
break;
case 'creates':
case 'from':
case 'to':

View File

@@ -384,7 +384,7 @@ describe('api/format/output', () => {
});
});
['blockNumber', 'gasPrice', 'gas', 'minBlock', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
@@ -396,8 +396,8 @@ describe('api/format/output', () => {
});
});
it('passes minBlock as null when null', () => {
expect(outTransaction({ minBlock: null })).to.deep.equal({ minBlock: null });
it('passes condition as null when null', () => {
expect(outTransaction({ condition: null })).to.deep.equal({ condition: null });
});
it('ignores and passes through unknown keys', () => {

View File

@@ -14,7 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
import {
inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber, inDeriveHash, inDeriveIndex
} from '../../format/input';
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';
export default class Parity {
@@ -117,6 +119,18 @@ export default class Parity {
.execute('parity_devLogsLevels');
}
deriveAddressHash (address, password, hash, shouldSave) {
return this._transport
.execute('parity_deriveAddressHash', inAddress(address), password, inDeriveHash(hash), !!shouldSave)
.then(outAddress);
}
deriveAddressIndex (address, password, index, shouldSave) {
return this._transport
.execute('parity_deriveAddressIndex', inAddress(address), password, inDeriveIndex(index), !!shouldSave)
.then(outAddress);
}
dropNonReservedPeers () {
return this._transport
.execute('parity_dropNonReservedPeers');
@@ -137,6 +151,11 @@ export default class Parity {
.execute('parity_executeUpgrade');
}
exportAccount (address, password) {
return this._transport
.execute('parity_exportAccount', inAddress(address), password);
}
extraData () {
return this._transport
.execute('parity_extraData');
@@ -389,6 +408,12 @@ export default class Parity {
.execute('parity_removeReservedPeer', encode);
}
removeTransaction (hash) {
return this._transport
.execute('parity_removeTransaction', inHex(hash))
.then(outTransaction);
}
rpcSettings () {
return this._transport
.execute('parity_rpcSettings');

View File

@@ -42,7 +42,7 @@ export default class Personal {
// FIXME: Because of the different API instances, the "wait for valid changes" approach
// doesn't work. Since the defaultAccount is critical to operation, we poll in exactly
// same way we do in ../eth (ala same as eth_blockNumber) and update. This should be moved
// same way we do in ../eth (ala eth_blockNumber) and update. This should be moved
// to pub-sub as it becomes available
_defaultAccount = (timerDisabled = false) => {
const nextTimeout = (timeout = 1000) => {

View File

@@ -0,0 +1,466 @@
[
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

View File

@@ -1 +1,476 @@
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]
[
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "removeOwner",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_addr",
"type": "address"
}
],
"name": "isOwner",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_numOwners",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_lastDay",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "resetSpentToday",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_spentToday",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_required",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_h",
"type": "bytes32"
}
],
"name": "confirm",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newLimit",
"type": "uint256"
}
],
"name": "setDailyLimit",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [
{
"name": "_r",
"type": "bytes32"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
}
],
"name": "revoke",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_newRequired",
"type": "uint256"
}
],
"name": "changeRequirement",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_operation",
"type": "bytes32"
},
{
"name": "_owner",
"type": "address"
}
],
"name": "hasConfirmed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "ownerIndex",
"type": "uint256"
}
],
"name": "getOwner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
}
],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
}
],
"name": "changeOwner",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "m_dailyLimit",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_owners",
"type": "address[]"
},
{
"name": "_required",
"type": "uint256"
},
{
"name": "_daylimit",
"type": "uint256"
}
],
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Confirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
}
],
"name": "Revoke",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "oldOwner",
"type": "address"
}
],
"name": "OwnerRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "newRequirement",
"type": "uint256"
}
],
"name": "RequirementChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "_from",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "SingleTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "owner",
"type": "address"
},
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"name": "created",
"type": "address"
}
],
"name": "MultiTransact",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "operation",
"type": "bytes32"
},
{
"indexed": false,
"name": "initiator",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "data",
"type": "bytes"
}
],
"name": "ConfirmationNeeded",
"type": "event"
}
]

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,14 @@
import * as abis from './abi';
const REGISTRY_V1_HASHES = [
'0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
'0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
];
export default class Registry {
_registryContract = null;
constructor (api) {
this._api = api;
@@ -43,11 +50,10 @@ export default class Registry {
this._fetching = true;
return this._api.parity
.registryAddress()
.then((address) => {
return this.fetchContract()
.then((contract) => {
this._fetching = false;
this._instance = this._api.newContract(abis.registry, address).instance;
this._instance = contract.instance;
this._queue.forEach((queued) => {
queued.resolve(this._instance);
@@ -89,6 +95,47 @@ export default class Registry {
.then((contract) => contract.instance);
}
fetchContract () {
if (this._registryContract) {
return Promise.resolve(this._registryContract);
}
return this._api.parity
.registryAddress()
.then((address) => Promise.all([ address, this._api.eth.getCode(address) ]))
.then(([ address, code ]) => {
const codeHash = this._api.util.sha3(code);
const version = REGISTRY_V1_HASHES.includes(codeHash)
? 1
: 2;
const abi = version === 1
? abis.registry
: abis.registry2;
const contract = this._api.newContract(abi, address);
// Add support for previous `set` and `get` methods
if (!contract.instance.get && contract.instance.getData) {
contract.instance.get = contract.instance.getData;
}
if (contract.instance.get && !contract.instance.getData) {
contract.instance.getData = contract.instance.get;
}
if (!contract.instance.set && contract.instance.setData) {
contract.instance.set = contract.instance.setData;
}
if (contract.instance.set && !contract.instance.setData) {
contract.instance.setData = contract.instance.set;
}
console.log(`registry at ${address}, code ${codeHash}, version ${version}`);
this._registryContract = contract;
return this._registryContract;
});
}
_createGetParams (_name, key) {
const name = _name.toLowerCase();
const sha3 = this._api.util.sha3.text(name);

View File

@@ -35,6 +35,9 @@ function create () {
}
};
api = {
eth: {
getCode: sinon.stub().resolves(0)
},
parity: {
registryAddress: sinon.stub().resolves('testRegistryAddress')
},

View File

@@ -8,453 +8,454 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity ^0.4.6;
contract multisig {
// EVENTS
pragma solidity ^0.4.9;
// this contract can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
contract WalletEvents {
// EVENTS
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
}
contract multisigAbi is multisig {
function isOwner(address _addr) returns (bool);
contract WalletAbi {
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external;
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool);
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) external;
function confirm(bytes32 _h) returns(bool);
function addOwner(address _owner) external;
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit);
function removeOwner(address _owner) external;
function addOwner(address _owner);
function changeRequirement(uint _newRequired) external;
function removeOwner(address _owner);
function isOwner(address _addr) constant returns (bool);
function changeRequirement(uint _newRequired);
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation);
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) external;
function changeOwner(address _from, address _to);
function execute(address _to, uint _value, bytes _data) returns(bool);
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) returns (bool o_success);
}
contract WalletLibrary is multisig {
// TYPES
contract WalletLibrary is WalletEvents {
// TYPES
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded;
uint ownersDone;
uint index;
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded;
uint ownersDone;
uint index;
}
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
}
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i) {
delete m_txs[m_pendingIndex[i]];
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
}
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
}
delete m_pendingIndex;
}
/******************************
***** MULTI OWNED SECTION ****
******************************/
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// MODIFIERS
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
// METHODS
// list of owners
uint[256] m_owners;
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
m_required = _required;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
function isOwner(address _addr) returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
/******************************
****** DAY LIMIT SECTION *****
******************************/
// MODIFIERS
// simple modifier for daily limit.
modifier limitedDaily(uint _value) {
if (underLimit(_value))
_;
}
// METHODS
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
m_spentToday = 0;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
/******************************
********* WALLET SECTION *****
******************************/
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initMultiowned(_owners, _required);
initDaylimit(_daylimit) ;
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
// first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_callValue =_to.call.value(_value)(_data);
} else {
// determine our operation hash.
bytes32 _r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
if (m_txs[_h].to != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function clearWalletPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
delete m_txs[m_pendingIndex[i]];
clearPending();
}
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run.
uint m_required;
// pointer used to find a free slot in m_owners
uint m_numOwners;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
}
contract Wallet is WalletEvents {
contract Wallet is multisig {
// WALLET CONSTRUCTOR
// calls the `initWallet` method of the Library in this context
function Wallet(address[] _owners, uint _required, uint _daylimit) {
// Signature of the Wallet Library's init function
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
address target = _walletLibrary;
// WALLET CONSTRUCTOR
// calls the `initWallet` method of the Library in this context
function Wallet(address[] _owners, uint _required, uint _daylimit) {
// Signature of the Wallet Library's init function
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
address target = _walletLibrary;
// Compute the size of the call data : arrays has 2
// 32bytes for offset and length, plus 32bytes per element ;
// plus 2 32bytes for each uint
uint argarraysize = (2 + _owners.length);
uint argsize = (2 + argarraysize) * 32;
// Compute the size of the call data : arrays has 2
// 32bytes for offset and length, plus 32bytes per element ;
// plus 2 32bytes for each uint
uint argarraysize = (2 + _owners.length);
uint argsize = (2 + argarraysize) * 32;
assembly {
// Add the signature first to memory
mstore(0x0, sig)
// Add the call data, which is at the end of the
// code
codecopy(0x4, sub(codesize, argsize), argsize)
// Delegate call to the library
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
}
assembly {
// Add the signature first to memory
mstore(0x0, sig)
// Add the call data, which is at the end of the
// code
codecopy(0x4, sub(codesize, argsize), argsize)
// Delegate call to the library
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
}
}
// METHODS
// METHODS
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
}
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
// As return statement unavailable in fallback, explicit the method here
// As return statement unavailable in fallback, explicit the method here
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
function isOwner(address _addr) returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
function isOwner(address _addr) constant returns (bool) {
return _walletLibrary.delegatecall(msg.data);
}
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
// list of owners
uint[256] m_owners;
// list of owners
uint[256] m_owners;
}

View File

@@ -8,221 +8,222 @@
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity ^0.4.6;
pragma solidity ^0.4.9;
contract multiowned {
// TYPES
// TYPES
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded;
uint ownersDone;
uint index;
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded;
uint ownersDone;
uint index;
}
// EVENTS
// this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}
// EVENTS
// this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// METHODS
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
// FIELDS
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
delete m_pendingIndex;
}
// FIELDS
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
}
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
@@ -230,79 +231,70 @@ contract multiowned {
// uses is specified in the modifier.
contract daylimit is multiowned {
// MODIFIERS
// METHODS
// simple modifier for daily limit.
modifier limitedDaily(uint _value) {
if (underLimit(_value))
_;
// constructor - stores initial daily limit and records the present day's index.
function daylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// METHODS
// constructor - stores initial daily limit and records the present day's index.
function daylimit(uint _limit) {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
// INTERNAL METHODS
// FIELDS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
// FIELDS
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
}
// interface contract for multisig proxy contracts; see below for docs.
contract multisig {
// EVENTS
// EVENTS
// logged events:
// Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// logged events:
// Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
// FUNCTIONS
// FUNCTIONS
// TODO: document
function changeOwner(address _from, address _to) external;
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
function confirm(bytes32 _h) returns (bool);
// TODO: document
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) external returns (bool o_success);
}
// usage:
@@ -310,79 +302,102 @@ contract multisig {
// Wallet(w).from(anotherOwner).confirm(h);
contract Wallet is multisig, multiowned, daylimit {
// TYPES
// TYPES
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
}
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function Wallet(address[] _owners, uint _required, uint _daylimit)
multiowned(_owners, _required) daylimit(_daylimit) {
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
// METHODS
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function Wallet(address[] _owners, uint _required, uint _daylimit)
multiowned(_owners, _required) daylimit(_daylimit) {
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
}
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
}
}
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// INTERNAL METHODS
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {
// first, take the opportunity to check that we're under the daily limit.
if (underLimit(_value)) {
SingleTransact(msg.sender, _value, _to, _data);
// yes - just execute the call.
_to.call.value(_value)(_data);
return 0;
}
// determine our operation hash.
_r = sha3(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
m_txs[_r].data = _data;
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
}
}
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
delete m_txs[m_pendingIndex[i]];
super.clearPending();
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
if (m_txs[_h].to != 0) {
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
delete m_txs[_h];
return true;
}
}
// FIELDS
// INTERNAL METHODS
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i)
delete m_txs[m_pendingIndex[i]];
super.clearPending();
}
// FIELDS
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
}

View File

@@ -16,6 +16,7 @@
import ReactDOM from 'react-dom';
import React from 'react';
import { AppContainer } from 'react-hot-loader';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
@@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
ReactDOM.render(
<Application />,
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
if (module.hot) {
module.hot.accept('./githubhint/Application/index.js', () => {
require('./githubhint/Application/index.js');
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
});
}

View File

@@ -16,6 +16,7 @@
import ReactDOM from 'react-dom';
import React from 'react';
import { AppContainer } from 'react-hot-loader';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
@@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
ReactDOM.render(
<Application />,
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
if (module.hot) {
module.hot.accept('./localtx/Application/index.js', () => {
require('./localtx/Application/index.js');
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
});
}

View File

@@ -15,5 +15,29 @@
th {
text-align: center;
}
td {
text-align: center;
}
}
button {
background-color: rgba(0, 136, 170, 1);
border: none;
border-radius: 5px;
color: white;
font-size: 1rem;
padding: 0.5em 1em;
width: 100%;
&:hover {
background-color: rgba(0, 136, 170, 0.8);
cursor: pointer;
}
}
input {
font-size: 1rem;
padding: 0.5em 1em;
}
}

View File

@@ -70,7 +70,6 @@ export default class Application extends Component {
local[tx.hash].transaction = tx;
local[tx.hash].stats = data.stats;
});
// Convert local transactions to array
const localTransactions = Object.keys(local).map(hash => {
const data = local[hash];

View File

@@ -6,6 +6,14 @@
}
}
.txhash {
display: inline-block;
overflow: hidden;
padding-right: 3ch;
text-overflow: ellipsis;
width: 10ch;
}
.transaction {
td {
padding: 7px 15px;

View File

@@ -31,8 +31,8 @@ class BaseTransaction extends Component {
renderHash (hash) {
return (
<code title={ hash }>
{ this.shortHash(hash) }
<code title={ hash } className={ styles.txhash }>
{ hash }
</code>
);
}
@@ -206,7 +206,10 @@ export class LocalTransaction extends BaseTransaction {
From
</th>
<th>
Gas Price / Gas
Gas Price
</th>
<th>
Gas
</th>
<th>
Status
@@ -224,18 +227,18 @@ export class LocalTransaction extends BaseTransaction {
toggleResubmit = () => {
const { transaction } = this.props;
const { isResubmitting, gasPrice } = this.state;
const { isResubmitting } = this.state;
this.setState({
const nextState = {
isResubmitting: !isResubmitting
});
};
if (gasPrice === null) {
this.setState({
gasPrice: `0x${transaction.gasPrice.toString(16)}`,
gas: `0x${transaction.gas.toString(16)}`
});
if (!isResubmitting) {
nextState.gasPrice = api.util.fromWei(transaction.gasPrice, 'shannon').toNumber();
nextState.gas = transaction.gas.div(1000000).toNumber();
}
this.setState(nextState);
};
setGasPrice = el => {
@@ -251,16 +254,15 @@ export class LocalTransaction extends BaseTransaction {
};
sendTransaction = () => {
const { transaction } = this.props;
const { transaction, status } = this.props;
const { gasPrice, gas } = this.state;
const newTransaction = {
from: transaction.from,
to: transaction.to,
nonce: transaction.nonce,
value: transaction.value,
data: transaction.input,
gasPrice, gas
gasPrice: api.util.toWei(gasPrice, 'shannon'),
gas: new BigNumber(gas).mul(1000000)
};
this.setState({
@@ -268,11 +270,21 @@ export class LocalTransaction extends BaseTransaction {
isSending: true
});
const closeSending = () => this.setState({
isSending: false,
gasPrice: null,
gas: null
});
const closeSending = () => {
this.setState({
isSending: false,
gasPrice: null,
gas: null
});
};
if (transaction.to) {
newTransaction.to = transaction.to;
}
if (!['mined', 'replaced'].includes(status)) {
newTransaction.nonce = transaction.nonce;
}
api.eth.sendTransaction(newTransaction)
.then(closeSending)
@@ -290,9 +302,9 @@ export class LocalTransaction extends BaseTransaction {
const resubmit = isSending ? (
'sending...'
) : (
<a href='javascript:void' onClick={ this.toggleResubmit }>
<button onClick={ this.toggleResubmit }>
resubmit
</a>
</button>
);
return (
@@ -308,7 +320,8 @@ export class LocalTransaction extends BaseTransaction {
</td>
<td>
{ this.renderGasPrice(transaction) }
<br />
</td>
<td>
{ this.renderGas(transaction) }
</td>
<td>
@@ -345,9 +358,9 @@ export class LocalTransaction extends BaseTransaction {
return (
<tr className={ styles.transaction }>
<td>
<a href='javascript:void' onClick={ this.toggleResubmit }>
<button onClick={ this.toggleResubmit }>
cancel
</a>
</button>
</td>
<td>
{ this.renderHash(transaction.hash) }
@@ -357,20 +370,24 @@ export class LocalTransaction extends BaseTransaction {
</td>
<td className={ styles.edit }>
<input
type='text'
type='number'
value={ gasPrice }
onChange={ this.setGasPrice }
/>
<span>shannon</span>
</td>
<td className={ styles.edit }>
<input
type='text'
type='number'
value={ gas }
onChange={ this.setGas }
/>
<span>MGas</span>
</td>
<td colSpan='2'>
<a href='javascript:void' onClick={ this.sendTransaction }>
<button onClick={ this.sendTransaction }>
Send
</a>
</button>
</td>
</tr>
);

View File

@@ -15,24 +15,10 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.button {
/* TODO remove !important once material design lite is used */
padding: 0 !important;
}
.icon {
/* TODO remove !important once material design lite is used */
margin: 0 !important;
width: 30px !important;
height: 30px !important;
}
.menuIcon {
display: inline-block;
vertical-align: middle;
}
.menuText {
display: inline-block;
line-height: 24px;
vertical-align: top;
margin: 0 !important;
padding: 0 !important;
width: 30px !important;
}

View File

@@ -17,83 +17,50 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import IconMenu from 'material-ui/IconMenu';
import IconButton from 'material-ui/IconButton/IconButton';
import AccountIcon from 'material-ui/svg-icons/action/account-circle';
import MenuItem from 'material-ui/MenuItem';
import { init } from './actions';
import IdentityIcon from '../IdentityIcon';
import Address from '../ui/address';
import { select } from './actions';
import styles from './accounts.css';
class Accounts extends Component {
static propTypes = {
all: PropTypes.object.isRequired,
selected: PropTypes.object,
selected: PropTypes.oneOfType([
PropTypes.oneOf([ null ]),
PropTypes.string
]),
onInit: PropTypes.func.isRequired
};
select: PropTypes.func.isRequired
componentWillMount () {
this.props.onInit();
}
render () {
const { all, selected } = this.props;
const { selected } = this.props;
const origin = { horizontal: 'right', vertical: 'top' };
const accountsButton = (
<IconButton className={ styles.button }>
{ selected
? (
<IdentityIcon
className={ styles.icon }
address={ selected.address }
/>
) : (
<AccountIcon
className={ styles.icon }
color='white'
/>
)
}
</IconButton>);
if (!selected) {
return (
<AccountIcon
className={ styles.icon }
color='white'
/>
);
}
return (
<IconMenu
value={ selected ? this.renderAccount(selected) : null }
onChange={ this.onAccountSelect }
iconButtonElement={ accountsButton }
anchorOrigin={ origin }
targetOrigin={ origin }
>
{ Object.values(all).map(this.renderAccount) }
</IconMenu>
<IdentityIcon
className={ styles.icon }
address={ selected }
/>
);
}
renderAccount = (account) => {
const { selected } = this.props;
const isSelected = selected && selected.address === account.address;
return (
<MenuItem
key={ account.address }
value={ account.address }
checked={ isSelected }
insetChildren={ !isSelected }
>
<Address address={ account.address } />
</MenuItem>
);
};
onAccountSelect = (e, address) => {
this.props.select(address);
};
}
const mapStateToProps = (state) => state.accounts;
const mapDispatchToProps = (dispatch) => bindActionCreators({ select }, dispatch);
const mapDispatchToProps = (dispatch) => bindActionCreators({
onInit: init
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Accounts);

View File

@@ -14,4 +14,27 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { api } from '../parity';
export const select = (address) => ({ type: 'accounts select', address });
export const init = () => (dispatch) => {
api.subscribe('parity_defaultAccount', (error, accountAddress) => {
if (error) {
return console.error(error);
}
if (accountAddress) {
dispatch(select(accountAddress));
}
});
return api.parity
.defaultAccount()
.then((accountAddress) => {
dispatch(select(accountAddress));
})
.catch((error) => {
console.error(error);
});
};

View File

@@ -16,9 +16,11 @@
*/
.header {
align-items: center;
display: flex;
justify-content: space-between;
margin: 0; padding: .3em 1em;
margin: 0;
padding: 0.3em 1em;
color: #fff;
background-color: #333;
}
@@ -54,6 +56,7 @@
background: #f80;
bottom: 0;
color: #fff;
cursor: pointer;
left: 0;
opacity: 1;
padding: 1.5em;

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