Perf and fixes
This commit is contained in:
parent
ee4f9da385
commit
50e0221dd1
@ -176,7 +176,7 @@
|
|||||||
"geopattern": "1.2.3",
|
"geopattern": "1.2.3",
|
||||||
"isomorphic-fetch": "2.2.1",
|
"isomorphic-fetch": "2.2.1",
|
||||||
"js-sha3": "0.5.5",
|
"js-sha3": "0.5.5",
|
||||||
"keythereum": "0.4.3",
|
"keythereum": "0.4.6",
|
||||||
"lodash": "4.17.2",
|
"lodash": "4.17.2",
|
||||||
"loglevel": "1.4.1",
|
"loglevel": "1.4.1",
|
||||||
"marked": "0.3.6",
|
"marked": "0.3.6",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { keythereum } from '../ethkey';
|
import { createKeyObject, decryptPrivateKey } from '../ethkey';
|
||||||
|
|
||||||
export default class Account {
|
export default class Account {
|
||||||
constructor (persist, data) {
|
constructor (persist, data) {
|
||||||
@ -31,12 +31,14 @@ export default class Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isValidPassword (password) {
|
isValidPassword (password) {
|
||||||
try {
|
return decryptPrivateKey(this._keyObject, password)
|
||||||
keythereum.recover(Buffer.from(password), this._keyObject);
|
.then((privateKey) => {
|
||||||
return true;
|
if (!privateKey) {
|
||||||
} catch (e) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get address () {
|
get address () {
|
||||||
@ -68,21 +70,23 @@ export default class Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decryptPrivateKey (password) {
|
decryptPrivateKey (password) {
|
||||||
return keythereum.recover(Buffer.from(password), this._keyObject);
|
return decryptPrivateKey(this._keyObject, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword (key, password) {
|
||||||
|
return createKeyObject(key, password).then((keyObject) => {
|
||||||
|
this._keyObject = keyObject;
|
||||||
|
|
||||||
|
this._persist();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromPrivateKey (persist, key, password) {
|
static fromPrivateKey (persist, key, password) {
|
||||||
const iv = keythereum.crypto.randomBytes(16);
|
return createKeyObject(key, password).then((keyObject) => {
|
||||||
const salt = keythereum.crypto.randomBytes(32);
|
|
||||||
|
|
||||||
// Keythereum will fail if `password` is an empty string
|
|
||||||
password = Buffer.from(password);
|
|
||||||
|
|
||||||
const keyObject = keythereum.dump(password, key, salt, iv);
|
|
||||||
|
|
||||||
const account = new Account(persist, { keyObject });
|
const account = new Account(persist, { keyObject });
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON () {
|
toJSON () {
|
||||||
|
@ -38,14 +38,22 @@ export default class Accounts {
|
|||||||
|
|
||||||
create (secret, password) {
|
create (secret, password) {
|
||||||
const privateKey = Buffer.from(secret.slice(2), 'hex');
|
const privateKey = Buffer.from(secret.slice(2), 'hex');
|
||||||
const account = Account.fromPrivateKey(this.persist, privateKey, password);
|
|
||||||
|
return Account.fromPrivateKey(this.persist, privateKey, password)
|
||||||
|
.then((account) => {
|
||||||
|
const { address } = account;
|
||||||
|
|
||||||
|
if (this._store.find((account) => account.address === address)) {
|
||||||
|
throw new Error(`Account ${address} already exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
this._store.push(account);
|
this._store.push(account);
|
||||||
this.lastAddress = account.address;
|
this.lastAddress = address;
|
||||||
|
|
||||||
this.persist();
|
this.persist();
|
||||||
|
|
||||||
return account.address;
|
return account.address;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
set lastAddress (value) {
|
set lastAddress (value) {
|
||||||
@ -73,16 +81,15 @@ export default class Accounts {
|
|||||||
remove (address, password) {
|
remove (address, password) {
|
||||||
address = address.toLowerCase();
|
address = address.toLowerCase();
|
||||||
|
|
||||||
const index = this._store.findIndex((account) => account.address === address);
|
const account = this.get(address);
|
||||||
|
|
||||||
if (index === -1) {
|
if (!account) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = this._store[index];
|
return account.isValidPassword(password)
|
||||||
|
.then((isValid) => {
|
||||||
if (!account.isValidPassword(password)) {
|
if (!isValid) {
|
||||||
console.log('invalid password');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +97,24 @@ export default class Accounts {
|
|||||||
this.lastAddress = NULL_ADDRESS;
|
this.lastAddress = NULL_ADDRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.removeUnsafe(address);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUnsafe (address) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
const index = this._store.findIndex((account) => account.address === address);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._store.splice(index, 1);
|
this._store.splice(index, 1);
|
||||||
|
|
||||||
this.persist();
|
this.persist();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapArray (mapper) {
|
mapArray (mapper) {
|
||||||
|
@ -14,31 +14,34 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Allow a web worker in the browser, with a fallback for Node.js
|
import workerPool from './workerPool';
|
||||||
const hasWebWorkers = typeof Worker !== 'undefined';
|
|
||||||
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
|
|
||||||
: require('./worker').KeyWorker;
|
|
||||||
|
|
||||||
// Local accounts should never be used outside of the browser
|
export function createKeyObject (key, password) {
|
||||||
export let keythereum = null;
|
return workerPool.getWorker().action('createKeyObject', { key, password })
|
||||||
|
.then((obj) => JSON.parse(obj));
|
||||||
|
}
|
||||||
|
|
||||||
if (hasWebWorkers) {
|
export function decryptPrivateKey (keyObject, password) {
|
||||||
require('keythereum/dist/keythereum');
|
return workerPool.getWorker()
|
||||||
|
.action('decryptPrivateKey', { keyObject, password })
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (privateKey) {
|
||||||
|
return Buffer.from(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
keythereum = window.keythereum;
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function phraseToAddress (phrase) {
|
export function phraseToAddress (phrase) {
|
||||||
return phraseToWallet(phrase).then((wallet) => wallet.address);
|
return phraseToWallet(phrase)
|
||||||
|
.then((wallet) => wallet.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function phraseToWallet (phrase) {
|
export function phraseToWallet (phrase) {
|
||||||
return new Promise((resolve, reject) => {
|
return workerPool.getWorker().action('phraseToWallet', phrase);
|
||||||
const worker = new KeyWorker();
|
}
|
||||||
|
|
||||||
worker.postMessage(phrase);
|
export function verifySecret (secret) {
|
||||||
worker.onmessage = ({ data }) => {
|
return workerPool.getWorker().action('verifySecret', secret);
|
||||||
resolve(data);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,37 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { keccak_256 as keccak256 } from 'js-sha3';
|
|
||||||
import secp256k1 from 'secp256k1/js';
|
import secp256k1 from 'secp256k1/js';
|
||||||
|
import { keccak_256 as keccak256 } from 'js-sha3';
|
||||||
|
|
||||||
|
const isWorker = typeof self !== 'undefined';
|
||||||
|
|
||||||
// Stay compatible between environments
|
// Stay compatible between environments
|
||||||
if (typeof self !== 'object') {
|
if (!isWorker) {
|
||||||
const scope = typeof global === 'undefined' ? window : global;
|
const scope = typeof global === 'undefined' ? window : global;
|
||||||
|
|
||||||
scope.self = scope;
|
scope.self = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bytesToHex (bytes) {
|
// keythereum should never be used outside of the browser
|
||||||
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join('');
|
let keythereum = null;
|
||||||
|
|
||||||
|
if (isWorker) {
|
||||||
|
require('keythereum/dist/keythereum');
|
||||||
|
|
||||||
|
keythereum = self.keythereum;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic ported from /ethkey/src/brain.rs
|
function route ({ action, payload }) {
|
||||||
function phraseToWallet (phrase) {
|
if (action in actions) {
|
||||||
|
return actions[action](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
phraseToWallet (phrase) {
|
||||||
let secret = keccak256.array(phrase);
|
let secret = keccak256.array(phrase);
|
||||||
|
|
||||||
for (let i = 0; i < 16384; i++) {
|
for (let i = 0; i < 16384; i++) {
|
||||||
@ -59,13 +74,47 @@ function phraseToWallet (phrase) {
|
|||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
verifySecret (secret) {
|
||||||
|
const key = Buffer.from(secret.slice(2), 'hex');
|
||||||
|
|
||||||
|
return secp256k1.privateKeyVerify(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
createKeyObject ({ key, password }) {
|
||||||
|
key = Buffer.from(key);
|
||||||
|
password = Buffer.from(password);
|
||||||
|
|
||||||
|
const iv = keythereum.crypto.randomBytes(16);
|
||||||
|
const salt = keythereum.crypto.randomBytes(32);
|
||||||
|
const keyObject = keythereum.dump(password, key, salt, iv);
|
||||||
|
|
||||||
|
return JSON.stringify(keyObject);
|
||||||
|
},
|
||||||
|
|
||||||
|
decryptPrivateKey ({ keyObject, password }) {
|
||||||
|
password = Buffer.from(password);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const key = keythereum.recover(password, keyObject);
|
||||||
|
|
||||||
|
// Convert to array to safely send from the worker
|
||||||
|
return Array.from(key);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function bytesToHex (bytes) {
|
||||||
|
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.onmessage = function ({ data }) {
|
self.onmessage = function ({ data }) {
|
||||||
const wallet = phraseToWallet(data);
|
const result = route(data);
|
||||||
|
|
||||||
postMessage(wallet);
|
postMessage(result);
|
||||||
close();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Emulate a web worker in Node.js
|
// Emulate a web worker in Node.js
|
||||||
@ -73,9 +122,9 @@ class KeyWorker {
|
|||||||
postMessage (data) {
|
postMessage (data) {
|
||||||
// Force async
|
// Force async
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const wallet = phraseToWallet(data);
|
const result = route(data);
|
||||||
|
|
||||||
this.onmessage({ data: wallet });
|
this.onmessage({ data: result });
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
js/src/api/local/ethkey/workerPool.js
Normal file
61
js/src/api/local/ethkey/workerPool.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Allow a web worker in the browser, with a fallback for Node.js
|
||||||
|
const hasWebWorkers = typeof Worker !== 'undefined';
|
||||||
|
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
|
||||||
|
: require('./worker').KeyWorker;
|
||||||
|
|
||||||
|
class WorkerContainer {
|
||||||
|
busy = false;
|
||||||
|
_worker = new KeyWorker();
|
||||||
|
|
||||||
|
action (action, payload) {
|
||||||
|
if (this.busy) {
|
||||||
|
throw new Error('Cannot issue an action on a busy worker!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = true;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._worker.postMessage({ action, payload });
|
||||||
|
this._worker.onmessage = ({ data }) => {
|
||||||
|
this.busy = false;
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WorkerPool {
|
||||||
|
pool = [];
|
||||||
|
|
||||||
|
getWorker () {
|
||||||
|
let container = this.pool.find((container) => !container.busy);
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
container = new WorkerContainer();
|
||||||
|
|
||||||
|
this.pool.push(container);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new WorkerPool();
|
@ -19,7 +19,7 @@ import accounts from './accounts';
|
|||||||
import transactions from './transactions';
|
import transactions from './transactions';
|
||||||
import { Middleware } from '../transport';
|
import { Middleware } from '../transport';
|
||||||
import { inNumber16 } from '../format/input';
|
import { inNumber16 } from '../format/input';
|
||||||
import { phraseToWallet, phraseToAddress } from './ethkey';
|
import { phraseToWallet, phraseToAddress, verifySecret } from './ethkey';
|
||||||
import { randomPhrase } from '@parity/wordlist';
|
import { randomPhrase } from '@parity/wordlist';
|
||||||
|
|
||||||
export default class LocalAccountsMiddleware extends Middleware {
|
export default class LocalAccountsMiddleware extends Middleware {
|
||||||
@ -57,6 +57,21 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_changePassword', ([address, oldPassword, newPassword]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
return account.decryptPrivateKey(oldPassword)
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
account.changePassword(privateKey, newPassword);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_checkRequest', ([id]) => {
|
register('parity_checkRequest', ([id]) => {
|
||||||
return transactions.hash(id) || Promise.resolve(null);
|
return transactions.hash(id) || Promise.resolve(null);
|
||||||
});
|
});
|
||||||
@ -84,6 +99,17 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_newAccountFromSecret', ([secret, password]) => {
|
||||||
|
return verifySecret(secret)
|
||||||
|
.then((isValid) => {
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('Invalid secret key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts.create(secret, password);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_setAccountMeta', ([address, meta]) => {
|
register('parity_setAccountMeta', ([address, meta]) => {
|
||||||
accounts.get(address).meta = meta;
|
accounts.get(address).meta = meta;
|
||||||
|
|
||||||
@ -127,6 +153,12 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
return accounts.remove(address, password);
|
return accounts.remove(address, password);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_testPassword', ([address, password]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
return account.isValidPassword(password);
|
||||||
|
});
|
||||||
|
|
||||||
register('signer_confirmRequest', ([id, modify, password]) => {
|
register('signer_confirmRequest', ([id, modify, password]) => {
|
||||||
const {
|
const {
|
||||||
gasPrice,
|
gasPrice,
|
||||||
@ -137,9 +169,13 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
data
|
data
|
||||||
} = Object.assign(transactions.get(id), modify);
|
} = Object.assign(transactions.get(id), modify);
|
||||||
|
|
||||||
return this
|
const account = accounts.get(from);
|
||||||
.rpcRequest('parity_nextNonce', [from])
|
|
||||||
.then((nonce) => {
|
return Promise.all([
|
||||||
|
this.rpcRequest('parity_nextNonce', [from]),
|
||||||
|
account.decryptPrivateKey(password)
|
||||||
|
])
|
||||||
|
.then(([nonce, privateKey]) => {
|
||||||
const tx = new EthereumTx({
|
const tx = new EthereumTx({
|
||||||
nonce,
|
nonce,
|
||||||
to,
|
to,
|
||||||
@ -148,9 +184,8 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
gasPrice: inNumber16(gasPrice),
|
gasPrice: inNumber16(gasPrice),
|
||||||
value: inNumber16(value)
|
value: inNumber16(value)
|
||||||
});
|
});
|
||||||
const account = accounts.get(from);
|
|
||||||
|
|
||||||
tx.sign(account.decryptPrivateKey(password));
|
tx.sign(privateKey);
|
||||||
|
|
||||||
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
||||||
|
|
||||||
|
@ -80,12 +80,16 @@ export default class JsonRpcBase extends EventEmitter {
|
|||||||
const res = middleware.handle(method, params);
|
const res = middleware.handle(method, params);
|
||||||
|
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
|
// If `res` isn't a promise, we need to wrap it
|
||||||
|
return Promise.resolve(res)
|
||||||
|
.then((res) => {
|
||||||
const result = this._wrapSuccessResult(res);
|
const result = this._wrapSuccessResult(res);
|
||||||
const json = this.encode(method, params);
|
const json = this.encode(method, params);
|
||||||
|
|
||||||
Logging.send(method, params, { json, result });
|
Logging.send(method, params, { json, result });
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
|||||||
import { Form, Input, IdentityIcon } from '~/ui';
|
import { Form, Input, IdentityIcon } from '~/ui';
|
||||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
import { RefreshIcon } from '~/ui/Icons';
|
import { RefreshIcon } from '~/ui/Icons';
|
||||||
|
import Loading from '~/ui/Loading';
|
||||||
|
|
||||||
import ChangeVault from '../ChangeVault';
|
import ChangeVault from '../ChangeVault';
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
@ -170,7 +171,9 @@ export default class CreateAccount extends Component {
|
|||||||
const { accounts } = this.state;
|
const { accounts } = this.state;
|
||||||
|
|
||||||
if (!accounts) {
|
if (!accounts) {
|
||||||
return null;
|
return (
|
||||||
|
<Loading className={ styles.selector } size={ 1 } />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const identities = Object
|
const identities = Object
|
||||||
@ -205,6 +208,14 @@ export default class CreateAccount extends Component {
|
|||||||
createIdentities = () => {
|
createIdentities = () => {
|
||||||
const { createStore } = this.props;
|
const { createStore } = this.props;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
accounts: null,
|
||||||
|
selectedAddress: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
createStore.setAddress('');
|
||||||
|
createStore.setPhrase('');
|
||||||
|
|
||||||
return createStore
|
return createStore
|
||||||
.createIdentities()
|
.createIdentities()
|
||||||
.then((accounts) => {
|
.then((accounts) => {
|
||||||
|
@ -58,12 +58,13 @@ describe('modals/CreateAccount/NewAccount', () => {
|
|||||||
return instance.componentWillMount();
|
return instance.componentWillMount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates initial accounts', () => {
|
it('resets the accounts', () => {
|
||||||
expect(Object.keys(instance.state.accounts).length).to.equal(7);
|
expect(instance.state.accounts).to.be.null;
|
||||||
|
// expect(Object.keys(instance.state.accounts).length).to.equal(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the initial selected value', () => {
|
it('resets the initial selected value', () => {
|
||||||
expect(instance.state.selectedAddress).to.equal(Object.keys(instance.state.accounts)[0]);
|
expect(instance.state.selectedAddress).to.equal('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -69,7 +69,7 @@ export default class Store {
|
|||||||
return !(this.nameError || this.walletFileError);
|
return !(this.nameError || this.walletFileError);
|
||||||
|
|
||||||
case 'fromNew':
|
case 'fromNew':
|
||||||
return !(this.nameError || this.passwordRepeatError);
|
return !(this.nameError || this.passwordRepeatError) && this.hasAddress;
|
||||||
|
|
||||||
case 'fromPhrase':
|
case 'fromPhrase':
|
||||||
return !(this.nameError || this.passwordRepeatError);
|
return !(this.nameError || this.passwordRepeatError);
|
||||||
@ -85,6 +85,10 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get hasAddress () {
|
||||||
|
return !!(this.address);
|
||||||
|
}
|
||||||
|
|
||||||
@computed get passwordRepeatError () {
|
@computed get passwordRepeatError () {
|
||||||
return this.password === this.passwordRepeat
|
return this.password === this.passwordRepeat
|
||||||
? null
|
? null
|
||||||
|
@ -329,6 +329,7 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
describe('createType === fromNew', () => {
|
describe('createType === fromNew', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.setCreateType('fromNew');
|
store.setCreateType('fromNew');
|
||||||
|
store.setAddress('0x0000000000000000000000000000000000000000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true on no errors', () => {
|
it('returns true on no errors', () => {
|
||||||
@ -337,11 +338,13 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
|
|
||||||
it('returns false on nameError', () => {
|
it('returns false on nameError', () => {
|
||||||
store.setName('');
|
store.setName('');
|
||||||
|
|
||||||
expect(store.canCreate).to.be.false;
|
expect(store.canCreate).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false on passwordRepeatError', () => {
|
it('returns false on passwordRepeatError', () => {
|
||||||
store.setPassword('testing');
|
store.setPassword('testing');
|
||||||
|
|
||||||
expect(store.canCreate).to.be.false;
|
expect(store.canCreate).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user