From ac2c678e6907670884989166d9954356f4f472b4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 4 Jan 2017 20:10:19 +0100 Subject: [PATCH 01/37] reverse caching: redux boilerplate --- js/src/redux/providers/registry/actions.js | 28 +++++++++++++++++++++ js/src/redux/providers/registry/reducer.js | 29 ++++++++++++++++++++++ js/src/redux/reducers.js | 4 ++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 js/src/redux/providers/registry/actions.js create mode 100644 js/src/redux/providers/registry/reducer.js diff --git a/js/src/redux/providers/registry/actions.js b/js/src/redux/providers/registry/actions.js new file mode 100644 index 00000000000..eeeb47f51ae --- /dev/null +++ b/js/src/redux/providers/registry/actions.js @@ -0,0 +1,28 @@ +// Copyright 2015, 2016 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 . + +export const setReverse = (address, reverse) => ({ + type: 'setReverse', + address, reverse +}); + +export const startCachingReverses = () => ({ + type: 'startCachingReverses' +}); + +export const stopCachingReverses = () => ({ + type: 'stopCachingReverses' +}); diff --git a/js/src/redux/providers/registry/reducer.js b/js/src/redux/providers/registry/reducer.js new file mode 100644 index 00000000000..42663897a68 --- /dev/null +++ b/js/src/redux/providers/registry/reducer.js @@ -0,0 +1,29 @@ +// Copyright 2015, 2016 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 . + +const initialState = { + reverse: {} // cache for reverse lookup +}; + +export default (state = initialState, action) => { + if (action.type === 'setReverse') { + return { ...state, reverse: { + ...state.reverse, [ action.address ]: action.reverse + } }; + } + + return state; +}; diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js index aac1ea779a7..577fca11f32 100644 --- a/js/src/redux/reducers.js +++ b/js/src/redux/reducers.js @@ -24,6 +24,7 @@ import { snackbarReducer, walletReducer } from './providers'; import certificationsReducer from './providers/certifications/reducer'; +import registryReducer from './providers/registry/reducer'; import errorReducer from '~/ui/Errors/reducers'; import settingsReducer from '~/views/Settings/reducers'; @@ -46,6 +47,7 @@ export default function () { personal: personalReducer, signer: signerReducer, snackbar: snackbarReducer, - wallet: walletReducer + wallet: walletReducer, + registry: registryReducer }); } From b38aa8d01fc273065517f9ada60bd81b19eafcd8 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 5 Jan 2017 16:56:33 +0100 Subject: [PATCH 02/37] reverse caching: middleware --- js/src/redux/middleware.js | 5 +- js/src/redux/providers/registry/middleware.js | 106 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 js/src/redux/providers/registry/middleware.js diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js index b62b4e5f283..5cb635f7f2a 100644 --- a/js/src/redux/middleware.js +++ b/js/src/redux/middleware.js @@ -23,6 +23,7 @@ import SignerMiddleware from './providers/signerMiddleware'; import statusMiddleware from '~/views/Status/middleware'; import CertificationsMiddleware from './providers/certifications/middleware'; import ChainMiddleware from './providers/chainMiddleware'; +import RegistryMiddleware from './providers/registry/middleware'; export default function (api, browserHistory) { const errors = new ErrorsMiddleware(); @@ -32,13 +33,15 @@ export default function (api, browserHistory) { const certifications = new CertificationsMiddleware(); const routeMiddleware = routerMiddleware(browserHistory); const chain = new ChainMiddleware(); + const registry = new RegistryMiddleware(); const middleware = [ settings.toMiddleware(), signer.toMiddleware(), errors.toMiddleware(), certifications.toMiddleware(), - chain.toMiddleware() + chain.toMiddleware(), + registry.toMiddleware() ]; return middleware.concat(status, routeMiddleware, thunk); diff --git a/js/src/redux/providers/registry/middleware.js b/js/src/redux/providers/registry/middleware.js new file mode 100644 index 00000000000..2fda34a5605 --- /dev/null +++ b/js/src/redux/providers/registry/middleware.js @@ -0,0 +1,106 @@ +// Copyright 2015, 2016 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 . + +import Contracts from '~/contracts'; +import subscribeToEvent from '~/util/subscribe-to-event'; + +import registryABI from '~/contracts/abi/registry.json'; + +import { setReverse, startCachingReverses } from './actions'; + +export default class RegistryMiddleware { + toMiddleware () { + return (store) => { + const api = Contracts.get()._api; + let contract, confirmedEvents, removedEvents, interval; + + let addressesToCheck = {}; + + const onLog = (log) => { + switch (log.event) { + case 'ReverseConfirmed': + addressesToCheck[log.params.reverse.value] = true; + + break; + case 'ReverseRemoved': + delete addressesToCheck[log.params.reverse.value]; + + break; + } + }; + + const checkReverses = () => { + Object + .keys(addressesToCheck) + .forEach((address) => { + contract + .instance + .reverse + .call({}, [ address ]) + .then((reverse) => store.dispatch(setReverse(address, reverse))); + }); + + addressesToCheck = {}; + }; + + return (next) => (action) => { + switch (action.type) { + case 'initAll': + next(action); + store.dispatch(startCachingReverses()); + + break; + case 'startCachingReverses': + const { registry } = Contracts.get(); + + registry.getInstance() + .then((instance) => api.newContract(registryABI, instance.address)) + .then((_contract) => { + contract = _contract; + + confirmedEvents = subscribeToEvent(_contract, 'ReverseConfirmed'); + confirmedEvents.on('log', onLog); + + removedEvents = subscribeToEvent(_contract, 'ReverseRemoved'); + removedEvents.on('log', onLog); + + interval = setInterval(checkReverses, 2000); + }) + .catch((err) => { + console.error('Failed to start caching reverses:', err); + throw err; + }); + + break; + case 'stopCachingReverses': + if (confirmedEvents) { + confirmedEvents.unsubscribe(); + } + if (removedEvents) { + removedEvents.unsubscribe(); + } + if (interval) { + clearInterval(interval); + } + + break; + default: + next(action); + } + }; + }; + } +}; From 6f492a49452ac59bba7de4a21d891b113f833b18 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 5 Jan 2017 17:19:26 +0100 Subject: [PATCH 03/37] address selector: use cached reverses --- js/src/ui/Form/AddressSelect/addressSelect.js | 5 +++- .../Form/AddressSelect/addressSelectStore.js | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 692ff42858a..7b09b5b0767 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -56,6 +56,7 @@ class AddressSelect extends Component { contacts: PropTypes.object, contracts: PropTypes.object, tokens: PropTypes.object, + reverse: PropTypes.object, // Optional props allowCopy: PropTypes.bool, @@ -584,10 +585,12 @@ class AddressSelect extends Component { function mapStateToProps (state) { const { accountsInfo } = state.personal; const { balances } = state.balances; + const { reverse } = state.registry; return { accountsInfo, - balances + balances, + reverse }; } diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 26f9fe80e16..ae868fb4ba6 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -30,7 +30,29 @@ export default class AddressSelectStore { @observable registryValues = []; initValues = []; - regLookups = []; + regLookups = [ + (address) => { + const name = this.reverse[address]; + + if (!name) { + return null; + } + + return { + address, + name, + description: ( + + ) + }; + } + ]; constructor (api) { this.api = api; @@ -114,7 +136,8 @@ export default class AddressSelectStore { } @action setValues (props) { - const { accounts = {}, contracts = {}, contacts = {} } = props; + const { accounts = {}, contracts = {}, contacts = {}, reverse = {} } = props; + this.reverse = reverse; const accountsN = Object.keys(accounts).length; const contractsN = Object.keys(contracts).length; From 71e80f10d88d8ea0d3bcc0c15c94bd9f321c2776 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 5 Jan 2017 17:20:10 +0100 Subject: [PATCH 04/37] reverse caching: optimize timing Users quickly want all reverses registered in the path, but don't care so much about live data. --- js/src/redux/providers/registry/middleware.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/js/src/redux/providers/registry/middleware.js b/js/src/redux/providers/registry/middleware.js index 2fda34a5605..87d9fbf7025 100644 --- a/js/src/redux/providers/registry/middleware.js +++ b/js/src/redux/providers/registry/middleware.js @@ -25,7 +25,7 @@ export default class RegistryMiddleware { toMiddleware () { return (store) => { const api = Contracts.get()._api; - let contract, confirmedEvents, removedEvents, interval; + let contract, confirmedEvents, removedEvents, timeout, interval; let addressesToCheck = {}; @@ -77,7 +77,8 @@ export default class RegistryMiddleware { removedEvents = subscribeToEvent(_contract, 'ReverseRemoved'); removedEvents.on('log', onLog); - interval = setInterval(checkReverses, 2000); + timeout = setTimeout(checkReverses, 5000); + interval = setInterval(checkReverses, 20000); }) .catch((err) => { console.error('Failed to start caching reverses:', err); @@ -95,6 +96,9 @@ export default class RegistryMiddleware { if (interval) { clearInterval(interval); } + if (timeout) { + clearTimeout(timeout); + } break; default: @@ -103,4 +107,4 @@ export default class RegistryMiddleware { }; }; } -}; +} From 2aa76e5acce221aab9e680356d375b155688fa60 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 5 Jan 2017 18:21:15 +0100 Subject: [PATCH 05/37] address selector: simple completion for reversed addresses --- .../ui/Form/AddressSelect/addressSelectStore.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index ae868fb4ba6..39894dac90f 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -32,10 +32,23 @@ export default class AddressSelectStore { initValues = []; regLookups = [ (address) => { - const name = this.reverse[address]; + if (address.length === 0 || address === '0x') { + return null; + } + + let name = this.reverse[address]; if (!name) { - return null; + const addr = Object + .keys(this.reverse) + .find((addr) => addr.slice(0, address.length) === address); + + if (addr) { + address = addr; + name = this.reverse[addr]; + } else { + return null; + } } return { From 81641a5839f5ac55043020ec764c87256b8053da Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 6 Jan 2017 17:23:04 +0100 Subject: [PATCH 06/37] Fix tags after Account view (#4070) --- js/src/ui/Actionbar/Search/search.js | 8 ++++++-- js/src/ui/Actionbar/actionbar.css | 15 ++++++++------- js/src/ui/Form/InputChip/inputChip.js | 4 ++++ js/src/views/Accounts/List/list.js | 4 ++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/js/src/ui/Actionbar/Search/search.js b/js/src/ui/Actionbar/Search/search.js index 30d8117bd98..31541f082fd 100644 --- a/js/src/ui/Actionbar/Search/search.js +++ b/js/src/ui/Actionbar/Search/search.js @@ -71,15 +71,15 @@ export default class ActionbarSearch extends Component { key='searchAccount'>
@@ -118,6 +118,10 @@ export default class ActionbarSearch extends Component { handleSearchClick = () => { const { showSearch } = this.state; + if (!showSearch) { + this.refs.inputChip.focus(); + } + this.handleOpenSearch(!showSearch); } diff --git a/js/src/ui/Actionbar/actionbar.css b/js/src/ui/Actionbar/actionbar.css index aacc3fc395e..311e2b546d0 100644 --- a/js/src/ui/Actionbar/actionbar.css +++ b/js/src/ui/Actionbar/actionbar.css @@ -27,13 +27,14 @@ } .toolbuttons { -} + overflow: hidden; -.toolbuttons button { - margin: 10px 0 10px 16px !important; - color: white !important; -} + button { + margin: 10px 0 10px 16px !important; + color: white !important; + } -.toolbuttons svg { - fill: white !important; + svg { + fill: white !important; + } } diff --git a/js/src/ui/Form/InputChip/inputChip.js b/js/src/ui/Form/InputChip/inputChip.js index a12825d71d7..479ca4b099d 100644 --- a/js/src/ui/Form/InputChip/inputChip.js +++ b/js/src/ui/Form/InputChip/inputChip.js @@ -170,6 +170,10 @@ export default class InputChip extends Component { .filter(v => v !== value)); this.handleTokensChange(newTokens); + this.focus(); + } + + focus = () => { this.refs.chipInput.focus(); } diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 9cebdda6e6e..19245f4a2b0 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -220,8 +220,8 @@ class List extends Component { const tags = account.meta.tags || []; const name = account.name || ''; - const values = [] - .concat(tags, name) + const values = tags + .concat(name) .map(v => v.toLowerCase()); return searchValues From 30394bdb1c346a5849ad7311e436aa8e8a738439 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 6 Jan 2017 17:24:35 +0100 Subject: [PATCH 07/37] Add Origin to events table (#4073) --- js/src/views/Contract/Events/events.js | 8 ++++++++ js/src/views/Contract/contract.css | 27 +++++++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index dba05dfd9fc..1367624e856 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -74,6 +74,14 @@ export default class Events extends Component { return ( + + + + + { list }
+ + origin +
diff --git a/js/src/views/Contract/contract.css b/js/src/views/Contract/contract.css index 4752fd04a9d..e3fa38f6cb3 100644 --- a/js/src/views/Contract/contract.css +++ b/js/src/views/Contract/contract.css @@ -29,14 +29,31 @@ .event { td { vertical-align: top; - padding: 1em 0.5em; + padding: 0 0.5em 1.5em; div { white-space: nowrap; } + + &.timestamp { + padding-right: 1.5em; + text-align: right; + line-height: 1.5em; + opacity: 0.5; + white-space: nowrap; + } } } +.origin { + text-align: left; + padding-left: 32px; + text-indent: 1em; + color: rgba(255, 255, 255, 0.5); + text-transform: uppercase; + font-size: 0.9em; +} + .txhash { text-overflow: ellipsis; width: 20%; @@ -54,14 +71,6 @@ opacity: 0.5; } -.timestamp { - padding-top: 1.5em; - text-align: right; - line-height: 1.5em; - opacity: 0.5; - white-space: nowrap; -} - .eventDetails { } From a7f23cbaeddd5d8745c6d713395a0ef82cf98a18 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 6 Jan 2017 16:31:16 +0000 Subject: [PATCH 08/37] [ci skip] js-precompiled 20170106-162807 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86f7cef578d..edd6b6d8cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a74caf6d8fe4b3371b291fb47f15c043504ef738" +source = "git+https://github.com/ethcore/js-precompiled.git#083070dce66df0fe18cce30c4d31977c3ee15da7" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 13e03f0c7ba..a86330e0b2a 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.173", + "version": "0.2.174", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 761ed913a8b8f171ecfa72bf1b3d9a4235d5d354 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Fri, 6 Jan 2017 17:56:57 +0100 Subject: [PATCH 09/37] Passwords are valid by default (#4075) * Passwords are valid by default #4059 * Actually fixing the issue --- js/src/modals/CreateAccount/NewAccount/newAccount.js | 2 +- js/src/modals/CreateAccount/NewImport/newImport.js | 2 +- js/src/modals/CreateAccount/RawKey/rawKey.js | 4 +--- .../CreateAccount/RecoveryPhrase/recoveryPhrase.js | 4 ++-- js/src/modals/CreateAccount/createAccount.js | 9 +++++++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index c0bcca91a50..ed2c24612a6 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -40,7 +40,7 @@ export default class CreateAccount extends Component { accountNameError: ERRORS.noName, accounts: null, isValidName: false, - isValidPass: false, + isValidPass: true, passwordHint: '', password1: '', password1Error: null, diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 17a1cfd447f..91ef39d9505 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -37,7 +37,7 @@ export default class NewImport extends Component { accountName: '', accountNameError: ERRORS.noName, isValidFile: false, - isValidPass: false, + isValidPass: true, isValidName: false, password: '', passwordError: null, diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index f284cf32308..d0b3a4c7118 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -36,7 +36,7 @@ export default class RawKey extends Component { accountNameError: ERRORS.noName, isValidKey: false, isValidName: false, - isValidPass: false, + isValidPass: true, passwordHint: '', password1: '', password1Error: null, @@ -119,8 +119,6 @@ export default class RawKey extends Component { const rawKey = event.target.value; let rawKeyError = null; - console.log(rawKey.length, rawKey); - if (!rawKey || !rawKey.trim().length) { rawKeyError = ERRORS.noKey; } else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !api.util.isHex(rawKey)) { diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index fd504302497..dc80e27ae43 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -31,9 +31,9 @@ export default class RecoveryPhrase extends Component { state = { accountName: '', accountNameError: ERRORS.noName, - isValidPass: false, + isValidPass: true, isValidName: false, - isValidPhrase: false, + isValidPhrase: true, passwordHint: '', password1: '', password1Error: null, diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index 569d374ccd1..53be1f91839 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -240,6 +240,7 @@ export default class CreateAccount extends Component { if (createType === 'fromNew' || createType === 'fromPhrase') { let phrase = this.state.phrase; + if (createType === 'fromPhrase' && windowsPhrase) { phrase = phrase .split(' ') // get the words @@ -271,7 +272,9 @@ export default class CreateAccount extends Component { this.newError(error); }); - } else if (createType === 'fromRaw') { + } + + if (createType === 'fromRaw') { return api.parity .newAccountFromSecret(this.state.rawKey, this.state.password) .then((address) => { @@ -296,7 +299,9 @@ export default class CreateAccount extends Component { this.newError(error); }); - } else if (createType === 'fromGeth') { + } + + if (createType === 'fromGeth') { return api.parity .importGethAccounts(this.state.gethAddresses) .then((result) => { From c73bb3eed74d0cf3f4707ff53c246eecd39f754a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 6 Jan 2017 17:06:28 +0000 Subject: [PATCH 10/37] [ci skip] js-precompiled 20170106-170326 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edd6b6d8cd9..934e111d824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#083070dce66df0fe18cce30c4d31977c3ee15da7" +source = "git+https://github.com/ethcore/js-precompiled.git#b225444f8c6d1153e2c7be125e70705eaefffedc" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index a86330e0b2a..9a4bb808378 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.174", + "version": "0.2.175", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From ba4263e33f8c5b40fd1225f921b694b6e2eb99a7 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 6 Jan 2017 18:55:18 +0100 Subject: [PATCH 11/37] fix tests :white_check_mark: --- js/src/modals/ExecuteContract/executeContract.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/src/modals/ExecuteContract/executeContract.test.js b/js/src/modals/ExecuteContract/executeContract.test.js index ce196408dfd..8d9e4ccca39 100644 --- a/js/src/modals/ExecuteContract/executeContract.test.js +++ b/js/src/modals/ExecuteContract/executeContract.test.js @@ -59,6 +59,9 @@ const STORE = { }, settings: { backgroundSeed: '' + }, + registry: { + reverse: {} } }; } From 920d2217c87586a0663dc5d93c6492e9bdda48b9 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 7 Jan 2017 04:21:07 +0000 Subject: [PATCH 12/37] [ci skip] js-precompiled 20170107-041806 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 934e111d824..11182e077e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b225444f8c6d1153e2c7be125e70705eaefffedc" +source = "git+https://github.com/ethcore/js-precompiled.git#a0da6607bf9c07e35c7a4e91dde3d12b31fcd2d6" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 9a4bb808378..cecd16d9ee1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.175", + "version": "0.2.176", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 33d3c1f999da9ae7c9fa3d0bbb9de86165678488 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 8 Jan 2017 03:34:41 +0000 Subject: [PATCH 13/37] [ci skip] js-precompiled 20170108-033146 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11182e077e3..fe7b4c0f9e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a0da6607bf9c07e35c7a4e91dde3d12b31fcd2d6" +source = "git+https://github.com/ethcore/js-precompiled.git#db482be7897e719d87696819d99c49a7be16bf61" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index cecd16d9ee1..d2fb941d4c4 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.176", + "version": "0.2.177", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 71e6e24a1f2ffe3d32d906f82d2ecb8a7593ac31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 8 Jan 2017 17:12:17 +0100 Subject: [PATCH 14/37] Improving logs for transactions sync and disable re-broadcasting while syncing (#4065) * Improving logs for transaction propagation * Propagate only on timer --- sync/src/chain.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index df69dbb084d..a9a806dd32d 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1447,7 +1447,7 @@ impl ChainSync { } let mut item_count = r.item_count(); - trace!(target: "sync", "{} -> Transactions ({} entries)", peer_id, item_count); + trace!(target: "sync", "{:02} -> Transactions ({} entries)", peer_id, item_count); item_count = min(item_count, MAX_TX_TO_IMPORT); let mut transactions = Vec::with_capacity(item_count); for i in 0 .. item_count { @@ -1987,7 +1987,7 @@ impl ChainSync { stats.propagated(*hash, id, block_number); } peer_info.last_sent_transactions = all_transactions_hashes.clone(); - return Some((*peer_id, all_transactions_rlp.clone())); + return Some((*peer_id, all_transactions_hashes.len(), all_transactions_rlp.clone())); } // Get hashes of all transactions to send to this peer @@ -2008,21 +2008,23 @@ impl ChainSync { } peer_info.last_sent_transactions = all_transactions_hashes.clone(); - Some((*peer_id, packet.out())) + Some((*peer_id, to_send.len(), packet.out())) }) .collect::>() }; // Send RLPs - let sent = lucky_peers.len(); - if sent > 0 { - for (peer_id, rlp) in lucky_peers { + let peers = lucky_peers.len(); + if peers > 0 { + let mut max_sent = 0; + for (peer_id, sent, rlp) in lucky_peers { self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp); + trace!(target: "sync", "{:02} <- Transactions ({} entries)", peer_id, sent); + max_sent = max(max_sent, sent); } - - trace!(target: "sync", "Sent up to {} transactions to {} peers.", transactions.len(), sent); + debug!(target: "sync", "Sent up to {} transactions to {} peers.", max_sent, peers); } - sent + peers } fn propagate_latest_blocks(&mut self, io: &mut SyncIo, sealed: &[H256]) { @@ -2042,7 +2044,6 @@ impl ChainSync { trace!(target: "sync", "Sent sealed block to all peers"); }; } - self.propagate_new_transactions(io); self.last_sent_block_number = chain_info.best_block_number; } @@ -2070,7 +2071,9 @@ impl ChainSync { /// called when block is imported to chain - propagates the blocks and updates transactions sent to peers pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], enacted: &[H256], _retracted: &[H256], sealed: &[H256], proposed: &[Bytes]) { let queue_info = io.chain().queue_info(); - if !self.status().is_syncing(queue_info) || !sealed.is_empty() { + let is_syncing = self.status().is_syncing(queue_info); + + if !is_syncing || !sealed.is_empty() { trace!(target: "sync", "Propagating blocks, state={:?}", self.state); self.propagate_latest_blocks(io, sealed); self.propagate_proposed_blocks(io, proposed); @@ -2080,7 +2083,7 @@ impl ChainSync { self.restart(io); } - if !enacted.is_empty() { + if !is_syncing && !enacted.is_empty() { // Select random peers to re-broadcast transactions to. let mut random = random::new(); let len = self.peers.len(); @@ -2531,7 +2534,7 @@ mod tests { } #[test] - fn propagates_new_transactions_after_new_block() { + fn does_not_propagate_new_transactions_after_new_block() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); @@ -2541,16 +2544,16 @@ mod tests { let mut io = TestIo::new(&mut client, &ss, &queue, None); let peer_count = sync.propagate_new_transactions(&mut io); io.chain.insert_transaction_to_queue(); - // New block import should trigger propagation. + // New block import should not trigger propagation. + // (we only propagate on timeout) sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // 2 message should be send - assert_eq!(2, io.packets.len()); + assert_eq!(1, io.packets.len()); // 1 peer should receive the message assert_eq!(1, peer_count); // TRANSACTIONS_PACKET assert_eq!(0x02, io.packets[0].packet_id); - assert_eq!(0x02, io.packets[1].packet_id); } #[test] From cf0a20f08baf534d139fd84aa5b7c09faaae44b7 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 9 Jan 2017 09:38:27 +0100 Subject: [PATCH 15/37] Display contract block creation (#4069) * Add contract block creation to metadata * Display mined block for contract on Contracts view * Better use of Summary for Accounts * Add sorted by mined block for contracts * Proper Block Number sort // display in contract page * PR Grumble * Linting issues --- js/src/api/contract/contract.js | 5 ++ .../modals/DeployContract/deployContract.js | 14 +++-- js/src/ui/Balance/balance.css | 2 +- js/src/ui/Container/Title/title.css | 7 ++- js/src/ui/Container/Title/title.js | 30 +++++++++-- js/src/views/Accounts/List/list.js | 43 ++++++++++----- js/src/views/Accounts/Summary/summary.js | 39 ++++++++++++-- js/src/views/Accounts/accounts.css | 9 ++++ js/src/views/Contract/contract.css | 5 ++ js/src/views/Contract/contract.js | 29 ++++++++++- js/src/views/Contracts/Summary/index.js | 17 ------ js/src/views/Contracts/Summary/summary.js | 52 ------------------- js/src/views/Contracts/contracts.js | 8 +-- 13 files changed, 161 insertions(+), 99 deletions(-) delete mode 100644 js/src/views/Contracts/Summary/index.js delete mode 100644 js/src/views/Contracts/Summary/summary.js diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 70853749d30..9c3b02b7232 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -75,6 +75,10 @@ export default class Contract { return this._functions; } + get receipt () { + return this._receipt; + } + get instance () { this._instance.address = this._address; return this._instance; @@ -139,6 +143,7 @@ export default class Contract { } setState({ state: 'hasReceipt', receipt }); + this._receipt = receipt; this._address = receipt.contractAddress; return this._address; }); diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 6b09986adb0..1bda0dddf77 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -455,10 +455,15 @@ class DeployContract extends Component { this.setState({ step: 'DEPLOYMENT' }); - api - .newContract(abiParsed) + const contract = api.newContract(abiParsed); + + contract .deploy(options, params, this.onDeploymentState) .then((address) => { + const blockNumber = contract._receipt + ? contract.receipt.blockNumber.toNumber() + : null; + return Promise.all([ api.parity.setAccountName(address, name), api.parity.setAccountMeta(address, { @@ -466,8 +471,9 @@ class DeployContract extends Component { contract: true, timestamp: Date.now(), deleted: false, - source, - description + blockNumber, + description, + source }) ]) .then(() => { diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index a2790483262..9fe7cc8ac7f 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -23,7 +23,7 @@ .empty { line-height: 24px; - margin: 0.75em 0.5em 0 0; + margin: 0 0.5em 0 0; opacity: 0.25; } diff --git a/js/src/ui/Container/Title/title.css b/js/src/ui/Container/Title/title.css index 341c25a7fea..9b636c0346f 100644 --- a/js/src/ui/Container/Title/title.css +++ b/js/src/ui/Container/Title/title.css @@ -14,7 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ -.byline { +.byline, .description { overflow: hidden; position: relative; line-height: 1.2em; @@ -31,6 +31,11 @@ } } +.description { + font-size: 0.75em; + margin: 0.5em 0 0; +} + .title { text-transform: uppercase; margin: 0; diff --git a/js/src/ui/Container/Title/title.js b/js/src/ui/Container/Title/title.js index de25b818cdb..ccd3f9d0e87 100644 --- a/js/src/ui/Container/Title/title.js +++ b/js/src/ui/Container/Title/title.js @@ -22,13 +22,14 @@ import styles from './title.css'; export default class Title extends Component { static propTypes = { + byline: nodeOrStringProptype(), className: PropTypes.string, - title: nodeOrStringProptype(), - byline: nodeOrStringProptype() + description: nodeOrStringProptype(), + title: nodeOrStringProptype() } render () { - const { className, title, byline } = this.props; + const { byline, className, title } = this.props; const byLine = typeof byline === 'string' ? ( @@ -46,6 +47,29 @@ export default class Title extends Component {
{ byLine }
+ { this.renderDescription() } + + ); + } + + renderDescription () { + const { description } = this.props; + + if (!description) { + return null; + } + + const desc = typeof description === 'string' + ? ( + + { description } + + ) + : description; + + return ( +
+ { desc }
); } diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 19245f4a2b0..ce7a2ae99b8 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -57,7 +57,7 @@ class List extends Component { } renderAccounts () { - const { accounts, balances, empty, link, handleAddSearchToken } = this.props; + const { accounts, balances, empty } = this.props; if (empty) { return ( @@ -80,20 +80,29 @@ class List extends Component { return (
- + key={ address } + > + { this.renderSummary(account, balance, owners) }
); }); } + renderSummary (account, balance, owners) { + const { handleAddSearchToken, link } = this.props; + + return ( + + ); + } + getAddresses () { const filteredAddresses = this.getFilteredAddresses(); return this.sortAddresses(filteredAddresses); @@ -122,7 +131,15 @@ class List extends Component { }); } - compareAccounts (accountA, accountB, key) { + compareAccounts (accountA, accountB, key, _reverse = null) { + if (key && key.split(':')[1] === '-1') { + return this.compareAccounts(accountA, accountB, key.split(':')[0], true); + } + + if (key === 'timestamp' && _reverse === null) { + return this.compareAccounts(accountA, accountB, key, true); + } + if (key === 'name') { return accountA.name.localeCompare(accountB.name); } @@ -177,7 +194,9 @@ class List extends Component { return tagsA.localeCompare(tagsB); } - const reverse = key === 'timestamp' ? -1 : 1; + const reverse = _reverse + ? -1 + : 1; const metaA = accountA.meta[key]; const metaB = accountB.meta[key]; diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 8658077a5c4..20924dc4ac6 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -19,6 +19,7 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; +import { FormattedMessage } from 'react-intl'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; import Certifications from '~/ui/Certifications'; @@ -107,14 +108,22 @@ export default class Summary extends Component { /> ); + const description = this.getDescription(account.meta); + return ( - - +
+ + +
{ this.renderOwners() } { this.renderBalance() } @@ -123,6 +132,26 @@ export default class Summary extends Component { ); } + getDescription (meta = {}) { + const { blockNumber } = meta; + + if (!blockNumber) { + return null; + } + + const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat(); + + return ( + + ); + } + renderOwners () { const { owners } = this.props; const ownersValid = (owners || []).filter((owner) => owner.address && new BigNumber(owner.address).gt(0)); diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css index 2a7cdcec9f9..25dfdcab449 100644 --- a/js/src/views/Accounts/accounts.css +++ b/js/src/views/Accounts/accounts.css @@ -56,3 +56,12 @@ } } } + +.heading { + display: flex; + flex-direction: row; + + .main { + flex: 1; + } +} diff --git a/js/src/views/Contract/contract.css b/js/src/views/Contract/contract.css index e3fa38f6cb3..924818ebdf5 100644 --- a/js/src/views/Contract/contract.css +++ b/js/src/views/Contract/contract.css @@ -45,6 +45,11 @@ } } +.blockNumber { + color: rgba(255, 255, 255, 0.25); + margin-top: 1.5em; +} + .origin { text-align: left; padding-left: 32px; diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index d06c22b92dd..fc299f7cb85 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -17,6 +17,9 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { FormattedMessage } from 'react-intl'; +import BigNumber from 'bignumber.js'; + import ActionDelete from 'material-ui/svg-icons/action/delete'; import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow'; import ContentCreate from 'material-ui/svg-icons/content/create'; @@ -136,7 +139,9 @@ class Contract extends Component { account={ account } balance={ balance } isContract - /> + > + { this.renderBlockNumber(account.meta) } + + + + ); + } + renderDetails (contract) { const { showDetailsDialog } = this.state; diff --git a/js/src/views/Contracts/Summary/index.js b/js/src/views/Contracts/Summary/index.js deleted file mode 100644 index 980ecff9a80..00000000000 --- a/js/src/views/Contracts/Summary/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2015, 2016 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 . - -export default from './summary'; diff --git a/js/src/views/Contracts/Summary/summary.js b/js/src/views/Contracts/Summary/summary.js deleted file mode 100644 index 36e88f03937..00000000000 --- a/js/src/views/Contracts/Summary/summary.js +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2015, 2016 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 . - -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; - -import { Container, ContainerTitle, IdentityIcon, IdentityName } from '~/ui'; - -export default class Summary extends Component { - static contextTypes = { - api: React.PropTypes.object.isRequired - } - - static propTypes = { - contract: PropTypes.object.isRequired, - children: PropTypes.node - } - - render () { - const contract = this.props.contract; - - if (!contract) { - return null; - } - - const viewLink = `/app/${contract.address}`; - - return ( - - - { } } - byline={ contract.address } /> - { this.props.children } - - ); - } -} diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index cc292275ac7..532c2ffb42b 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -45,7 +45,7 @@ class Contracts extends Component { state = { addContract: false, deployContract: false, - sortOrder: 'timestamp', + sortOrder: 'blockNumber', searchValues: [], searchTokens: [] } @@ -92,7 +92,8 @@ class Contracts extends Component { empty={ !hasContracts } order={ sortOrder } orderFallback='name' - handleAddSearchToken={ this.onAddSearchToken } /> + handleAddSearchToken={ this.onAddSearchToken } + /> ); @@ -109,7 +110,8 @@ class Contracts extends Component { id='sortContracts' order={ this.state.sortOrder } metas={ [ - { key: 'timestamp', label: 'date' } + { key: 'timestamp', label: 'date' }, + { key: 'blockNumber:-1', label: 'mined block' } ] } showDefault={ false } onChange={ onChange } /> From ec4b4cfbf2b22a2bd209dfc53461173650c2c56c Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 9 Jan 2017 08:47:45 +0000 Subject: [PATCH 16/37] [ci skip] js-precompiled 20170109-084458 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe7b4c0f9e3..923418a2c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#db482be7897e719d87696819d99c49a7be16bf61" +source = "git+https://github.com/ethcore/js-precompiled.git#fbc7864393ebbc78ea8f7bc4729f2ac3bdcb9a0e" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index d2fb941d4c4..d5a35ad89b9 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.177", + "version": "0.2.178", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 40f0ee004f03d0d5adc04313c9f2fe6460505bfb Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 9 Jan 2017 11:14:36 +0100 Subject: [PATCH 17/37] Key derivation in Worker (#4071) * Add Signer Key Derivation in Service Worker * Several fixes throughout the UI * Hint for external account // working Worker * Add Worker state change * PR Grumbles --- js/src/modals/PasswordManager/store.js | 2 +- js/src/redux/providers/compilerActions.js | 69 ------------------- js/src/redux/providers/index.js | 2 +- js/src/redux/providers/signerMiddleware.js | 67 +++++++++++------- js/src/redux/providers/status.js | 5 +- js/src/redux/providers/worker.js | 68 ++++++++++++++++++ js/src/redux/providers/workerActions.js | 29 ++++++++ .../{compilerReducer.js => workerReducer.js} | 2 +- js/src/redux/reducers.js | 6 +- js/src/redux/store.js | 2 + js/src/serviceWorker.js | 10 +++ js/src/ui/MethodDecoding/methodDecoding.css | 4 ++ js/src/ui/MethodDecoding/methodDecoding.js | 2 +- js/src/util/{wallet.js => signer.js} | 28 ++++++-- .../transactionPendingFormConfirm.js | 33 ++++++++- js/src/views/WriteContract/writeContract.js | 18 +---- 16 files changed, 223 insertions(+), 124 deletions(-) delete mode 100644 js/src/redux/providers/compilerActions.js create mode 100644 js/src/redux/providers/worker.js create mode 100644 js/src/redux/providers/workerActions.js rename js/src/redux/providers/{compilerReducer.js => workerReducer.js} (94%) rename js/src/util/{wallet.js => signer.js} (83%) diff --git a/js/src/modals/PasswordManager/store.js b/js/src/modals/PasswordManager/store.js index c60576e0fa4..659543c28ef 100644 --- a/js/src/modals/PasswordManager/store.js +++ b/js/src/modals/PasswordManager/store.js @@ -133,7 +133,7 @@ export default class Store { } testPassword = (password) => { - this.setBusy(false); + this.setBusy(true); return this._api.parity .testPassword(this.address, password || this.validatePassword) diff --git a/js/src/redux/providers/compilerActions.js b/js/src/redux/providers/compilerActions.js deleted file mode 100644 index d638c03a2f4..00000000000 --- a/js/src/redux/providers/compilerActions.js +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2015, 2016 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 . - -import PromiseWorker from 'promise-worker'; -import runtime from 'serviceworker-webpack-plugin/lib/runtime'; - -let workerRegistration; - -// Setup the Service Worker -if ('serviceWorker' in navigator) { - workerRegistration = runtime - .register() - .then(() => navigator.serviceWorker.ready) - .then((registration) => { - const _worker = registration.active; - _worker.controller = registration.active; - const worker = new PromiseWorker(_worker); - - return worker; - }); -} else { - workerRegistration = Promise.reject('Service Worker is not available in your browser.'); -} - -export function setWorker (worker) { - return { - type: 'setWorker', - worker - }; -} - -export function setError (error) { - return { - type: 'setError', - error - }; -} - -export function setupWorker () { - return (dispatch, getState) => { - const state = getState(); - - if (state.compiler.worker) { - return; - } - - workerRegistration - .then((worker) => { - dispatch(setWorker(worker)); - }) - .catch((error) => { - console.error('sw', error); - dispatch(setWorker(null)); - }); - }; -} diff --git a/js/src/redux/providers/index.js b/js/src/redux/providers/index.js index 6a000bdac51..e1441c4794e 100644 --- a/js/src/redux/providers/index.js +++ b/js/src/redux/providers/index.js @@ -22,7 +22,7 @@ export Status from './status'; export apiReducer from './apiReducer'; export balancesReducer from './balancesReducer'; export blockchainReducer from './blockchainReducer'; -export compilerReducer from './compilerReducer'; +export workerReducer from './workerReducer'; export imagesReducer from './imagesReducer'; export personalReducer from './personalReducer'; export signerReducer from './signerReducer'; diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js index 018e01e59ad..ba51d34266a 100644 --- a/js/src/redux/providers/signerMiddleware.js +++ b/js/src/redux/providers/signerMiddleware.js @@ -17,7 +17,7 @@ import * as actions from './signerActions'; import { inHex } from '~/api/format/input'; -import { Wallet } from '../../util/wallet'; +import { Signer } from '../../util/signer'; export default class SignerMiddleware { constructor (api) { @@ -58,6 +58,7 @@ export default class SignerMiddleware { promise .then((txHash) => { console.log('confirmRequest', id, txHash); + if (!txHash) { store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); return; @@ -73,33 +74,49 @@ export default class SignerMiddleware { // Sign request in-browser const transaction = payload.sendTransaction || payload.signTransaction; + if (wallet && transaction) { - (transaction.nonce.isZero() + const noncePromise = transaction.nonce.isZero() ? this._api.parity.nextNonce(transaction.from) - : Promise.resolve(transaction.nonce) - ).then(nonce => { - let txData = { - to: inHex(transaction.to), - nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce), - gasPrice: inHex(transaction.gasPrice), - gasLimit: inHex(transaction.gas), - value: inHex(transaction.value), - data: inHex(transaction.data) - }; - - try { - // NOTE: Derving the key takes significant amount of time, - // make sure to display some kind of "in-progress" state. - const signer = Wallet.fromJson(wallet, password); - const rawTx = signer.signTransaction(txData); - - handlePromise(this._api.signer.confirmRequestRaw(id, rawTx)); - } catch (error) { - console.error(error); + : Promise.resolve(transaction.nonce); + + const { worker } = store.getState().worker; + + const signerPromise = worker && worker._worker.state === 'activated' + ? worker + .postMessage({ + action: 'getSignerSeed', + data: { wallet, password } + }) + .then((result) => { + const seed = Buffer.from(result.data); + return new Signer(seed); + }) + : Signer.fromJson(wallet, password); + + // NOTE: Derving the key takes significant amount of time, + // make sure to display some kind of "in-progress" state. + return Promise + .all([ signerPromise, noncePromise ]) + .then(([ signer, nonce ]) => { + const txData = { + to: inHex(transaction.to), + nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce), + gasPrice: inHex(transaction.gasPrice), + gasLimit: inHex(transaction.gas), + value: inHex(transaction.value), + data: inHex(transaction.data) + }; + + return signer.signTransaction(txData); + }) + .then((rawTx) => { + return handlePromise(this._api.signer.confirmRequestRaw(id, rawTx)); + }) + .catch((error) => { + console.error(error.message); store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); - } - }); - return; + }); } handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password)); diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index ef4c092242d..6d0e24c6b53 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -125,12 +125,13 @@ export default class Status { this._store.dispatch(statusCollection(status)); this._status = status; } + + nextTimeout(); }) .catch((error) => { console.error('_pollStatus', error); + nextTimeout(); }); - - nextTimeout(); } /** diff --git a/js/src/redux/providers/worker.js b/js/src/redux/providers/worker.js new file mode 100644 index 00000000000..35ca0f17398 --- /dev/null +++ b/js/src/redux/providers/worker.js @@ -0,0 +1,68 @@ +// Copyright 2015, 2016 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 . + +import PromiseWorker from 'promise-worker'; +import runtime from 'serviceworker-webpack-plugin/lib/runtime'; + +import { setWorker } from './workerActions'; + +function getWorker () { + // Setup the Service Worker + if ('serviceWorker' in navigator) { + return runtime + .register() + .then(() => navigator.serviceWorker.ready) + .then((registration) => { + const worker = registration.active; + worker.controller = registration.active; + + return new PromiseWorker(worker); + }); + } + + return Promise.reject('Service Worker is not available in your browser.'); +} + +export const setupWorker = (store) => { + const { dispatch, getState } = store; + + const state = getState(); + const stateWorker = state.worker.worker; + + if (stateWorker !== undefined && !(stateWorker && stateWorker._worker.state === 'redundant')) { + return; + } + + getWorker() + .then((worker) => { + if (worker) { + worker._worker.addEventListener('statechange', (event) => { + console.warn('worker state changed to', worker._worker.state); + + // Re-install the new Worker + if (worker._worker.state === 'redundant') { + setupWorker(store); + } + }); + } + + dispatch(setWorker(worker)); + }) + .catch((error) => { + console.error('sw', error); + dispatch(setWorker(null)); + }); +}; diff --git a/js/src/redux/providers/workerActions.js b/js/src/redux/providers/workerActions.js new file mode 100644 index 00000000000..50a87750804 --- /dev/null +++ b/js/src/redux/providers/workerActions.js @@ -0,0 +1,29 @@ +// Copyright 2015, 2016 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 . + +export function setWorker (worker) { + return { + type: 'setWorker', + worker + }; +} + +export function setError (error) { + return { + type: 'setError', + error + }; +} diff --git a/js/src/redux/providers/compilerReducer.js b/js/src/redux/providers/workerReducer.js similarity index 94% rename from js/src/redux/providers/compilerReducer.js rename to js/src/redux/providers/workerReducer.js index e23bf3b167d..27144e11b80 100644 --- a/js/src/redux/providers/compilerReducer.js +++ b/js/src/redux/providers/workerReducer.js @@ -24,7 +24,7 @@ const initialState = { export default handleActions({ setWorker (state, action) { const { worker } = action; - return Object.assign({}, state, { worker }); + return Object.assign({}, state, { worker: worker || null }); }, setError (state, action) { diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js index aac1ea779a7..1156d183666 100644 --- a/js/src/redux/reducers.js +++ b/js/src/redux/reducers.js @@ -19,7 +19,7 @@ import { routerReducer } from 'react-router-redux'; import { apiReducer, balancesReducer, blockchainReducer, - compilerReducer, imagesReducer, personalReducer, + workerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer, walletReducer } from './providers'; @@ -40,12 +40,12 @@ export default function () { balances: balancesReducer, certifications: certificationsReducer, blockchain: blockchainReducer, - compiler: compilerReducer, images: imagesReducer, nodeStatus: nodeStatusReducer, personal: personalReducer, signer: signerReducer, snackbar: snackbarReducer, - wallet: walletReducer + wallet: walletReducer, + worker: workerReducer }); } diff --git a/js/src/redux/store.js b/js/src/redux/store.js index dc043e242a2..9924aa461c2 100644 --- a/js/src/redux/store.js +++ b/js/src/redux/store.js @@ -20,6 +20,7 @@ import initMiddleware from './middleware'; import initReducers from './reducers'; import { load as loadWallet } from './providers/walletActions'; +import { setupWorker } from './providers/worker'; import { Balances as BalancesProvider, @@ -43,6 +44,7 @@ export default function (api, browserHistory) { new StatusProvider(store, api).start(); store.dispatch(loadWallet(api)); + setupWorker(store); return store; } diff --git a/js/src/serviceWorker.js b/js/src/serviceWorker.js index 136e6a6b7f0..3fdfc02ac85 100644 --- a/js/src/serviceWorker.js +++ b/js/src/serviceWorker.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import registerPromiseWorker from 'promise-worker/register'; +import { Signer } from '~/util/signer'; import SolidityUtils from '~/util/solidity'; const CACHE_NAME = 'parity-cache-v1'; @@ -93,12 +94,21 @@ function handleMessage (message) { case 'setFiles': return setFiles(message.data); + case 'getSignerSeed': + return getSignerSeed(message.data); + default: console.warn(`unknown action "${message.action}"`); return null; } } +function getSignerSeed (data) { + console.log('deriving seed from service-worker'); + const { wallet, password } = data; + return Signer.getSeed(wallet, password); +} + function compile (data) { const { build } = data; diff --git a/js/src/ui/MethodDecoding/methodDecoding.css b/js/src/ui/MethodDecoding/methodDecoding.css index adb899e1c10..c782d6ce740 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.css +++ b/js/src/ui/MethodDecoding/methodDecoding.css @@ -38,6 +38,10 @@ justify-content: center; } +.details { + line-height: 1.75em; +} + .details, .gasDetails { color: #aaa; diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index 59704a7313f..693ae60b58e 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -196,7 +196,7 @@ class MethodDecoding extends Component { : text.slice(0, 50) + '...'; return ( -
+
with the { + return new Signer(seed); + }); + } + + static getSeed (json, password) { + try { + const seed = Signer.getSyncSeed(json, password); + return Promise.resolve(seed); + } catch (error) { + return Promise.reject(error); + } + } + + static getSyncSeed (json, password) { if (json.version !== 3) { throw new Error('Only V3 wallets are supported'); } @@ -43,15 +60,17 @@ export class Wallet { if (kdfparams.prf !== 'hmac-sha256') { throw new Error('Unsupported parameters to PBKDF2'); } + derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256'); } else { throw new Error('Unsupported key derivation scheme'); } const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex'); - let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + if (mac !== inHex(json.crypto.mac)) { - throw new Error('Key derivation failed - possibly wrong passphrase'); + throw new Error('Key derivation failed - possibly wrong password'); } const decipher = createDecipheriv( @@ -59,6 +78,7 @@ export class Wallet { derivedKey.slice(0, 16), Buffer.from(json.crypto.cipherparams.iv, 'hex') ); + let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]); while (seed.length < 32) { @@ -66,7 +86,7 @@ export class Wallet { seed = Buffer.concat([nullBuff, seed]); } - return new Wallet(seed); + return seed; } constructor (seed) { diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js index 99bd1c5f3e3..45eb3e5dd76 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js @@ -77,13 +77,28 @@ class TransactionPendingFormConfirm extends Component { } } + getPasswordHint () { + const { account } = this.props; + const accountHint = account && account.meta && account.meta.passwordHint; + + if (accountHint) { + return accountHint; + } + + const { wallet } = this.state; + const walletHint = wallet && wallet.meta && wallet.meta.passwordHint; + + return walletHint || null; + } + render () { const { account, address, isSending } = this.props; const { password, wallet, walletError } = this.state; const isExternal = !account.uuid; - const passwordHint = account.meta && account.meta.passwordHint - ? (
(hint) { account.meta.passwordHint }
) + const passwordHintText = this.getPasswordHint(); + const passwordHint = passwordHintText + ? (
(hint) { passwordHintText }
) : null; const isWalletOk = !isExternal || (walletError === null && wallet !== null); @@ -170,12 +185,26 @@ class TransactionPendingFormConfirm extends Component { } onKeySelect = (event) => { + // Check that file have been selected + if (event.target.files.length === 0) { + return this.setState({ + wallet: null, + walletError: null + }); + } + const fileReader = new FileReader(); fileReader.onload = (e) => { try { const wallet = JSON.parse(e.target.result); + try { + if (wallet && typeof wallet.meta === 'string') { + wallet.meta = JSON.parse(wallet.meta); + } + } catch (e) {} + this.setState({ wallet, walletError: null diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js index c013775a158..8a3ddf3d11b 100644 --- a/js/src/views/WriteContract/writeContract.js +++ b/js/src/views/WriteContract/writeContract.js @@ -18,7 +18,6 @@ import React, { PropTypes, Component } from 'react'; import { observer } from 'mobx-react'; import { MenuItem, Toggle } from 'material-ui'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import CircularProgress from 'material-ui/CircularProgress'; import moment from 'moment'; import { throttle } from 'lodash'; @@ -32,8 +31,6 @@ import SendIcon from 'material-ui/svg-icons/content/send'; import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '~/ui'; import { DeployContract, SaveContract, LoadContract } from '~/modals'; -import { setupWorker } from '~/redux/providers/compilerActions'; - import WriteContractStore from './writeContractStore'; import styles from './writeContract.css'; @@ -42,7 +39,6 @@ class WriteContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - setupWorker: PropTypes.func.isRequired, worker: PropTypes.object, workerError: PropTypes.any }; @@ -55,8 +51,7 @@ class WriteContract extends Component { }; componentWillMount () { - const { setupWorker, worker } = this.props; - setupWorker(); + const { worker } = this.props; if (worker !== undefined) { this.store.setWorker(worker); @@ -575,17 +570,10 @@ class WriteContract extends Component { function mapStateToProps (state) { const { accounts } = state.personal; - const { worker, error } = state.compiler; + const { worker, error } = state.worker; return { accounts, worker, workerError: error }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({ - setupWorker - }, dispatch); -} - export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(WriteContract); From e1847442cc012e28508fa5faac1d30104e138f9a Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 9 Jan 2017 11:15:00 +0100 Subject: [PATCH 18/37] Fix balances update (#4077) * Fix balances fetching * Fix fetching balances while syncing --- js/src/redux/providers/balances.js | 2 +- js/src/redux/providers/balancesActions.js | 11 ++++------- js/src/redux/providers/certifications/middleware.js | 1 + js/src/redux/providers/personalActions.js | 2 +- js/src/redux/providers/personalReducer.js | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/js/src/redux/providers/balances.js b/js/src/redux/providers/balances.js index 0a73ef4f93c..8d46e42d2c4 100644 --- a/js/src/redux/providers/balances.js +++ b/js/src/redux/providers/balances.js @@ -86,7 +86,7 @@ export default class Balances { // If syncing, only retrieve balances once every // few seconds if (syncing) { - this.shortThrottledFetch(); + this.shortThrottledFetch.cancel(); return this.longThrottledFetch(); } diff --git a/js/src/redux/providers/balancesActions.js b/js/src/redux/providers/balancesActions.js index d90cab6786f..56a2ebafd77 100644 --- a/js/src/redux/providers/balancesActions.js +++ b/js/src/redux/providers/balancesActions.js @@ -173,18 +173,15 @@ export function fetchTokens (_tokenIds) { export function fetchBalances (_addresses) { return (dispatch, getState) => { const { api, personal } = getState(); - const { visibleAccounts, accountsInfo } = personal; - - const addresses = uniq((_addresses || visibleAccounts || []).concat(Object.keys(accountsInfo))); + const { visibleAccounts, accounts } = personal; - if (addresses.length === 0) { - return Promise.resolve(); - } + const addresses = uniq(_addresses || visibleAccounts || []); // With only a single account, more info will be displayed. const fullFetch = addresses.length === 1; - const addressesToFetch = uniq(addresses); + // Add accounts addresses (for notifications, accounts selection, etc.) + const addressesToFetch = uniq(addresses.concat(Object.keys(accounts))); return Promise .all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch))) diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index c81aa7e6797..498c1cb933f 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -218,6 +218,7 @@ export default class CertificationsMiddleware { const _addresses = action.addresses || []; addresses = uniq(addresses.concat(_addresses)); fetchConfirmedEvents(); + next(action); break; default: diff --git a/js/src/redux/providers/personalActions.js b/js/src/redux/providers/personalActions.js index 1ed39c05aa4..5d91aeef8ec 100644 --- a/js/src/redux/providers/personalActions.js +++ b/js/src/redux/providers/personalActions.js @@ -122,7 +122,7 @@ export function setVisibleAccounts (addresses) { return; } - dispatch(fetchBalances(addresses)); dispatch(_setVisibleAccounts(addresses)); + dispatch(fetchBalances(addresses)); }; } diff --git a/js/src/redux/providers/personalReducer.js b/js/src/redux/providers/personalReducer.js index daadd54b36c..b6cd051a471 100644 --- a/js/src/redux/providers/personalReducer.js +++ b/js/src/redux/providers/personalReducer.js @@ -47,7 +47,7 @@ export default handleActions({ setVisibleAccounts (state, action) { const addresses = (action.addresses || []).sort(); - if (isEqual(addresses, state.addresses)) { + if (isEqual(addresses, state.visibleAccounts)) { return state; } From 9fec422a1b06fc6b383d931fe1801900abb435cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 9 Jan 2017 11:16:05 +0100 Subject: [PATCH 19/37] Fixing compilation without dapps. (#4088) --- parity/dapps.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/parity/dapps.rs b/parity/dapps.rs index 711ba3a4974..591c1759310 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -100,6 +100,7 @@ pub use self::server::setup_dapps_server; mod server { use super::Dependencies; use std::net::SocketAddr; + use std::path::PathBuf; pub struct WebappServer; pub fn setup_dapps_server( From 14e65e5c3c8b3e41105ba3db31f957592c37a213 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 9 Jan 2017 11:16:21 +0100 Subject: [PATCH 20/37] Move to new CI servers. (#4091) --- .gitlab-ci.yml | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b05c1e01c5..2833a46c383 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,8 @@ linux-stable: - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5 - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5" - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu + - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu + - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu tags: - rust - rust-stable @@ -106,7 +107,8 @@ linux-centos: - 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 - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - rust - rust-centos @@ -144,7 +146,8 @@ linux-i686: - 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"_i386.deb" --body "parity_"$VER"_i386.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5" - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - rust - rust-i686 @@ -189,7 +192,8 @@ linux-armv7: - 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"_armhf.deb" --body "parity_"$VER"_armhf.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - rust - rust-arm @@ -235,6 +239,7 @@ linux-arm: - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5" - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: - rust - rust-arm @@ -272,7 +277,8 @@ linux-armv6: - 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 - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - rust - rust-arm @@ -316,7 +322,8 @@ linux-aarch64: - 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" - aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5" - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - rust - rust-arm @@ -352,7 +359,8 @@ darwin: - 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" - - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.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: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: - osx artifacts: @@ -413,7 +421,8 @@ windows: - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5 - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip - aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5 - - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://icarus.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM% + - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&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.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM% tags: - rust-windows artifacts: @@ -526,6 +535,7 @@ push-release: - triggers image: ethcore/rust:stable script: - - curl --data "secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF + - 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 tags: - curl From 4c94878cf7798b1cd001b91b9e6cfb576b53d4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 9 Jan 2017 11:20:02 +0100 Subject: [PATCH 21/37] Sending transactions in chunks. (#4089) --- sync/src/chain.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index a9a806dd32d..a87c7d1be8a 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -125,6 +125,8 @@ const MAX_NEW_HASHES: usize = 64; const MAX_TX_TO_IMPORT: usize = 512; const MAX_NEW_BLOCK_AGE: BlockNumber = 20; const MAX_TRANSACTION_SIZE: usize = 300*1024; +// Maximal number of transactions in sent in single packet. +const MAX_TRANSACTIONS_TO_PROPAGATE: usize = 64; // Min number of blocks to be behind for a snapshot sync const SNAPSHOT_RESTORE_THRESHOLD: BlockNumber = 100000; const SNAPSHOT_MIN_PEERS: usize = 3; @@ -1991,7 +1993,10 @@ impl ChainSync { } // Get hashes of all transactions to send to this peer - let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions).cloned().collect::>(); + let to_send = all_transactions_hashes.difference(&peer_info.last_sent_transactions) + .take(MAX_TRANSACTIONS_TO_PROPAGATE) + .cloned() + .collect::>(); if to_send.is_empty() { return None; } @@ -2007,7 +2012,11 @@ impl ChainSync { } } - peer_info.last_sent_transactions = all_transactions_hashes.clone(); + peer_info.last_sent_transactions = all_transactions_hashes + .intersection(&peer_info.last_sent_transactions) + .chain(&to_send) + .cloned() + .collect(); Some((*peer_id, to_send.len(), packet.out())) }) .collect::>() From 378739fae1b0a734c6cd7a89236703c0154f6a18 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 9 Jan 2017 12:40:29 +0100 Subject: [PATCH 22/37] Use shallow-only rendering in all tests (#4087) * Container/Title with shallow * IdentityName with shallow * IdentityIcon with shallow * TypedInput to shallow * DetailsStep to shallow --- .../DetailsStep/detailsStep.spec.js | 30 +++++++--------- js/src/ui/Container/Title/title.spec.js | 18 ++++------ js/src/ui/Form/TypedInput/typedInput.spec.js | 21 ++++++----- js/src/ui/IdentityIcon/identityIcon.js | 7 +--- js/src/ui/IdentityIcon/identityIcon.spec.js | 26 +++++--------- js/src/ui/IdentityName/identityName.js | 7 +--- js/src/ui/IdentityName/identityName.spec.js | 35 +++++++++++-------- 7 files changed, 61 insertions(+), 83 deletions(-) diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js index 3622b980566..060b25c17b7 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js @@ -14,15 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; import sinon from 'sinon'; -import { ContextProvider, muiTheme } from '~/ui'; - import DetailsStep from './'; -import { createApi, STORE, CONTRACT } from '../executeContract.test.js'; +import { CONTRACT } from '../executeContract.test.js'; let component; let onAmountChange; @@ -40,18 +38,16 @@ function render (props) { onGasEditClick = sinon.stub(); onValueChange = sinon.stub(); - component = mount( - - - + component = shallow( + ); return component; @@ -74,7 +70,7 @@ describe('modals/ExecuteContract/DetailsStep', () => { describe('bool parameters', () => { it('toggles from false to true', () => { - component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } }); + component.find('TypedInput').last().shallow().simulate('change', { target: { value: 'true' } }); expect(onValueChange).to.have.been.calledWith(null, 0, true); }); diff --git a/js/src/ui/Container/Title/title.spec.js b/js/src/ui/Container/Title/title.spec.js index 2d5335c145c..957d59a84c7 100644 --- a/js/src/ui/Container/Title/title.spec.js +++ b/js/src/ui/Container/Title/title.spec.js @@ -15,38 +15,32 @@ // along with Parity. If not, see . import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import Title from './title'; -function renderShallow (props) { +function render (props) { return shallow( ); } -function renderMount (props) { - return mount( - <Title { ...props } /> - ); -} - describe('ui/Container/Title', () => { describe('rendering', () => { it('renders defaults', () => { - expect(renderShallow()).to.be.ok; + expect(render()).to.be.ok; }); it('renders with the specified className', () => { - expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + expect(render({ className: 'testClass' })).to.have.className('testClass'); }); it('renders the specified title', () => { - expect(renderMount({ title: 'titleText' })).to.contain.text('titleText'); + expect(render({ title: 'titleText' })).to.contain.text('titleText'); }); it('renders the specified byline', () => { - expect(renderMount({ byline: 'bylineText' })).to.contain.text('bylineText'); + expect(render({ byline: 'bylineText' })).to.contain.text('bylineText'); }); }); }); diff --git a/js/src/ui/Form/TypedInput/typedInput.spec.js b/js/src/ui/Form/TypedInput/typedInput.spec.js index e27c7482a42..7cd123d973a 100644 --- a/js/src/ui/Form/TypedInput/typedInput.spec.js +++ b/js/src/ui/Form/TypedInput/typedInput.spec.js @@ -14,27 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; import sinon from 'sinon'; -import { ContextProvider, muiTheme } from '~/ui'; import { ABI_TYPES } from '~/util/abi'; import TypedInput from './'; let component; +let select; let onChange; function render (props) { onChange = sinon.stub(); - component = mount( - <ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }> - <TypedInput - { ...props } - onChange={ onChange } /> - </ContextProvider> + component = shallow( + <TypedInput + { ...props } + onChange={ onChange } /> ); + select = component.find('Select'); return component; } @@ -50,19 +49,19 @@ describe('ui/Form/TypedInput', () => { }); it('calls onChange when value changes', () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + select.shallow().simulate('change', { target: { value: 'true' } }); expect(onChange).to.have.been.called; }); it("calls onChange(true) when value changes to 'true'", () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + select.shallow().simulate('change', { target: { value: 'true' } }); expect(onChange).to.have.been.calledWith(true); }); it("calls onChange(false) when value changes to 'false'", () => { - component.find('DropDownMenu').simulate('change', { target: { value: 'false' } }); + select.shallow().simulate('change', { target: { value: 'false' } }); expect(onChange).to.have.been.calledWith(false); }); diff --git a/js/src/ui/IdentityIcon/identityIcon.js b/js/src/ui/IdentityIcon/identityIcon.js index d4d65241ae3..54a36d500e2 100644 --- a/js/src/ui/IdentityIcon/identityIcon.js +++ b/js/src/ui/IdentityIcon/identityIcon.js @@ -16,7 +16,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { createIdentityImg } from '~/api/util/identity'; import { isNullAddress } from '~/util/validation'; @@ -145,11 +144,7 @@ function mapStateToProps (state) { return { images }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(IdentityIcon); diff --git a/js/src/ui/IdentityIcon/identityIcon.spec.js b/js/src/ui/IdentityIcon/identityIcon.spec.js index 759907deb1f..df9665a2f6d 100644 --- a/js/src/ui/IdentityIcon/identityIcon.spec.js +++ b/js/src/ui/IdentityIcon/identityIcon.spec.js @@ -14,12 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; -import React, { PropTypes } from 'react'; +import { shallow } from 'enzyme'; +import React from 'react'; import sinon from 'sinon'; -import muiTheme from '../Theme'; - import IdentityIcon from './'; const ADDRESS0 = '0x0000000000000000000000000000000000000000'; @@ -27,6 +25,7 @@ const ADDRESS1 = '0x0123456789012345678901234567890123456789'; const ADDRESS2 = '0x9876543210987654321098765432109876543210'; let component; +let instance; function createApi () { return { @@ -53,20 +52,13 @@ function render (props = {}) { props.address = ADDRESS1; } - component = mount( + component = shallow( <IdentityIcon { ...props } />, - { - childContextTypes: { - api: PropTypes.object, - muiTheme: PropTypes.object - }, - context: { - api: createApi(), - muiTheme, - store: createRedux() - } - } - ); + { context: { store: createRedux() } } + ).find('IdentityIcon').shallow({ context: { api: createApi() } }); + + instance = component.instance(); + instance.componentDidMount(); return component; } diff --git a/js/src/ui/IdentityName/identityName.js b/js/src/ui/IdentityName/identityName.js index 980f42638b1..56118c59a03 100644 --- a/js/src/ui/IdentityName/identityName.js +++ b/js/src/ui/IdentityName/identityName.js @@ -17,7 +17,6 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { isNullAddress } from '~/util/validation'; import ShortenedHash from '../ShortenedHash'; @@ -85,11 +84,7 @@ function mapStateToProps (state) { }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(IdentityName); diff --git a/js/src/ui/IdentityName/identityName.spec.js b/js/src/ui/IdentityName/identityName.spec.js index 12bd073636e..eafcf91f844 100644 --- a/js/src/ui/IdentityName/identityName.spec.js +++ b/js/src/ui/IdentityName/identityName.spec.js @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import React from 'react'; -import { IntlProvider } from 'react-intl'; import sinon from 'sinon'; @@ -45,13 +44,11 @@ const STORE = { }; function render (props) { - return mount( - <IntlProvider locale='en'> - <IdentityName - store={ STORE } - { ...props } /> - </IntlProvider> - ); + return shallow( + <IdentityName + store={ STORE } + { ...props } /> + ).find('IdentityName').shallow(); } describe('ui/IdentityName', () => { @@ -62,23 +59,33 @@ describe('ui/IdentityName', () => { describe('account not found', () => { it('renders null with empty', () => { - expect(render({ address: ADDR_C, empty: true }).html()).to.be.null; + expect( + render({ address: ADDR_C, empty: true }).html() + ).to.be.null; }); it('renders address without empty', () => { - expect(render({ address: ADDR_C }).text()).to.equal(ADDR_C); + expect( + render({ address: ADDR_C }).text() + ).to.equal(ADDR_C); }); it('renders short address with shorten', () => { - expect(render({ address: ADDR_C, shorten: true }).text()).to.equal('123456…56789c'); + expect( + render({ address: ADDR_C, shorten: true }).find('ShortenedHash').props().data + ).to.equal(ADDR_C); }); it('renders unknown with flag', () => { - expect(render({ address: ADDR_C, unknown: true }).text()).to.equal('UNNAMED'); + expect( + render({ address: ADDR_C, unknown: true } + ).find('FormattedMessage').props().id).to.equal('ui.identityName.unnamed'); }); it('renders 0x000...000 as null', () => { - expect(render({ address: ADDR_NULL }).text()).to.equal('NULL'); + expect( + render({ address: ADDR_NULL }).find('FormattedMessage').props().id + ).to.equal('ui.identityName.null'); }); }); }); From 8d256b223da1d4ced61ca26a09289e920611e272 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Mon, 9 Jan 2017 12:41:19 +0100 Subject: [PATCH 23/37] Fix rebroadcast panic (#4084) * fix * fix compile * remove peers trace * simplify * Fixing 'simplify' --- sync/src/chain.rs | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index a87c7d1be8a..6ae69bf74fd 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2092,19 +2092,13 @@ impl ChainSync { self.restart(io); } - if !is_syncing && !enacted.is_empty() { - // Select random peers to re-broadcast transactions to. - let mut random = random::new(); - let len = self.peers.len(); - let peers = random.gen_range(0, min(len, 3)); - trace!(target: "sync", "Re-broadcasting transactions to {} random peers.", peers); - - for _ in 0..peers { - let peer = random.gen_range(0, len); - self.peers.values_mut().nth(peer).map(|mut peer_info| { - peer_info.last_sent_transactions.clear() - }); - } + if !is_syncing && !enacted.is_empty() && !self.peers.is_empty() { + // Select random peer to re-broadcast transactions to. + let peer = random::new().gen_range(0, self.peers.len()); + trace!(target: "sync", "Re-broadcasting transactions to a random peer."); + self.peers.values_mut().nth(peer).map(|mut peer_info| + peer_info.last_sent_transactions.clear() + ); } } @@ -2565,6 +2559,26 @@ mod tests { assert_eq!(0x02, io.packets[0].packet_id); } + #[test] + fn does_not_fail_for_no_peers() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + client.insert_transaction_to_queue(); + // Sync with no peers + let mut sync = ChainSync::new(SyncConfig::default(), &client); + let queue = RwLock::new(VecDeque::new()); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &queue, None); + let peer_count = sync.propagate_new_transactions(&mut io); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); + // Try to propagate same transactions for the second time + let peer_count2 = sync.propagate_new_transactions(&mut io); + + assert_eq!(0, io.packets.len()); + assert_eq!(0, peer_count); + assert_eq!(0, peer_count2); + } + #[test] fn propagates_transactions_without_alternating() { let mut client = TestBlockChainClient::new(); From 38734c92c254673c4618e558a1e1e5b0879db252 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 14:02:52 +0100 Subject: [PATCH 24/37] address selector: lower case for reverse completion --- js/src/ui/Form/AddressSelect/addressSelectStore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 39894dac90f..43a8207acae 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -32,6 +32,7 @@ export default class AddressSelectStore { initValues = []; regLookups = [ (address) => { + address = address.toLowerCase().trim(); if (address.length === 0 || address === '0x') { return null; } @@ -41,7 +42,7 @@ export default class AddressSelectStore { if (!name) { const addr = Object .keys(this.reverse) - .find((addr) => addr.slice(0, address.length) === address); + .find((addr) => addr.toLowerCase().slice(0, address.length) === address); if (addr) { address = addr; From f05ffd2e09fb1dae8d1d7491d067c7eaec0a1a93 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 14:13:11 +0100 Subject: [PATCH 25/37] address selector: complete reverses by name as well --- .../Form/AddressSelect/addressSelectStore.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 43a8207acae..4f6de148a1e 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -31,18 +31,26 @@ export default class AddressSelectStore { initValues = []; regLookups = [ - (address) => { - address = address.toLowerCase().trim(); - if (address.length === 0 || address === '0x') { + (query) => { + query = query.toLowerCase().trim(); + if (query.length === 0 || query === '0x') { return null; } - let name = this.reverse[address]; + let address; + let name = this.reverse[query]; if (!name) { const addr = Object .keys(this.reverse) - .find((addr) => addr.toLowerCase().slice(0, address.length) === address); + .find((addr) => { + if (addr.toLowerCase().slice(0, query.length) === query) { + return true; + } + + const name = this.reverse[addr]; + return name.toLowerCase().slice(0, query.length) === query; + }); if (addr) { address = addr; From 8c2aa4d87c35035d6081ae4b235822d003987e6a Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 14:24:04 +0100 Subject: [PATCH 26/37] address selector: unique registry results --- js/src/ui/Form/AddressSelect/addressSelectStore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 4f6de148a1e..1c93088d774 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -16,7 +16,7 @@ import React from 'react'; import { observable, action } from 'mobx'; -import { flatMap } from 'lodash'; +import { flatMap, uniqBy } from 'lodash'; import { FormattedMessage } from 'react-intl'; import Contracts from '~/contracts'; @@ -239,6 +239,8 @@ export default class AddressSelectStore { .filter((result) => result && !ZERO.test(result.address)); }) .then((results) => { + results = uniqBy(results, (result) => result.address); + this.registryValues = results .map((result) => { const lowercaseAddress = result.address.toLowerCase(); From 61a42cc7e4a93ba7c2097285f82505d5a854341b Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 16:59:33 +0100 Subject: [PATCH 27/37] reverse caching: pass API into middleware --- js/src/redux/middleware.js | 4 +- js/src/redux/providers/registry/middleware.js | 163 +++++++++--------- 2 files changed, 81 insertions(+), 86 deletions(-) diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js index 5cb635f7f2a..bffeddc98f1 100644 --- a/js/src/redux/middleware.js +++ b/js/src/redux/middleware.js @@ -33,7 +33,7 @@ export default function (api, browserHistory) { const certifications = new CertificationsMiddleware(); const routeMiddleware = routerMiddleware(browserHistory); const chain = new ChainMiddleware(); - const registry = new RegistryMiddleware(); + const registry = new RegistryMiddleware(api); const middleware = [ settings.toMiddleware(), @@ -41,7 +41,7 @@ export default function (api, browserHistory) { errors.toMiddleware(), certifications.toMiddleware(), chain.toMiddleware(), - registry.toMiddleware() + registry ]; return middleware.concat(status, routeMiddleware, thunk); diff --git a/js/src/redux/providers/registry/middleware.js b/js/src/redux/providers/registry/middleware.js index 87d9fbf7025..2c8b45d0e37 100644 --- a/js/src/redux/providers/registry/middleware.js +++ b/js/src/redux/providers/registry/middleware.js @@ -21,90 +21,85 @@ import registryABI from '~/contracts/abi/registry.json'; import { setReverse, startCachingReverses } from './actions'; -export default class RegistryMiddleware { - toMiddleware () { - return (store) => { - const api = Contracts.get()._api; - let contract, confirmedEvents, removedEvents, timeout, interval; - - let addressesToCheck = {}; - - const onLog = (log) => { - switch (log.event) { - case 'ReverseConfirmed': - addressesToCheck[log.params.reverse.value] = true; - - break; - case 'ReverseRemoved': - delete addressesToCheck[log.params.reverse.value]; - - break; - } - }; - - const checkReverses = () => { - Object - .keys(addressesToCheck) - .forEach((address) => { - contract - .instance - .reverse - .call({}, [ address ]) - .then((reverse) => store.dispatch(setReverse(address, reverse))); +export default (api) => (store) => { + let contract, confirmedEvents, removedEvents, timeout, interval; + + let addressesToCheck = {}; + + const onLog = (log) => { + switch (log.event) { + case 'ReverseConfirmed': + addressesToCheck[log.params.reverse.value] = true; + + break; + case 'ReverseRemoved': + delete addressesToCheck[log.params.reverse.value]; + + break; + } + }; + + const checkReverses = () => { + Object + .keys(addressesToCheck) + .forEach((address) => { + contract + .instance + .reverse + .call({}, [ address ]) + .then((reverse) => store.dispatch(setReverse(address, reverse))); + }); + + addressesToCheck = {}; + }; + + return (next) => (action) => { + switch (action.type) { + case 'initAll': + next(action); + store.dispatch(startCachingReverses()); + + break; + case 'startCachingReverses': + const { registry } = Contracts.get(); + + registry.getInstance() + .then((instance) => api.newContract(registryABI, instance.address)) + .then((_contract) => { + contract = _contract; + + confirmedEvents = subscribeToEvent(_contract, 'ReverseConfirmed'); + confirmedEvents.on('log', onLog); + + removedEvents = subscribeToEvent(_contract, 'ReverseRemoved'); + removedEvents.on('log', onLog); + + timeout = setTimeout(checkReverses, 5000); + interval = setInterval(checkReverses, 20000); + }) + .catch((err) => { + console.error('Failed to start caching reverses:', err); + throw err; }); - addressesToCheck = {}; - }; - - return (next) => (action) => { - switch (action.type) { - case 'initAll': - next(action); - store.dispatch(startCachingReverses()); - - break; - case 'startCachingReverses': - const { registry } = Contracts.get(); - - registry.getInstance() - .then((instance) => api.newContract(registryABI, instance.address)) - .then((_contract) => { - contract = _contract; - - confirmedEvents = subscribeToEvent(_contract, 'ReverseConfirmed'); - confirmedEvents.on('log', onLog); - - removedEvents = subscribeToEvent(_contract, 'ReverseRemoved'); - removedEvents.on('log', onLog); - - timeout = setTimeout(checkReverses, 5000); - interval = setInterval(checkReverses, 20000); - }) - .catch((err) => { - console.error('Failed to start caching reverses:', err); - throw err; - }); - - break; - case 'stopCachingReverses': - if (confirmedEvents) { - confirmedEvents.unsubscribe(); - } - if (removedEvents) { - removedEvents.unsubscribe(); - } - if (interval) { - clearInterval(interval); - } - if (timeout) { - clearTimeout(timeout); - } - - break; - default: - next(action); + break; + case 'stopCachingReverses': + if (confirmedEvents) { + confirmedEvents.unsubscribe(); } - }; - }; - } -} + if (removedEvents) { + removedEvents.unsubscribe(); + } + if (interval) { + clearInterval(interval); + } + if (timeout) { + clearTimeout(timeout); + } + + break; + default: + next(action); + } + }; +}; From a584c6396c21f36d496d5cb5f7d1581083401614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomusdrw@users.noreply.github.com> Date: Mon, 9 Jan 2017 17:01:09 +0100 Subject: [PATCH 28/37] Don't remove out of date local transactions (#4094) --- ethcore/src/miner/transaction_queue.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index b52ec797296..fe3af79dc28 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -828,6 +828,7 @@ impl TransactionQueue { let balance_check = max_time >> 3; // Clear transactions occupying the queue too long let invalid = self.by_hash.iter() + .filter(|&(_, ref tx)| !tx.origin.is_local()) .map(|(hash, tx)| (hash, tx, current_time.saturating_sub(tx.insertion_time))) .filter_map(|(hash, tx, time_diff)| { if time_diff > max_time { @@ -2624,7 +2625,7 @@ mod test { let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); // Insert all transactions - txq.add(tx1, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_account_details, &gas_estimator).unwrap(); txq.add(tx2, TransactionOrigin::External, 5, None, &default_account_details, &gas_estimator).unwrap(); txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_account_details, &gas_estimator).unwrap(); txq.add(tx4, TransactionOrigin::External, 0, None, &default_account_details, &gas_estimator).unwrap(); @@ -2635,9 +2636,9 @@ mod test { txq.remove_old(&default_account_details, 9 + super::DEFAULT_QUEUING_PERIOD); // then - assert_eq!(txq.top_transactions().len(), 1); + assert_eq!(txq.top_transactions().len(), 2); assert_eq!(txq.future_transactions().len(), 0); - assert_eq!(txq.top_transactions(), vec![tx3]); + assert_eq!(txq.top_transactions(), vec![tx1, tx3]); } } From cca7627fb805a39a5039f9edbea51114c242fe77 Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 17:49:01 +0100 Subject: [PATCH 29/37] reverse caching: PR grumbles --- js/src/redux/providers/registry/reducer.js | 4 ++++ js/src/ui/Form/AddressSelect/addressSelectStore.js | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/js/src/redux/providers/registry/reducer.js b/js/src/redux/providers/registry/reducer.js index 42663897a68..5c267d822bf 100644 --- a/js/src/redux/providers/registry/reducer.js +++ b/js/src/redux/providers/registry/reducer.js @@ -20,6 +20,10 @@ const initialState = { export default (state = initialState, action) => { if (action.type === 'setReverse') { + if (state.reverse[action.address] === action.reverse) { + return state; + } + return { ...state, reverse: { ...state.reverse, [ action.address ]: action.reverse } }; diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js index 1c93088d774..bdb7c1fb2cf 100644 --- a/js/src/ui/Form/AddressSelect/addressSelectStore.js +++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js @@ -36,6 +36,7 @@ export default class AddressSelectStore { if (query.length === 0 || query === '0x') { return null; } + const startsWithQuery = (s) => new RegExp('^' + query, 'i').test(s); let address; let name = this.reverse[query]; @@ -44,12 +45,8 @@ export default class AddressSelectStore { const addr = Object .keys(this.reverse) .find((addr) => { - if (addr.toLowerCase().slice(0, query.length) === query) { - return true; - } - const name = this.reverse[addr]; - return name.toLowerCase().slice(0, query.length) === query; + return startsWithQuery(addr) || (name && startsWithQuery(name)); }); if (addr) { From e86b955815c1dbde9a10187abf1b051a1fc63d5b Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Mon, 9 Jan 2017 18:33:53 +0100 Subject: [PATCH 30/37] Default to no auto-update. (#4092) * Default to no auto-update. * Fix test. --- parity/cli/config.full.toml | 4 +--- parity/cli/mod.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 088066e632f..67cba6a4859 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -2,7 +2,7 @@ mode = "last" mode_timeout = 300 mode_alarm = 3600 -auto_update = "critical" +auto_update = "none" release_track = "current" no_download = false no_consensus = false @@ -116,5 +116,3 @@ jit = false logging = "own_tx=trace" log_file = "/var/log/parity.log" color = true - - diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 7e5e4d1d85a..2c008623a85 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -542,7 +542,7 @@ mod tests { flag_mode: "last".into(), flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, - flag_auto_update: "critical".into(), + flag_auto_update: "none".into(), flag_release_track: "current".into(), flag_no_download: false, flag_no_consensus: false, From 67c298197597827620505e5e254cf6951240e73e Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 18:02:59 +0100 Subject: [PATCH 31/37] subscribeToEvent -> subscribeToEvents --- js/src/contracts/verification.js | 4 +- js/src/util/subscribe-to-event.js | 77 ------------------------ js/src/util/subscribe-to-events.js | 97 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 79 deletions(-) delete mode 100644 js/src/util/subscribe-to-event.js create mode 100644 js/src/util/subscribe-to-events.js diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js index 05b7ea35fab..af2f3b3a768 100644 --- a/js/src/contracts/verification.js +++ b/js/src/contracts/verification.js @@ -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/>. -import subscribeToEvent from '../util/subscribe-to-event'; +import subscribeToEvents from '../util/subscribe-to-events'; export const checkIfVerified = (contract, account) => { return contract.instance.certified.call({}, [account]); @@ -72,7 +72,7 @@ export const awaitPuzzle = (api, contract, account) => { return blockNumber(api) .then((block) => { return new Promise((resolve, reject) => { - const subscription = subscribeToEvent(contract, 'Puzzled', { + const subscription = subscribeToEvents(contract, 'Puzzled', { from: block.toNumber(), filter: (log) => log.params.who.value === account }); diff --git a/js/src/util/subscribe-to-event.js b/js/src/util/subscribe-to-event.js deleted file mode 100644 index 36d1f6d5557..00000000000 --- a/js/src/util/subscribe-to-event.js +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015, 2016 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/>. - -import EventEmitter from 'eventemitter3'; - -const defaults = { - from: 0, // TODO - to: 'latest', - timeout: null, - filter: () => true -}; - -const subscribeToEvent = (contract, name, opt = {}) => { - opt = Object.assign({}, defaults, opt); - - let subscription = null; - let timeout = null; - - const unsubscribe = () => { - if (subscription) { - contract.unsubscribe(subscription); - subscription = null; - } - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - }; - - const emitter = new EventEmitter(); - emitter.unsubscribe = unsubscribe; - - if (typeof opt.timeout === 'number') { - timeout = setTimeout(() => { - unsubscribe(); - emitter.emit('timeout'); - }, opt.timeout); - } - - const callback = (err, logs) => { - if (err) { - return emitter.emit('error', err); - } - for (let log of logs) { - if (opt.filter(log)) { - emitter.emit('log', log); - } - } - }; - - contract.subscribe(name, { - fromBlock: opt.from, toBlock: opt.to - }, callback) - .then((_subscription) => { - subscription = _subscription; - }) - .catch((err) => { - emitter.emit('error', err); - }); - - return emitter; -}; - -export default subscribeToEvent; diff --git a/js/src/util/subscribe-to-events.js b/js/src/util/subscribe-to-events.js new file mode 100644 index 00000000000..48c99027766 --- /dev/null +++ b/js/src/util/subscribe-to-events.js @@ -0,0 +1,97 @@ +// Copyright 2015, 2016 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/>. + +import EventEmitter from 'eventemitter3'; + +const defaults = { + from: 0, + to: 'latest', + interval: 5000, + filter: () => true +}; + +const subscribeToEvents = (contract, events, opt = {}) => { + const { api } = contract; + opt = Object.assign({}, defaults, opt); + + let filter = null; + let interval = null; + + const unsubscribe = () => { + if (filter) { + filter + .then((filterId) => { + return api.eth.uninstallFilter(filterId); + }) + .catch((err) => { + emitter.emit('error', err); + }); + filter = null; + } + if (interval) { + clearInterval(interval); + interval = null; + } + }; + + const emitter = new EventEmitter(); + emitter.unsubscribe = unsubscribe; + + const fetcher = (method, filterId) => () => { + api + .eth[method](filterId) + .then((logs) => { + logs = contract.parseEventLogs(logs); + + for (let log of logs) { + if (opt.filter(log)) { + emitter.emit('log', log); + emitter.emit(log.event, log); + } + } + }) + .catch((err) => { + emitter.emit('error', err); + }); + }; + + const signatures = events + .filter((event) => contract.instance[event]) + .map((event) => contract.instance[event].signature); + + filter = api.eth + .newFilter({ + fromBlock: opt.from, toBlock: opt.to, + address: contract.address, + topics: [signatures] + }) + .then((filterId) => { + fetcher('getFilterLogs', filterId)(); // fetch immediately + + const fetchChanges = fetcher('getFilterChanges', filterId); + interval = setInterval(fetchChanges, opt.interval); + + return filterId; + }) + .catch((err) => { + emitter.emit('error', err); + throw err; // reject Promise + }); + + return emitter; +}; + +export default subscribeToEvents; From f12937a21e3bf98c8690c576f0a05c31cc08c0db Mon Sep 17 00:00:00 2001 From: Jannis R <mail@jannisr.de> Date: Mon, 9 Jan 2017 19:03:19 +0100 Subject: [PATCH 32/37] reverse caching: use subscribeToEvents --- js/src/contracts/verification.js | 2 +- js/src/redux/providers/registry/middleware.js | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js index af2f3b3a768..3940e0e1814 100644 --- a/js/src/contracts/verification.js +++ b/js/src/contracts/verification.js @@ -72,7 +72,7 @@ export const awaitPuzzle = (api, contract, account) => { return blockNumber(api) .then((block) => { return new Promise((resolve, reject) => { - const subscription = subscribeToEvents(contract, 'Puzzled', { + const subscription = subscribeToEvents(contract, ['Puzzled'], { from: block.toNumber(), filter: (log) => log.params.who.value === account }); diff --git a/js/src/redux/providers/registry/middleware.js b/js/src/redux/providers/registry/middleware.js index 2c8b45d0e37..136a9eb0803 100644 --- a/js/src/redux/providers/registry/middleware.js +++ b/js/src/redux/providers/registry/middleware.js @@ -15,14 +15,14 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import Contracts from '~/contracts'; -import subscribeToEvent from '~/util/subscribe-to-event'; +import subscribeToEvents from '~/util/subscribe-to-events'; import registryABI from '~/contracts/abi/registry.json'; import { setReverse, startCachingReverses } from './actions'; export default (api) => (store) => { - let contract, confirmedEvents, removedEvents, timeout, interval; + let contract, subscription, timeout, interval; let addressesToCheck = {}; @@ -68,11 +68,8 @@ export default (api) => (store) => { .then((_contract) => { contract = _contract; - confirmedEvents = subscribeToEvent(_contract, 'ReverseConfirmed'); - confirmedEvents.on('log', onLog); - - removedEvents = subscribeToEvent(_contract, 'ReverseRemoved'); - removedEvents.on('log', onLog); + subscription = subscribeToEvents(_contract, ['ReverseConfirmed', 'ReverseRemoved']); + subscription.on('log', onLog); timeout = setTimeout(checkReverses, 5000); interval = setInterval(checkReverses, 20000); @@ -84,11 +81,8 @@ export default (api) => (store) => { break; case 'stopCachingReverses': - if (confirmedEvents) { - confirmedEvents.unsubscribe(); - } - if (removedEvents) { - removedEvents.unsubscribe(); + if (subscription) { + subscription.unsubscribe(); } if (interval) { clearInterval(interval); From d67ceec50c889f991a02b3718e924fc1009146c9 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan <arkady.paronyan@gmail.com> Date: Tue, 10 Jan 2017 12:11:32 +0100 Subject: [PATCH 33/37] Update registration after every write (#4102) --- util/network/src/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/network/src/connection.rs b/util/network/src/connection.rs index 22fb845f41b..40138a32c86 100644 --- a/util/network/src/connection.rs +++ b/util/network/src/connection.rs @@ -158,8 +158,8 @@ impl<Socket: GenericSocket> GenericConnection<Socket> { } if self.send_queue.is_empty() { self.interest.remove(Ready::writable()); - io.update_registration(self.token)?; } + io.update_registration(self.token)?; Ok(r) }) } From 5c5244911e0277be5eadf557a68ffaa9bf829be7 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan <arkady.paronyan@gmail.com> Date: Tue, 10 Jan 2017 12:22:28 +0100 Subject: [PATCH 34/37] No reorg limit for ancient blocks (#4099) --- sync/src/chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 6ae69bf74fd..d36cc26c9d3 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -583,7 +583,7 @@ impl ChainSync { if let (Some(ancient_block_hash), Some(ancient_block_number)) = (chain.ancient_block_hash, chain.ancient_block_number) { trace!(target: "sync", "Downloading old blocks from {:?} (#{}) till {:?} (#{:?})", ancient_block_hash, ancient_block_number, chain.first_block_hash, chain.first_block_number); - let mut downloader = BlockDownloader::new(true, &ancient_block_hash, ancient_block_number, pruning.state_history_size); + let mut downloader = BlockDownloader::new(true, &ancient_block_hash, ancient_block_number, None); if let Some(hash) = chain.first_block_hash { trace!(target: "sync", "Downloader target set to {:?}", hash); downloader.set_target(&hash); From be30c44179b499fad27cd0e9a36c4a6ba10eb170 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Tue, 10 Jan 2017 12:23:59 +0100 Subject: [PATCH 35/37] Validator/authority contract (#3937) * dir * simple validator list * stub validator contract * make the engine hold Weak<Client> instead of IoChannel * validator set factory * register weak client with ValidatorContract * check chain security * add address array to generator * register provider contract * update validator set on notify * add validator contract spec * simple list test * split update and contract test * contract change * use client in tendermint * fix deadlock * step duration in params * adapt tendermint tests * add storage fields to test spec * constructor spec * execute under wrong address * create under correct address * revert * validator contract constructor * move genesis block lookup * add removal ability to contract * validator contract adding validators * fix basic authority * validator changing test * more docs * update sync tests * remove env_logger * another env_logger * cameltoe * hold EngineClient instead of Client * add a comment about lock scope --- ethcore/res/authority_round.json | 10 +- ethcore/res/basic_authority.json | 6 +- ethcore/res/tendermint.json | 10 +- ethcore/res/validator_contract.json | 42 ++++ ethcore/src/client/client.rs | 33 +-- ethcore/src/client/mod.rs | 2 +- ethcore/src/client/test_client.rs | 28 +-- ethcore/src/client/traits.rs | 21 +- ethcore/src/engines/authority_round.rs | 90 ++++---- ethcore/src/engines/basic_authority.rs | 49 ++-- ethcore/src/engines/mod.rs | 16 +- ethcore/src/engines/tendermint/mod.rs | 205 ++++++++-------- ethcore/src/engines/tendermint/params.rs | 29 +-- ethcore/src/engines/tendermint/transition.rs | 34 +-- ethcore/src/engines/validator_set/contract.rs | 218 ++++++++++++++++++ ethcore/src/engines/validator_set/mod.rs | 46 ++++ .../src/engines/validator_set/simple_list.rs | 68 ++++++ ethcore/src/miner/miner.rs | 14 +- ethcore/src/service.rs | 13 +- ethcore/src/spec/spec.rs | 7 +- json/src/spec/authority_round.rs | 8 +- json/src/spec/basic_authority.rs | 8 +- json/src/spec/engine.rs | 5 +- json/src/spec/mod.rs | 2 + json/src/spec/tendermint.rs | 12 +- json/src/spec/validator_set.rs | 47 ++++ scripts/contractABI.js | 21 +- sync/src/tests/consensus.rs | 13 +- util/io/src/service.rs | 2 +- 29 files changed, 747 insertions(+), 312 deletions(-) create mode 100644 ethcore/res/validator_contract.json create mode 100644 ethcore/src/engines/validator_set/contract.rs create mode 100644 ethcore/src/engines/validator_set/mod.rs create mode 100644 ethcore/src/engines/validator_set/simple_list.rs create mode 100644 json/src/spec/validator_set.rs diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index 2dd38c7559d..ac7eb504172 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -6,10 +6,12 @@ "gasLimitBoundDivisor": "0x0400", "stepDuration": 1, "startStep": 2, - "authorities" : [ - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" - ] + "validators": { + "list": [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } } } }, diff --git a/ethcore/res/basic_authority.json b/ethcore/res/basic_authority.json index 623590bfa36..6b9f4c0ed2d 100644 --- a/ethcore/res/basic_authority.json +++ b/ethcore/res/basic_authority.json @@ -5,7 +5,9 @@ "params": { "gasLimitBoundDivisor": "0x0400", "durationLimit": "0x0d", - "authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + "validators": { + "list": ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + } } } }, @@ -17,7 +19,7 @@ }, "genesis": { "seal": { - "generic": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + "generic": "0xc180" }, "difficulty": "0x20000", "author": "0x0000000000000000000000000000000000000000", diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index da62448e568..a1262fa335c 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,10 +4,12 @@ "tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "authorities" : [ - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" - ] + "validators" : { + "list": [ + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ] + } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json new file mode 100644 index 00000000000..4bf120b5470 --- /dev/null +++ b/ethcore/res/validator_contract.json @@ -0,0 +1,42 @@ +{ + "name": "TestValidatorContract", + "engine": { + "basicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators": { + "contract": "0x0000000000000000000000000000000000000005" + } + } + } + }, + "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 } } } }, + "0000000000000000000000000000000000000005": { + "balance": "1", + "constructor": "0x60a06040819052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60609081527382a978b3f5962a5b0957d9ee9eef472ee55b42f1608052600080546002825581805290927f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639182019291905b828111156100a25782518254600160a060020a031916600160a060020a0390911617825560209092019160019091019061006d565b5b506100cd9291505b808211156100c9578054600160a060020a03191681556001016100ab565b5090565b505034610000575b610332806100e46000396000f300606060405263ffffffff60e060020a60003504166335aa2e4481146100455780634d238c8e14610071578063b7ab4db51461008c578063f94e1867146100f4575b610000565b3461000057610055600435610106565b60408051600160a060020a039092168252519081900360200190f35b346100005761008a600160a060020a0360043516610136565b005b34610000576100996101ad565b60408051602080825283518183015283519192839290830191858101910280838382156100e1575b8051825260208311156100e157601f1990920191602091820191016100c1565b5050509050019250505060405180910390f35b346100005761008a600435610217565b005b600081815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b60008054806001018281815481835581811511610178576000838152602090206101789181019083015b808211156101745760008155600101610160565b5090565b5b505050916000526020600020900160005b8154600160a060020a038086166101009390930a92830292021916179055505b50565b604080516020818101835260008083528054845181840281018401909552808552929392909183018282801561020c57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116101ee575b505050505090505b90565b6000805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600082815481101561000057906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a031602179055506000600160008054905003815481101561000057906000526020600020900160005b6101000a815490600160a060020a03021916905560008054809190600190038154818355818115116102fd576000838152602090206102fd9181019083015b808211156101745760008155600101610160565b5090565b5b505050505b505600a165627a7a72305820d742dd391941c1c255f0e1187ffa5b1e783219264fb10196018aefa379f5638b0029" + }, + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + } +} diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f57b1248ab0..0a5bad75af8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -53,7 +53,7 @@ use verification::queue::BlockQueue; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, - MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, + MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, PruningInfo, }; use client::Error as ClientError; @@ -1315,11 +1315,6 @@ impl BlockChainClient for Client { } } - fn broadcast_consensus_message(&self, message: Bytes) { - self.notify(|notify| notify.broadcast(message.clone())); - } - - fn signing_network_id(&self) -> Option<u64> { self.engine.signing_network_id(&self.latest_env_info()) } @@ -1414,16 +1409,6 @@ impl MiningBlockChainClient for Client { &self.factories.vm } - fn update_sealing(&self) { - self.miner.update_sealing(self) - } - - fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") - } - } - fn broadcast_proposal_block(&self, block: SealedBlock) { self.notify(|notify| { notify.new_blocks( @@ -1471,6 +1456,22 @@ impl MiningBlockChainClient for Client { } } +impl EngineClient for Client { + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + + fn broadcast_consensus_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } +} + impl MayPanic for Client { fn on_panic<F>(&self, closure: F) where F: OnPanicListener { self.panic_handler.on_panic(closure); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index d8e5f19e562..8af20f51689 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -28,7 +28,7 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain pub use self::error::Error; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; -pub use self::traits::{BlockChainClient, MiningBlockChainClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient}; pub use self::traits::ProvingBlockChainClient; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index da7425a3d3a..551b5243749 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -24,7 +24,7 @@ use devtools::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, PendingTransaction, Action}; use blockchain::TreeRoute; use client::{ - BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, + BlockChainClient, MiningBlockChainClient, EngineClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, }; use db::{NUM_COLUMNS, COL_STATE}; @@ -372,16 +372,6 @@ impl MiningBlockChainClient for TestBlockChainClient { } fn broadcast_proposal_block(&self, _block: SealedBlock) {} - - fn update_sealing(&self) { - self.miner.update_sealing(self) - } - - fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") - } - } } impl BlockChainClient for TestBlockChainClient { @@ -699,8 +689,6 @@ impl BlockChainClient for TestBlockChainClient { self.spec.engine.handle_message(&message).unwrap(); } - fn broadcast_consensus_message(&self, _message: Bytes) {} - fn ready_transactions(&self) -> Vec<PendingTransaction> { self.miner.ready_transactions(self.chain_info().best_block_number) } @@ -727,3 +715,17 @@ impl BlockChainClient for TestBlockChainClient { fn registry_address(&self, _name: String) -> Option<Address> { None } } + +impl EngineClient for TestBlockChainClient { + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + + fn broadcast_consensus_message(&self, _message: Bytes) {} +} diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7e39d5002e7..57151519834 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -208,9 +208,6 @@ pub trait BlockChainClient : Sync + Send { /// Queue conensus engine message. fn queue_consensus_message(&self, message: Bytes); - /// Used by PoA to communicate with peers. - fn broadcast_consensus_message(&self, message: Bytes); - /// List all transactions that are allowed into the next block. fn ready_transactions(&self) -> Vec<PendingTransaction>; @@ -294,12 +291,6 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; - /// Used by PoA to try sealing on period change. - fn update_sealing(&self); - - /// Used by PoA to submit gathered signatures. - fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>); - /// Broadcast a block proposal. fn broadcast_proposal_block(&self, block: SealedBlock); @@ -310,6 +301,18 @@ pub trait MiningBlockChainClient: BlockChainClient { fn latest_schedule(&self) -> Schedule; } +/// Client facilities used by internally sealing Engines. +pub trait EngineClient: MiningBlockChainClient { + /// Make a new block and seal it. + fn update_sealing(&self); + + /// Submit a seal for a block in the mining queue. + fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>); + + /// Broadcast a consensus message to the network. + fn broadcast_consensus_message(&self, message: Bytes); +} + /// Extended client interface for providing proofs of the state. pub trait ProvingBlockChainClient: BlockChainClient { /// Prove account storage at a specific block id. diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dca84e3a22c..0986a5aa7b7 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -32,11 +32,12 @@ use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; -use service::ClientIoMessage; +use io::{IoContext, IoHandler, TimerToken, IoService}; use transaction::SignedTransaction; use env_info::EnvInfo; use builtin::Builtin; +use client::{Client, EngineClient}; +use super::validator_set::{ValidatorSet, new_validator_set}; use state::CleanupMode; /// `AuthorityRound` params. @@ -46,14 +47,12 @@ pub struct AuthorityRoundParams { pub gas_limit_bound_divisor: U256, /// Time to wait before next block or authority switching. pub step_duration: Duration, - /// Valid authorities. - pub authorities: Vec<Address>, - /// Number of authorities. - pub authority_n: usize, /// Block reward. pub block_reward: U256, /// Starting step, pub start_step: Option<u64>, + /// Valid validators. + pub validators: ethjson::spec::ValidatorSet, } impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { @@ -61,8 +60,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { AuthorityRoundParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), step_duration: Duration::from_secs(p.step_duration.into()), - authority_n: p.authorities.len(), - authorities: p.authorities.into_iter().map(Into::into).collect::<Vec<_>>(), + validators: p.validators, block_reward: p.block_reward.map_or_else(U256::zero, Into::into), start_step: p.start_step.map(Into::into), } @@ -73,14 +71,17 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { /// mainnet chains in the Olympic, Frontier and Homestead eras. pub struct AuthorityRound { params: CommonParams, - our_params: AuthorityRoundParams, + gas_limit_bound_divisor: U256, + block_reward: U256, + step_duration: Duration, builtins: BTreeMap<Address, Builtin>, transition_service: IoService<()>, - message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>, step: AtomicUsize, proposed: AtomicBool, - account_provider: Mutex<Option<Arc<AccountProvider>>>, + client: RwLock<Option<Weak<EngineClient>>>, + account_provider: Mutex<Arc<AccountProvider>>, password: RwLock<Option<String>>, + validators: Box<ValidatorSet + Send + Sync>, } fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> { @@ -109,14 +110,17 @@ impl AuthorityRound { let engine = Arc::new( AuthorityRound { params: params, - our_params: our_params, + gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, + block_reward: our_params.block_reward, + step_duration: our_params.step_duration, builtins: builtins, transition_service: IoService::<()>::start()?, - message_channel: Mutex::new(None), step: AtomicUsize::new(initial_step), proposed: AtomicBool::new(false), - account_provider: Mutex::new(None), + client: RwLock::new(None), + account_provider: Mutex::new(Arc::new(AccountProvider::transient_provider())), password: RwLock::new(None), + validators: new_validator_set(our_params.validators), }); // Do not initialize timeouts for tests. if should_timeout { @@ -128,7 +132,7 @@ impl AuthorityRound { fn remaining_step_duration(&self) -> Duration { let now = unix_now(); - let step_end = self.our_params.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); + let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); if step_end > now { step_end - now } else { @@ -136,13 +140,12 @@ impl AuthorityRound { } } - fn step_proposer(&self, step: usize) -> &Address { - let p = &self.our_params; - p.authorities.get(step % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed") + fn step_proposer(&self, step: usize) -> Address { + self.validators.get(step) } fn is_step_proposer(&self, step: usize, address: &Address) -> bool { - self.step_proposer(step) == address + self.step_proposer(step) == *address } } @@ -187,10 +190,9 @@ impl Engine for AuthorityRound { fn step(&self) { self.step.fetch_add(1, AtomicOrdering::SeqCst); self.proposed.store(false, AtomicOrdering::SeqCst); - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)), - Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)), + if let Some(ref weak) = *self.client.read() { + if let Some(c) = weak.upgrade() { + c.update_sealing(); } } } @@ -211,7 +213,7 @@ impl Engine for AuthorityRound { header.set_difficulty(parent.difficulty().clone()); header.set_gas_limit({ let gas_limit = parent.gas_limit().clone(); - let bound_divisor = self.our_params.gas_limit_bound_divisor; + let bound_divisor = self.gas_limit_bound_divisor; if gas_limit < gas_floor_target { min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into()) } else { @@ -221,8 +223,7 @@ impl Engine for AuthorityRound { } fn is_sealer(&self, author: &Address) -> Option<bool> { - let p = &self.our_params; - Some(p.authorities.contains(author)) + Some(self.validators.contains(author)) } /// Attempt to seal the block internally. @@ -234,18 +235,13 @@ impl Engine for AuthorityRound { let header = block.header(); let step = self.step.load(AtomicOrdering::SeqCst); if self.is_step_proposer(step, header.author()) { - if let Some(ref ap) = *self.account_provider.lock() { - // Account should be permanently unlocked, otherwise sealing will fail. - if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { - trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); - self.proposed.store(true, AtomicOrdering::SeqCst); - let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]; - return Seal::Regular(rlps); - } else { - warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); - } + let ref ap = *self.account_provider.lock(); + if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { + trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); + self.proposed.store(true, AtomicOrdering::SeqCst); + return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); } else { - warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); + warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); } } else { trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); @@ -255,12 +251,9 @@ impl Engine for AuthorityRound { /// Apply the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) { - let reward = self.our_params.block_reward; let fields = block.fields_mut(); - // Bestow block reward - fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty); - + fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty); // Commit state so that we can actually figure out the state root. if let Err(e) = fields.state.commit() { warn!("Encountered error on state commit: {}", e); @@ -285,7 +278,7 @@ impl Engine for AuthorityRound { // 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 ok_sig = verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?; + let ok_sig = verify_address(&self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?; if ok_sig { Ok(()) } else { @@ -310,7 +303,7 @@ impl Engine for AuthorityRound { Err(EngineError::DoubleVote(header.author().clone()))?; } - let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; + let gas_limit_divisor = self.gas_limit_bound_divisor; let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { @@ -341,8 +334,9 @@ impl Engine for AuthorityRound { } } - fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) { - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak<Client>) { + *self.client.write() = Some(client.clone()); + self.validators.register_call_contract(client); } fn set_signer(&self, _address: Address, password: String) { @@ -350,7 +344,7 @@ impl Engine for AuthorityRound { } fn register_account_provider(&self, account_provider: Arc<AccountProvider>) { - *self.account_provider.lock() = Some(account_provider); + *self.account_provider.lock() = account_provider; } } @@ -458,7 +452,7 @@ mod tests { let engine = Spec::new_test_round().engine; let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); - // Two authorities. + // 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()); @@ -477,7 +471,7 @@ mod tests { let engine = Spec::new_test_round().engine; let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); - // Two authorities. + // 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()); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 8112a574e15..61e25e58f8f 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -16,6 +16,8 @@ //! A blockchain engine that supports a basic, non-BFT proof-of-authority. +use std::sync::Weak; +use util::*; use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; @@ -28,26 +30,23 @@ use evm::Schedule; use ethjson; use header::Header; use transaction::SignedTransaction; - -use util::*; +use client::Client; +use super::validator_set::{ValidatorSet, new_validator_set}; /// `BasicAuthority` params. #[derive(Debug, PartialEq)] pub struct BasicAuthorityParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// Block duration. - pub duration_limit: u64, /// Valid signatories. - pub authorities: HashSet<Address>, + pub validators: ethjson::spec::ValidatorSet, } impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams { fn from(p: ethjson::spec::BasicAuthorityParams) -> Self { BasicAuthorityParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - duration_limit: p.duration_limit.into(), - authorities: p.authorities.into_iter().map(Into::into).collect::<HashSet<_>>(), + validators: p.validators, } } } @@ -56,10 +55,11 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams { /// mainnet chains in the Olympic, Frontier and Homestead eras. pub struct BasicAuthority { params: CommonParams, - our_params: BasicAuthorityParams, + gas_limit_bound_divisor: U256, builtins: BTreeMap<Address, Builtin>, account_provider: Mutex<Option<Arc<AccountProvider>>>, password: RwLock<Option<String>>, + validators: Box<ValidatorSet + Send + Sync>, } impl BasicAuthority { @@ -67,8 +67,9 @@ impl BasicAuthority { pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap<Address, Builtin>) -> Self { BasicAuthority { params: params, - our_params: our_params, + gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, builtins: builtins, + validators: new_validator_set(our_params.validators), account_provider: Mutex::new(None), password: RwLock::new(None), } @@ -95,7 +96,7 @@ impl Engine for BasicAuthority { header.set_difficulty(parent.difficulty().clone()); header.set_gas_limit({ let gas_limit = parent.gas_limit().clone(); - let bound_divisor = self.our_params.gas_limit_bound_divisor; + let bound_divisor = self.gas_limit_bound_divisor; if gas_limit < gas_floor_target { min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into()) } else { @@ -105,22 +106,22 @@ impl Engine for BasicAuthority { } fn is_sealer(&self, author: &Address) -> Option<bool> { - Some(self.our_params.authorities.contains(author)) + Some(self.validators.contains(author)) } /// Attempt to seal the block internally. - /// - /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will - /// be returned. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); - let message = header.bare_hash(); - // account should be pernamently unlocked, otherwise sealing will fail - if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) { - return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); - } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + let author = header.author(); + if self.validators.contains(author) { + let message = header.bare_hash(); + // account should be pernamently unlocked, otherwise sealing will fail + if let Ok(signature) = ap.sign(*author, self.password.read().clone(), message) { + return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + } } } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); @@ -145,7 +146,7 @@ impl Engine for BasicAuthority { // 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.our_params.authorities.contains(&signer) { + if !self.validators.contains(&signer) { return Err(BlockError::InvalidSeal)?; } Ok(()) @@ -161,7 +162,7 @@ impl Engine for BasicAuthority { if header.difficulty() != parent.difficulty() { return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() }))) } - let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; + let gas_limit_divisor = self.gas_limit_bound_divisor; let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { @@ -179,6 +180,10 @@ impl Engine for BasicAuthority { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn register_client(&self, client: Weak<Client>) { + self.validators.register_call_contract(client); + } + fn set_signer(&self, _address: Address, password: String) { *self.password.write() = Some(password); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index a3e57dd65f7..b82fce1b83f 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -21,6 +21,7 @@ mod instant_seal; mod basic_authority; mod authority_round; mod tendermint; +mod validator_set; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; @@ -28,6 +29,7 @@ pub use self::basic_authority::BasicAuthority; pub use self::authority_round::AuthorityRound; pub use self::tendermint::Tendermint; +use std::sync::Weak; use util::*; use account_provider::AccountProvider; use block::ExecutedBlock; @@ -36,13 +38,12 @@ use env_info::EnvInfo; use error::Error; use spec::CommonParams; use evm::Schedule; -use io::IoChannel; -use service::ClientIoMessage; use header::Header; use transaction::SignedTransaction; use ethereum::ethash; use blockchain::extras::BlockDetails; use views::HeaderView; +use client::Client; /// Voting errors. #[derive(Debug)] @@ -207,14 +208,15 @@ pub trait Engine : Sync + Send { /// Register an account which signs consensus messages. fn set_signer(&self, _address: Address, _password: String) {} - /// Stops any services that the may hold the Engine and makes it safe to drop. - fn stop(&self) {} - - /// Add a channel for communication with Client which can be used for sealing. - fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {} + /// Add Client which can be used for sealing, querying the state and sending messages. + fn register_client(&self, _client: Weak<Client>) {} /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {} + /// Trigger next step of the consensus engine. fn step(&self) {} + + /// Stops any services that the may hold the Engine and makes it safe to drop. + fn stop(&self) {} } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index becc9d9c066..5fe9a0248d6 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -27,8 +27,10 @@ mod transition; mod params; mod vote_collector; +use std::sync::Weak; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; +use client::{Client, EngineClient}; use error::{Error, BlockError}; use header::Header; use builtin::Builtin; @@ -43,9 +45,9 @@ use engines::{Engine, Seal, EngineError}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; -use io::{IoService, IoChannel}; -use service::ClientIoMessage; use state::CleanupMode; +use io::IoService; +use super::validator_set::{ValidatorSet, new_validator_set}; use self::message::*; use self::transition::TransitionHandler; use self::params::TendermintParams; @@ -75,9 +77,11 @@ pub type BlockHash = H256; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, - our_params: TendermintParams, + gas_limit_bound_divisor: U256, builtins: BTreeMap<Address, Builtin>, step_service: IoService<Step>, + client: RwLock<Option<Weak<EngineClient>>>, + block_reward: U256, /// Address to be used as authority. authority: RwLock<Address>, /// Password used for signing messages. @@ -90,8 +94,6 @@ pub struct Tendermint { step: RwLock<Step>, /// Vote accumulator. votes: VoteCollector, - /// Channel for updating the sealing. - message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>, /// Used to sign messages and proposals. account_provider: Mutex<Option<Arc<AccountProvider>>>, /// Message for the last PoLC. @@ -100,6 +102,8 @@ pub struct Tendermint { last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock<Option<H256>>, + /// Set used to determine the current validators. + validators: Box<ValidatorSet + Send + Sync>, } impl Tendermint { @@ -108,53 +112,49 @@ impl Tendermint { let engine = Arc::new( Tendermint { params: params, - our_params: our_params, + gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, builtins: builtins, + client: RwLock::new(None), step_service: IoService::<Step>::start()?, + block_reward: our_params.block_reward, authority: RwLock::new(Address::default()), password: RwLock::new(None), height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), votes: VoteCollector::new(), - message_channel: Mutex::new(None), account_provider: Mutex::new(None), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), proposal: RwLock::new(None), + validators: new_validator_set(our_params.validators), }); - let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; + let handler = TransitionHandler::new(Arc::downgrade(&engine), our_params.timeouts); engine.step_service.register_handler(Arc::new(handler))?; Ok(engine) } fn update_sealing(&self) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "UpdateSealing message sent."), - Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err), + if let Some(ref weak) = *self.client.read() { + if let Some(c) = weak.upgrade() { + c.update_sealing(); } } } fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) { - Ok(_) => trace!(target: "poa", "SubmitSeal message sent."), - Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err), + if let Some(ref weak) = *self.client.read() { + if let Some(c) = weak.upgrade() { + c.submit_seal(block_hash, seal); } } } fn broadcast_message(&self, message: Bytes) { - let channel = self.message_channel.lock().clone(); - if let Some(ref channel) = channel { - match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), + if let Some(ref weak) = *self.client.read() { + if let Some(c) = weak.upgrade() { + c.broadcast_consensus_message(message); } - } else { - warn!(target: "poa", "broadcast_message: No IoChannel available."); } } @@ -265,23 +265,22 @@ impl Tendermint { } fn is_authority(&self, address: &Address) -> bool { - self.our_params.authorities.contains(address) + self.validators.contains(address) } fn is_above_threshold(&self, n: usize) -> bool { - n > self.our_params.authority_n * 2/3 + n > self.validators.count() * 2/3 } /// Check if address is a proposer for given round. fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> { - let ref p = self.our_params; let proposer_nonce = height + round; trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce); - let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); - if proposer == address { + let proposer = self.validators.get(proposer_nonce); + if proposer == *address { Ok(()) } else { - Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + Err(EngineError::NotProposer(Mismatch { expected: proposer, found: address.clone() })) } } @@ -405,7 +404,7 @@ impl Engine for Tendermint { header.set_difficulty(parent.difficulty().clone()); header.set_gas_limit({ let gas_limit = parent.gas_limit().clone(); - let bound_divisor = self.our_params.gas_limit_bound_divisor; + let bound_divisor = self.gas_limit_bound_divisor; if gas_limit < gas_floor_target { min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into()) } else { @@ -472,12 +471,9 @@ impl Engine for Tendermint { /// Apply the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) { - let reward = self.our_params.block_reward; let fields = block.fields_mut(); - // Bestow block reward - fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty); - + fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty); // Commit state so that we can actually figure out the state root. if let Err(e) = fields.state.commit() { warn!("Encountered error on state commit: {}", e); @@ -522,7 +518,7 @@ impl Engine for Tendermint { Some(a) => a, None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), }; - if !self.our_params.authorities.contains(&address) { + if !self.validators.contains(&address) { Err(EngineError::NotAuthorized(address.to_owned()))? } @@ -555,7 +551,7 @@ impl Engine for Tendermint { Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; } - let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; + let gas_limit_divisor = self.gas_limit_bound_divisor; let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { @@ -658,9 +654,9 @@ impl Engine for Tendermint { self.to_step(next_step); } - fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) { - trace!(target: "poa", "Register the IoChannel."); - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak<Client>) { + *self.client.write() = Some(client.clone()); + self.validators.register_call_contract(client); } fn register_account_provider(&self, account_provider: Arc<AccountProvider>) { @@ -671,21 +667,20 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; - use io::{IoContext, IoHandler}; use block::*; use error::{Error, BlockError}; use header::Header; - use io::IoChannel; use env_info::EnvInfo; + use client::chain_notify::ChainNotify; + use miner::MinerService; use tests::helpers::*; use account_provider::AccountProvider; - use service::ClientIoMessage; use spec::Spec; use engines::{Engine, EngineError, Seal}; use super::*; use super::message::*; - /// Accounts inserted with "0" and "1" are authorities. First proposer is "0". + /// Accounts inserted with "0" and "1" are validators. First proposer is "0". fn setup() -> (Spec, Arc<AccountProvider>) { let tap = Arc::new(AccountProvider::transient_provider()); let spec = Spec::new_test_tendermint(); @@ -693,13 +688,13 @@ mod tests { (spec, tap) } - fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec<Bytes>) { + fn propose_default(spec: &Spec, proposer: Address) -> (ClosedBlock, Vec<Bytes>) { let mut db_result = get_temp_state_db(); let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); - let b = b.close_and_lock(); + let b = b.close(); if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) { (b, seal) } else { @@ -707,7 +702,7 @@ mod tests { } } - fn vote<F>(engine: &Arc<Engine>, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> { + fn vote<F>(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> { let mi = message_info_rlp(height, round, step, block_hash); let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi); engine.handle_message(&m).unwrap(); @@ -725,37 +720,26 @@ mod tests { ] } - fn precommit_signatures(tap: &Arc<AccountProvider>, height: Height, round: Round, bare_hash: Option<H256>, v1: H160, v2: H160) -> Bytes { - let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash); - ::rlp::encode(&vec![ - H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()), - H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap()) - ]).to_vec() - } - fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address { let addr = tap.insert_account(acc.sha3(), acc).unwrap(); tap.unlock_account_permanently(addr, acc.into()).unwrap(); addr } - fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Arc<Engine>, acc: &str) -> Address { + fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Engine, acc: &str) -> Address { let addr = insert_and_unlock(tap, acc); engine.set_signer(addr.clone(), acc.into()); addr } - struct TestIo { - received: RwLock<Vec<ClientIoMessage>> + #[derive(Default)] + struct TestNotify { + messages: RwLock<Vec<Bytes>>, } - impl TestIo { - fn new() -> Arc<Self> { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) } - } - - impl IoHandler<ClientIoMessage> for TestIo { - fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) { - self.received.write().push(net_message.clone()); + impl ChainNotify for TestNotify { + fn broadcast(&self, data: Vec<u8>) { + self.messages.write().push(data); } } @@ -879,10 +863,10 @@ mod tests { fn can_generate_seal() { let (spec, tap) = setup(); - let proposer = insert_and_register(&tap, &spec.engine, "1"); + let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1"); let (b, seal) = propose_default(&spec, proposer); - assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); + assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok()); spec.engine.stop(); } @@ -890,10 +874,10 @@ mod tests { fn can_recognize_proposal() { let (spec, tap) = setup(); - let proposer = insert_and_register(&tap, &spec.engine, "1"); + let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1"); let (b, seal) = propose_default(&spec, proposer); - let sealed = b.seal(spec.engine.as_ref(), seal).unwrap(); + let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap(); assert!(spec.engine.is_proposal(sealed.header())); spec.engine.stop(); } @@ -903,8 +887,8 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine.clone(); - let v0 = insert_and_register(&tap, &engine, "0"); - let v1 = insert_and_register(&tap, &engine, "1"); + let v0 = insert_and_register(&tap, engine.as_ref(), "0"); + let v1 = insert_and_register(&tap, engine.as_ref(), "1"); let h = 0; let r = 0; @@ -913,57 +897,74 @@ mod tests { let (b, _) = propose_default(&spec, v1.clone()); let proposal = Some(b.header().bare_hash()); - // Register IoHandler remembers messages. - let test_io = TestIo::new(); - let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>))); - engine.register_message_channel(channel); + let client = generate_dummy_client(0); + let notify = Arc::new(TestNotify::default()); + client.add_notify(notify.clone()); + engine.register_client(Arc::downgrade(&client)); - let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + let prevote_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + let precommit_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); + let prevote_future = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); // Relays all valid present and future messages. - assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current))); - assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current))); - assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future))); + assert!(notify.messages.read().contains(&prevote_current)); + assert!(notify.messages.read().contains(&precommit_current)); + assert!(notify.messages.read().contains(&prevote_future)); engine.stop(); } #[test] fn seal_submission() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - - let v0 = insert_and_register(&tap, &engine, "0"); - let v1 = insert_and_register(&tap, &engine, "1"); + use ethkey::{Generator, Random}; + use types::transaction::{Transaction, Action}; + use client::BlockChainClient; - let h = 1; - let r = 0; + let client = generate_dummy_client_with_spec_and_data(Spec::new_test_tendermint, 0, 0, &[]); + let engine = client.engine(); + let tap = Arc::new(AccountProvider::transient_provider()); + + // Accounts for signing votes. + let v0 = insert_and_unlock(&tap, "0"); + let v1 = insert_and_unlock(&tap, "1"); + + let notify = Arc::new(TestNotify::default()); + engine.register_account_provider(tap.clone()); + client.add_notify(notify.clone()); + engine.register_client(Arc::downgrade(&client)); - // Register IoHandler remembers messages. - let test_io = TestIo::new(); - let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>))); - engine.register_message_channel(channel); + let keypair = Random.generate().unwrap(); + let transaction = Transaction { + action: Action::Create, + value: U256::zero(), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::zero(), + nonce: U256::zero(), + }.sign(keypair.secret(), None); + client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap(); + + client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap(); // Propose - let (b, mut seal) = propose_default(&spec, v1.clone()); - let proposal = Some(b.header().bare_hash()); + let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash()); + // Propose timeout engine.step(); + let h = 1; + let r = 0; + // Prevote. - vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); + assert_eq!(client.chain_info().best_block_number, 0); + // Last precommit. + vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + assert_eq!(client.chain_info().best_block_number, 1); - assert!(first ^ second); engine.stop(); } } diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index e31182ae99c..aad3a4b7a75 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -18,38 +18,22 @@ use ethjson; use super::transition::TendermintTimeouts; -use util::{Address, Uint, U256}; +use util::{U256, Uint}; use time::Duration; /// `Tendermint` params. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// List of authorities. - pub authorities: Vec<Address>, - /// Number of authorities. - pub authority_n: usize, + /// List of validators. + pub validators: ethjson::spec::ValidatorSet, /// Timeout durations for different steps. pub timeouts: TendermintTimeouts, /// Block reward. pub block_reward: U256, } -impl Default for TendermintParams { - fn default() -> Self { - let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; - let val_n = authorities.len(); - TendermintParams { - gas_limit_bound_divisor: 0x0400.into(), - authorities: authorities, - authority_n: val_n, - block_reward: U256::zero(), - timeouts: TendermintTimeouts::default(), - } - } -} - fn to_duration(ms: ethjson::uint::Uint) -> Duration { let ms: usize = ms.into(); Duration::milliseconds(ms as i64) @@ -57,13 +41,10 @@ fn to_duration(ms: ethjson::uint::Uint) -> Duration { impl From<ethjson::spec::TendermintParams> for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { - let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect(); - let val_n = val.len(); let dt = TendermintTimeouts::default(); TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - authorities: val, - authority_n: val_n, + validators: p.validators, timeouts: TendermintTimeouts { propose: p.timeout_propose.map_or(dt.propose, to_duration), prevote: p.timeout_prevote.map_or(dt.prevote, to_duration), diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 83b390d7443..62ae7b03f77 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -23,7 +23,17 @@ use super::{Tendermint, Step}; use engines::Engine; pub struct TransitionHandler { - pub engine: Weak<Tendermint>, + engine: Weak<Tendermint>, + timeouts: TendermintTimeouts, +} + +impl TransitionHandler { + pub fn new(engine: Weak<Tendermint>, timeouts: TendermintTimeouts) -> Self { + TransitionHandler { + engine: engine, + timeouts: timeouts, + } + } } /// Base timeout of each step in ms. @@ -67,9 +77,7 @@ fn set_timeout(io: &IoContext<Step>, timeout: Duration) { impl IoHandler<Step> for TransitionHandler { fn initialize(&self, io: &IoContext<Step>) { - if let Some(engine) = self.engine.upgrade() { - set_timeout(io, engine.our_params.timeouts.propose) - } + set_timeout(io, self.timeouts.propose) } fn timeout(&self, _io: &IoContext<Step>, timer: TimerToken) { @@ -81,16 +89,14 @@ impl IoHandler<Step> for TransitionHandler { } fn message(&self, io: &IoContext<Step>, next_step: &Step) { - if let Some(engine) = self.engine.upgrade() { - if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) { - warn!(target: "poa", "Could not remove consensus timer {}.", io_err) - } - match *next_step { - Step::Propose => set_timeout(io, engine.our_params.timeouts.propose), - Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote), - Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), - Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), - }; + if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) { + warn!(target: "poa", "Could not remove consensus timer {}.", io_err) } + match *next_step { + Step::Propose => set_timeout(io, self.timeouts.propose), + Step::Prevote => set_timeout(io, self.timeouts.prevote), + Step::Precommit => set_timeout(io, self.timeouts.precommit), + Step::Commit => set_timeout(io, self.timeouts.commit), + }; } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs new file mode 100644 index 00000000000..b8b63112c10 --- /dev/null +++ b/ethcore/src/engines/validator_set/contract.rs @@ -0,0 +1,218 @@ +// Copyright 2015, 2016 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 maintained in a contract. + +use std::sync::Weak; +use util::*; +use client::{Client, BlockChainClient}; +use client::chain_notify::ChainNotify; +use super::ValidatorSet; +use super::simple_list::SimpleList; + +/// The validator contract should have the following interface: +/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] +pub struct ValidatorContract { + address: Address, + validators: RwLock<SimpleList>, + provider: RwLock<Option<provider::Contract>>, +} + +impl ValidatorContract { + pub fn new(contract_address: Address) -> Self { + ValidatorContract { + address: contract_address, + validators: Default::default(), + provider: RwLock::new(None), + } + } + + /// Queries the state and updates the set of validators. + pub fn update(&self) { + if let Some(ref provider) = *self.provider.read() { + match provider.get_validators() { + Ok(new) => { + debug!(target: "engine", "Set of validators obtained: {:?}", new); + *self.validators.write() = SimpleList::new(new); + }, + 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.") + } + } +} + +/// Checks validators on every block. +impl ChainNotify for ValidatorContract { + fn new_blocks( + &self, + _imported: Vec<H256>, + _: Vec<H256>, + _: Vec<H256>, + _: Vec<H256>, + _: Vec<H256>, + _: Vec<Bytes>, + _duration: u64) { + self.update(); + } +} + +impl ValidatorSet for Arc<ValidatorContract> { + fn contains(&self, address: &Address) -> bool { + self.validators.read().contains(address) + } + + fn get(&self, nonce: usize) -> Address { + self.validators.read().get(nonce).clone() + } + + fn count(&self) -> usize { + self.validators.read().count() + } + + fn register_call_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(); + } +} + +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}; + + pub struct Contract { + contract: ethabi::Contract, + address: util::Address, + do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, 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 { + 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<_>>() })) + } + } +} + +#[cfg(test)] +mod tests { + use util::*; + use spec::Spec; + use account_provider::AccountProvider; + use transaction::{Transaction, Action}; + use client::{BlockChainClient, EngineClient}; + use miner::MinerService; + use tests::helpers::generate_dummy_client_with_spec_and_data; + use super::super::ValidatorSet; + use super::ValidatorContract; + + #[test] + fn fetches_validators() { + let client = generate_dummy_client_with_spec_and_data(Spec::new_validator_contract, 0, 0, &[]); + let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); + vc.register_call_contract(Arc::downgrade(&client)); + vc.update(); + assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + } + + #[test] + fn changes_validators() { + let tap = Arc::new(AccountProvider::transient_provider()); + let v0 = tap.insert_account("1".sha3(), "").unwrap(); + let v1 = tap.insert_account("0".sha3(), "").unwrap(); + let spec_factory = || { + let spec = Spec::new_validator_contract(); + spec.engine.register_account_provider(tap.clone()); + spec + }; + let client = generate_dummy_client_with_spec_and_data(spec_factory, 0, 0, &[]); + client.engine().register_client(Arc::downgrade(&client)); + let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap(); + + client.miner().set_engine_signer(v1, "".into()).unwrap(); + // Remove "1" validator. + let tx = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 500_000.into(), + action: Action::Call(validator_contract), + value: 0.into(), + data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(), + }.sign(&"1".sha3(), None); + client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 1); + // Add "1" validator back in. + let tx = Transaction { + nonce: 1.into(), + gas_price: 0.into(), + gas: 500_000.into(), + action: Action::Call(validator_contract), + value: 0.into(), + data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(), + }.sign(&"1".sha3(), None); + client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); + client.update_sealing(); + // The transaction is not yet included so still unable to seal. + assert_eq!(client.chain_info().best_block_number, 1); + + // Switch to the validator that is still there. + client.miner().set_engine_signer(v0, "".into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 2); + // Switch back to the added validator, since the state is updated. + client.miner().set_engine_signer(v1, "".into()).unwrap(); + let tx = Transaction { + nonce: 2.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(&"1".sha3(), None); + client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap(); + client.update_sealing(); + // Able to seal again. + assert_eq!(client.chain_info().best_block_number, 3); + } +} diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs new file mode 100644 index 00000000000..566dccabbe6 --- /dev/null +++ b/ethcore/src/engines/validator_set/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2015, 2016 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 lists. + +mod simple_list; +mod contract; + +use std::sync::Weak; +use util::{Address, Arc}; +use ethjson::spec::ValidatorSet as ValidatorSpec; +use client::Client; +use self::simple_list::SimpleList; +use self::contract::ValidatorContract; + +/// Creates a validator set from spec. +pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> { + match spec { + ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), + ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), + } +} + +pub trait ValidatorSet { + /// Checks if a given address is a validator. + fn contains(&self, address: &Address) -> bool; + /// Draws an validator nonce modulo number of validators. + fn get(&self, nonce: usize) -> Address; + /// Returns the current number of validators. + fn count(&self) -> usize; + /// Allows blockchain state access. + fn register_call_contract(&self, _client: Weak<Client>) {} +} diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs new file mode 100644 index 00000000000..9244ce477a6 --- /dev/null +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -0,0 +1,68 @@ +// Copyright 2015, 2016 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/>. + +/// Preconfigured validator list. + +use util::Address; +use super::ValidatorSet; + +#[derive(Debug, PartialEq, Eq, Default)] +pub struct SimpleList { + validators: Vec<Address>, + validator_n: usize, +} + +impl SimpleList { + pub fn new(validators: Vec<Address>) -> Self { + SimpleList { + validator_n: validators.len(), + validators: validators, + } + } +} + +impl ValidatorSet for SimpleList { + fn contains(&self, address: &Address) -> bool { + self.validators.contains(address) + } + + fn get(&self, 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 { + self.validator_n + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use util::Address; + use super::super::ValidatorSet; + use super::SimpleList; + + #[test] + fn simple_list() { + 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); + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3ffb790b130..7a848b21da7 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -764,9 +764,17 @@ impl MinerService for Miner { if let Some(ref ap) = self.accounts { ap.sign(address.clone(), Some(password.clone()), Default::default())?; } - let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); - *self.author.write() = address; + // Limit the scope of the locks. + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + } + // -------------------------------------------------------------------------- + // | NOTE Code below may require author and sealing_work locks | + // | (some `Engine`s call `EngineClient.update_sealing()`) |. + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- self.engine.set_signer(address, password); } Ok(()) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b0f1df4206f..d55a228e41b 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -20,7 +20,7 @@ use util::*; use io::*; use spec::Spec; use error::*; -use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify}; +use client::{Client, ClientConfig, ChainNotify}; use miner::Miner; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; @@ -46,12 +46,6 @@ pub enum ClientIoMessage { FeedBlockChunk(H256, Bytes), /// Take a snapshot for the block with given number. TakeSnapshot(u64), - /// Trigger sealing update (useful for internal sealing). - UpdateSealing, - /// Submit seal (useful for internal sealing). - SubmitSeal(H256, Vec<Bytes>), - /// Broadcast a message to the network. - BroadcastMessage(Bytes), /// New consensus message received. NewMessage(Bytes) } @@ -114,7 +108,7 @@ impl ClientService { }); io_service.register_handler(client_io)?; - spec.engine.register_message_channel(io_service.channel()); + spec.engine.register_client(Arc::downgrade(&client)); let stop_guard = ::devtools::StopGuard::new(); run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share()); @@ -221,9 +215,6 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler { debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e); } }, - ClientIoMessage::UpdateSealing => self.client.update_sealing(), - ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), - ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()), ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { trace!(target: "poa", "Invalid message received: {}", e); }, diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f9a4b6f6a87..2f957098d9e 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -337,9 +337,14 @@ impl Spec { pub fn new_instant() -> Spec { load_bundled!("instant_seal") } /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). - /// Accounts with secrets "0".sha3() and "1".sha3() are the authorities. + /// Accounts with secrets "0".sha3() and "1".sha3() are the validators. pub fn new_test_round() -> Self { load_bundled!("authority_round") } + /// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators. + /// Accounts with secrets "0".sha3() and "1".sha3() are initially the validators. + /// Second validator can be removed with "0xf94e18670000000000000000000000000000000000000000000000000000000000000001" and added back in using "0x4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1". + pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") } + /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). /// Account "0".sha3() and "1".sha3() are a authorities. pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") } diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index b302cc6922d..330a9b665ef 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -17,7 +17,7 @@ //! Authority params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -29,7 +29,7 @@ pub struct AuthorityRoundParams { #[serde(rename="stepDuration")] pub step_duration: Uint, /// Valid authorities - pub authorities: Vec<Address>, + pub validators: ValidatorSet, /// Block reward. #[serde(rename="blockReward")] pub block_reward: Option<Uint>, @@ -57,7 +57,9 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "stepDuration": "0x02", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "validators": { + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, "blockReward": "0x50", "startStep" : 24 } diff --git a/json/src/spec/basic_authority.rs b/json/src/spec/basic_authority.rs index 0e388f1eb4e..b6ff3f47fe3 100644 --- a/json/src/spec/basic_authority.rs +++ b/json/src/spec/basic_authority.rs @@ -17,7 +17,7 @@ //! Authority params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -29,7 +29,7 @@ pub struct BasicAuthorityParams { #[serde(rename="durationLimit")] pub duration_limit: Uint, /// Valid authorities - pub authorities: Vec<Address>, + pub validators: ValidatorSet, } /// Authority engine deserialization. @@ -50,7 +50,9 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "durationLimit": "0x0d", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "validators" : { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } } }"#; diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index b0c34b2ea9f..d437b06bab2 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -16,10 +16,7 @@ //! Engine deserialization. -use spec::Ethash; -use spec::BasicAuthority; -use spec::AuthorityRound; -use spec::Tendermint; +use super::{Ethash, BasicAuthority, AuthorityRound, Tendermint}; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index d923be0695c..2f4b06f319f 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -25,6 +25,7 @@ pub mod seal; pub mod engine; pub mod state; pub mod ethash; +pub mod validator_set; pub mod basic_authority; pub mod authority_round; pub mod tendermint; @@ -38,6 +39,7 @@ pub use self::seal::{Seal, Ethereum, AuthorityRoundSeal, TendermintSeal}; pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; +pub use self::validator_set::ValidatorSet; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; pub use self::tendermint::{Tendermint, TendermintParams}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index b821b945c16..ede6f52e5f1 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -17,7 +17,7 @@ //! Tendermint params deserialization. use uint::Uint; -use hash::Address; +use super::ValidatorSet; /// Tendermint params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -25,8 +25,8 @@ pub struct TendermintParams { /// Gas limit divisor. #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, - /// Valid authorities - pub authorities: Vec<Address>, + /// Valid validators. + pub validators: ValidatorSet, /// Propose step timeout in milliseconds. #[serde(rename="timeoutPropose")] pub timeout_propose: Option<Uint>, @@ -60,8 +60,10 @@ mod tests { fn tendermint_deserialization() { let s = r#"{ "params": { - "gasLimitBoundDivisor": "0x400", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "gasLimitBoundDivisor": "0x0400", + "validators": { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, "blockReward": "0x50" } }"#; diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs new file mode 100644 index 00000000000..47acd7c3665 --- /dev/null +++ b/json/src/spec/validator_set.rs @@ -0,0 +1,47 @@ +// Copyright 2015, 2016 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 deserialization. + +use hash::Address; + +/// Different ways of specifying validators. +#[derive(Debug, PartialEq, Deserialize)] +pub enum ValidatorSet { + /// A simple list of authorities. + #[serde(rename="list")] + List(Vec<Address>), + /// Address of a contract that indicates the list of authorities. + #[serde(rename="contract")] + Contract(Address), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::validator_set::ValidatorSet; + + #[test] + fn validator_set_deserialization() { + let s = r#"[{ + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, { + "contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }]"#; + + let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap(); + } +} diff --git a/scripts/contractABI.js b/scripts/contractABI.js index e6c8e923a94..9a8225ccd9e 100644 --- a/scripts/contractABI.js +++ b/scripts/contractABI.js @@ -24,7 +24,7 @@ String.prototype.toSnake = function(){ function makeContractFile(name, json, prefs) { return `// Autogenerated from JSON contract definition using Rust contract convertor. - +#![allow(unused_imports)] use std::string::String; use std::result::Result; use std::fmt; @@ -39,10 +39,10 @@ function convertContract(name, json, prefs) { return `${prefs._pub ? "pub " : ""}struct ${name} { contract: ethabi::Contract, address: util::Address, - do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + 'static>, + do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static>, } impl ${name} { - pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + 'static { + pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static { ${name} { contract: ethabi::Contract::new(ethabi::Interface::load(b"${JSON.stringify(json.filter(a => a.type == 'function')).replaceAll('"', '\\"')}").expect("JSON is autogenerated; qed")), address: address, @@ -112,8 +112,10 @@ function mapReturnType(name, type, _prefs) { return "String"; if (type == "bytes") return "Vec<u8>"; + if (type == "address[]") + return "Vec<util::Address>"; - console.log(`Unsupported argument type: ${type} (${name})`); + console.log(`Unsupported return type: ${type} (${name})`); } function convertToken(name, type, _prefs) { @@ -163,8 +165,10 @@ function tokenType(name, type, _prefs) { return `${name}.to_string()`; if (type == "bytes") return `${name}.to_bytes()`; + if (type == "address[]") + return `${name}.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>())`; - // ERROR - unsupported + console.log(`Unsupported return type: ${type} (${name})`); } function tokenCoerce(name, type, _prefs) { @@ -190,6 +194,8 @@ function tokenCoerce(name, type, _prefs) { return `${name}`; if (type == "bytes") return `${name}`; + if (type == "address[]") + return `${name}.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>()`; console.log(`Unsupported return type: ${type} (${name})`); } @@ -217,6 +223,7 @@ function convertFunction(json, _prefs) { }`; } -jsonabi = [{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_hard","type":"bool"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"release","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"},{"name":"o_critical","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"build","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"client","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"fork","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"hard","type":"bool"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"clientOwner","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"grandOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"},{"name":"_critical","type":"bool"}],"name":"addRelease","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"checksum","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"gas","type":"uint256"}],"name":"TransactionProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":false,"name":"success","type":"bool"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"},{"indexed":true,"name":"name","type":"bytes32"},{"indexed":false,"name":"spec","type":"bytes32"},{"indexed":false,"name":"hard","type":"bool"}],"name":"ForkProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkAcceptedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkRejectedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRatified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"forkBlock","type":"uint32"},{"indexed":false,"name":"release","type":"bytes32"},{"indexed":false,"name":"track","type":"uint8"},{"indexed":false,"name":"semver","type":"uint24"},{"indexed":true,"name":"critical","type":"bool"}],"name":"ReleaseAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":true,"name":"platform","type":"bytes32"},{"indexed":false,"name":"checksum","type":"bytes32"}],"name":"ChecksumAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"ClientAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"}],"name":"ClientRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"now","type":"address"}],"name":"ClientOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"now","type":"bool"}],"name":"ClientRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old","type":"address"},{"indexed":false,"name":"now","type":"address"}],"name":"OwnerChanged","type":"event"}]; +let jsonabi = [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]; -makeContractFile("Operations", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}}); +let out = makeContractFile("Contract", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}, "_sync": true}); +console.log(`${out}`); diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 85538687077..ea8bd970d97 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -16,7 +16,7 @@ use util::*; use io::{IoHandler, IoContext, IoChannel}; -use ethcore::client::{BlockChainClient, Client, MiningBlockChainClient}; +use ethcore::client::{BlockChainClient, Client}; use ethcore::service::ClientIoMessage; use ethcore::spec::Spec; use ethcore::miner::MinerService; @@ -33,9 +33,6 @@ struct TestIoHandler { impl IoHandler<ClientIoMessage> for TestIoHandler { fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) { match *net_message { - ClientIoMessage::UpdateSealing => self.client.update_sealing(), - ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), - ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()), ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { panic!("Invalid message received: {}", e); }, @@ -75,8 +72,8 @@ fn authority_round() { // Push transaction to both clients. Only one of them gets lucky to produce a block. net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); - net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); - net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); // exchange statuses @@ -140,8 +137,8 @@ fn tendermint() { trace!(target: "poa", "Peer 0 is {}.", s0.address()); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); trace!(target: "poa", "Peer 1 is {}.", s1.address()); - net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); - net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain)); + net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain)); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); // Exhange statuses diff --git a/util/io/src/service.rs b/util/io/src/service.rs index 6f5c870048a..ed63600f2d5 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -391,7 +391,7 @@ impl<Message> IoChannel<Message> where Message: Send + Clone + Sync + 'static { } Ok(()) } - /// Create a new channel to connected to event loop. + /// Create a new channel disconnected from an event loop. pub fn disconnected() -> IoChannel<Message> { IoChannel { channel: None, From 78566cf9bfc9b50c14abfd3b762cdf2293b49d46 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Tue, 10 Jan 2017 11:41:00 +0000 Subject: [PATCH 36/37] [ci skip] js-precompiled 20170110-113807 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 923418a2c85..43c7f9aa1cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#fbc7864393ebbc78ea8f7bc4729f2ac3bdcb9a0e" +source = "git+https://github.com/ethcore/js-precompiled.git#7ff9567a47a66f312ca9fa83fc7a9ba23905d96c" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index d5a35ad89b9..d09f865d747 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.178", + "version": "0.2.179", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From ae7619431b4331bb5fa359e76195734c56fe64c9 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Tue, 10 Jan 2017 13:19:18 +0100 Subject: [PATCH 37/37] Fix Signer (#4104) --- .../transactionPendingFormConfirm.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js index 45eb3e5dd76..5a93cef0d93 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js @@ -247,13 +247,13 @@ class TransactionPendingFormConfirm extends Component { } } -function mapStateToProps (initState, initProps) { - const { accounts } = initState.personal; +function mapStateToProps (_, initProps) { const { address } = initProps; - const account = accounts[address] || {}; + return (state) => { + const { accounts } = state.personal; + const account = accounts[address] || {}; - return () => { return { account }; }; }