Public node WASM, performance and fixes (#5734)
This commit is contained in:
parent
edea41d35e
commit
b2a42f03eb
@ -159,6 +159,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parity/wordlist": "1.0.1",
|
"@parity/wordlist": "1.0.1",
|
||||||
|
"arraybuffer-loader": "0.2.2",
|
||||||
"babel-runtime": "6.23.0",
|
"babel-runtime": "6.23.0",
|
||||||
"base32.js": "0.1.0",
|
"base32.js": "0.1.0",
|
||||||
"bignumber.js": "3.0.1",
|
"bignumber.js": "3.0.1",
|
||||||
@ -218,7 +219,6 @@
|
|||||||
"redux-thunk": "2.1.0",
|
"redux-thunk": "2.1.0",
|
||||||
"rlp": "2.0.0",
|
"rlp": "2.0.0",
|
||||||
"scryptsy": "2.0.0",
|
"scryptsy": "2.0.0",
|
||||||
"secp256k1": "3.2.5",
|
|
||||||
"solc": "ngotchac/solc-js",
|
"solc": "ngotchac/solc-js",
|
||||||
"store": "1.3.20",
|
"store": "1.3.20",
|
||||||
"sw-toolbox": "^3.6.0",
|
"sw-toolbox": "^3.6.0",
|
||||||
|
@ -23,7 +23,7 @@ import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'
|
|||||||
import Subscriptions from './subscriptions';
|
import Subscriptions from './subscriptions';
|
||||||
import util from './util';
|
import util from './util';
|
||||||
import { isFunction } from './util/types';
|
import { isFunction } from './util/types';
|
||||||
// import { LocalAccountsMiddleware } from './local';
|
import { LocalAccountsMiddleware } from './local';
|
||||||
|
|
||||||
export default class Api extends EventEmitter {
|
export default class Api extends EventEmitter {
|
||||||
constructor (transport, allowSubscriptions = true) {
|
constructor (transport, allowSubscriptions = true) {
|
||||||
@ -54,9 +54,9 @@ export default class Api extends EventEmitter {
|
|||||||
const middleware = this.parity
|
const middleware = this.parity
|
||||||
.nodeKind()
|
.nodeKind()
|
||||||
.then((nodeKind) => {
|
.then((nodeKind) => {
|
||||||
// if (nodeKind.availability === 'public') {
|
if (nodeKind.availability === 'public') {
|
||||||
// return LocalAccountsMiddleware;
|
return LocalAccountsMiddleware;
|
||||||
// }
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
import { createKeyObject, decryptPrivateKey } from '../ethkey';
|
import { createKeyObject, decryptPrivateKey } from '../ethkey';
|
||||||
|
|
||||||
export default class Account {
|
export default class Account {
|
||||||
constructor (persist, data) {
|
constructor (persist, data = {}) {
|
||||||
const {
|
const {
|
||||||
keyObject,
|
keyObject = null,
|
||||||
meta = {},
|
meta = {},
|
||||||
name = ''
|
name = ''
|
||||||
} = data;
|
} = data;
|
||||||
@ -41,6 +41,15 @@ export default class Account {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export () {
|
||||||
|
const exported = Object.assign({}, this._keyObject);
|
||||||
|
|
||||||
|
exported.meta = JSON.stringify(this._meta);
|
||||||
|
exported.name = this._name;
|
||||||
|
|
||||||
|
return exported;
|
||||||
|
}
|
||||||
|
|
||||||
get address () {
|
get address () {
|
||||||
return `0x${this._keyObject.address.toLowerCase()}`;
|
return `0x${this._keyObject.address.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
@ -66,6 +75,10 @@ export default class Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get uuid () {
|
get uuid () {
|
||||||
|
if (!this._keyObject) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return this._keyObject.id;
|
return this._keyObject.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,23 +17,74 @@
|
|||||||
import Account from './account';
|
import Account from './account';
|
||||||
import localStore from 'store';
|
import localStore from 'store';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import { decryptPrivateKey } from '../ethkey';
|
||||||
|
|
||||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
const LS_STORE_KEY = '_parity::localAccounts';
|
const LS_STORE_KEY = '_parity::localAccounts';
|
||||||
|
|
||||||
export default class Accounts {
|
export default class Accounts {
|
||||||
constructor (data = localStore.get(LS_STORE_KEY) || {}) {
|
persist = debounce(() => {
|
||||||
const {
|
this._lastState = JSON.stringify(this);
|
||||||
last = NULL_ADDRESS,
|
|
||||||
store = []
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
this.persist = debounce(() => {
|
|
||||||
localStore.set(LS_STORE_KEY, this);
|
localStore.set(LS_STORE_KEY, this);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
constructor (data = localStore.get(LS_STORE_KEY) || {}) {
|
||||||
|
this._lastState = JSON.stringify(data);
|
||||||
|
|
||||||
|
window.addEventListener('storage', ({ key, newValue }) => {
|
||||||
|
if (key !== LS_STORE_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue !== this._lastState) {
|
||||||
|
console.log('Data changed in a second tab, syncing state');
|
||||||
|
|
||||||
|
this.restore(JSON.parse(newValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.restore(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
restore (data) {
|
||||||
|
const {
|
||||||
|
last = NULL_ADDRESS,
|
||||||
|
dappsDefault = NULL_ADDRESS,
|
||||||
|
store = {}
|
||||||
|
} = data;
|
||||||
|
|
||||||
this._last = last;
|
this._last = last;
|
||||||
this._store = store.map((data) => new Account(this.persist, data));
|
this._dappsDefaultAddress = dappsDefault;
|
||||||
|
this._store = {};
|
||||||
|
|
||||||
|
if (Array.isArray(store)) {
|
||||||
|
// Recover older version that stored accounts as an array
|
||||||
|
store.forEach((data) => {
|
||||||
|
const account = new Account(this.persist, data);
|
||||||
|
|
||||||
|
this._store[account.address] = account;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.keys(store).forEach((key) => {
|
||||||
|
this._store[key] = new Account(this.persist, store[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addAccount = (account) => {
|
||||||
|
const { address } = account;
|
||||||
|
|
||||||
|
if (address in this._store && this._store[address].uuid) {
|
||||||
|
throw new Error(`Account ${address} already exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._store[address] = account;
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
|
this.persist();
|
||||||
|
|
||||||
|
return account.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
create (secret, password) {
|
create (secret, password) {
|
||||||
@ -41,20 +92,19 @@ export default class Accounts {
|
|||||||
|
|
||||||
return Account
|
return Account
|
||||||
.fromPrivateKey(this.persist, privateKey, password)
|
.fromPrivateKey(this.persist, privateKey, password)
|
||||||
.then((account) => {
|
.then(this._addAccount);
|
||||||
const { address } = account;
|
|
||||||
|
|
||||||
if (this._store.find((account) => account.address === address)) {
|
|
||||||
throw new Error(`Account ${address} already exists!`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._store.push(account);
|
restoreFromWallet (wallet, password) {
|
||||||
this.lastAddress = address;
|
return decryptPrivateKey(wallet, password)
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Invalid password');
|
||||||
|
}
|
||||||
|
|
||||||
this.persist();
|
return Account.fromPrivateKey(this.persist, privateKey, password);
|
||||||
|
})
|
||||||
return account.address;
|
.then(this._addAccount);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set lastAddress (value) {
|
set lastAddress (value) {
|
||||||
@ -65,20 +115,48 @@ export default class Accounts {
|
|||||||
return this._last;
|
return this._last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dappsDefaultAddress () {
|
||||||
|
if (this._dappsDefaultAddress === NULL_ADDRESS) {
|
||||||
|
return this._last;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._dappsDefaultAddress in this._store) {
|
||||||
|
return this._dappsDefaultAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
set dappsDefaultAddress (value) {
|
||||||
|
this._dappsDefaultAddress = value.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
get (address) {
|
get (address) {
|
||||||
address = address.toLowerCase();
|
address = address.toLowerCase();
|
||||||
|
|
||||||
this.lastAddress = address;
|
const account = this._store[address];
|
||||||
|
|
||||||
const account = this._store.find((account) => account.address === address);
|
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error(`Account not found: ${address}`);
|
throw new Error(`Account not found: ${address}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLazyCreate (address) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
|
if (!(address in this._store)) {
|
||||||
|
this._store[address] = new Account(this.persist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._store[address];
|
||||||
|
}
|
||||||
|
|
||||||
remove (address, password) {
|
remove (address, password) {
|
||||||
address = address.toLowerCase();
|
address = address.toLowerCase();
|
||||||
|
|
||||||
@ -108,26 +186,20 @@ export default class Accounts {
|
|||||||
removeUnsafe (address) {
|
removeUnsafe (address) {
|
||||||
address = address.toLowerCase();
|
address = address.toLowerCase();
|
||||||
|
|
||||||
const index = this._store.findIndex((account) => account.address === address);
|
delete this._store[address];
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._store.splice(index, 1);
|
|
||||||
|
|
||||||
this.persist();
|
this.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
mapArray (mapper) {
|
addresses () {
|
||||||
return this._store.map(mapper);
|
return Object.keys(this._store);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapObject (mapper) {
|
map (mapper) {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
this._store.forEach((account) => {
|
Object.keys(this._store).forEach((key) => {
|
||||||
result[account.address] = mapper(account);
|
result[key] = mapper(this._store[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -136,6 +208,7 @@ export default class Accounts {
|
|||||||
toJSON () {
|
toJSON () {
|
||||||
return {
|
return {
|
||||||
last: this._last,
|
last: this._last,
|
||||||
|
dappsDefault: this._dappsDefaultAddress,
|
||||||
store: this._store
|
store: this._store
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
147
js/src/api/local/ethkey/ethkey.js
Normal file
147
js/src/api/local/ethkey/ethkey.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
/* global WebAssembly */
|
||||||
|
|
||||||
|
import wasmBuffer from './ethkey.wasm.js';
|
||||||
|
|
||||||
|
const NOOP = () => {};
|
||||||
|
|
||||||
|
// WASM memory setup
|
||||||
|
const WASM_PAGE_SIZE = 65536;
|
||||||
|
const STATIC_BASE = 1024;
|
||||||
|
const STATICTOP = STATIC_BASE + WASM_PAGE_SIZE * 2;
|
||||||
|
const STACK_BASE = align(STATICTOP + 16);
|
||||||
|
const STACKTOP = STACK_BASE;
|
||||||
|
const TOTAL_STACK = 5 * 1024 * 1024;
|
||||||
|
const TOTAL_MEMORY = 16777216;
|
||||||
|
const STACK_MAX = STACK_BASE + TOTAL_STACK;
|
||||||
|
const DYNAMIC_BASE = STACK_MAX + 64;
|
||||||
|
const DYNAMICTOP_PTR = STACK_MAX;
|
||||||
|
|
||||||
|
function mockWebAssembly () {
|
||||||
|
function throwWasmError () {
|
||||||
|
throw new Error('Missing WebAssembly support');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple mock replacement
|
||||||
|
return {
|
||||||
|
Memory: class { buffer = new ArrayBuffer(2048) },
|
||||||
|
Table: class {},
|
||||||
|
Module: class {},
|
||||||
|
Instance: class {
|
||||||
|
exports = {
|
||||||
|
'_input_ptr': () => 0,
|
||||||
|
'_secret_ptr': () => 0,
|
||||||
|
'_public_ptr': () => 0,
|
||||||
|
'_address_ptr': () => 0,
|
||||||
|
'_ecpointg': NOOP,
|
||||||
|
'_brain': throwWasmError,
|
||||||
|
'_verify_secret': throwWasmError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Memory, Table, Module, Instance } = typeof WebAssembly !== 'undefined' ? WebAssembly : mockWebAssembly();
|
||||||
|
|
||||||
|
const wasmMemory = new Memory({
|
||||||
|
initial: TOTAL_MEMORY / WASM_PAGE_SIZE,
|
||||||
|
maximum: TOTAL_MEMORY / WASM_PAGE_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
const wasmTable = new Table({
|
||||||
|
initial: 8,
|
||||||
|
maximum: 8,
|
||||||
|
element: 'anyfunc'
|
||||||
|
});
|
||||||
|
|
||||||
|
// TypedArray views into the memory
|
||||||
|
const wasmMemoryU8 = new Uint8Array(wasmMemory.buffer);
|
||||||
|
const wasmMemoryU32 = new Uint32Array(wasmMemory.buffer);
|
||||||
|
|
||||||
|
// Keep DYNAMIC_BASE in memory
|
||||||
|
wasmMemoryU32[DYNAMICTOP_PTR >> 2] = align(DYNAMIC_BASE);
|
||||||
|
|
||||||
|
function align (mem) {
|
||||||
|
const ALIGN_SIZE = 16;
|
||||||
|
|
||||||
|
return (Math.ceil(mem / ALIGN_SIZE) * ALIGN_SIZE) | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function slice (ptr, len) {
|
||||||
|
return wasmMemoryU8.subarray(ptr, ptr + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function abort (what) {
|
||||||
|
throw new Error(what || 'WASM abort');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function abortOnCannotGrowMemory () {
|
||||||
|
abort(`Cannot enlarge memory arrays.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function enlargeMemory () {
|
||||||
|
abortOnCannotGrowMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function getTotalMemory () {
|
||||||
|
return TOTAL_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten - used to perform memcpy on large data
|
||||||
|
function memcpy (dest, src, len) {
|
||||||
|
wasmMemoryU8.set(wasmMemoryU8.subarray(src, src + len), dest);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronously compile WASM from the buffer
|
||||||
|
const module = new Module(wasmBuffer);
|
||||||
|
|
||||||
|
// Instantiated WASM module
|
||||||
|
const instance = new Instance(module, {
|
||||||
|
global: {},
|
||||||
|
env: {
|
||||||
|
DYNAMICTOP_PTR,
|
||||||
|
STACKTOP,
|
||||||
|
STACK_MAX,
|
||||||
|
abort,
|
||||||
|
enlargeMemory,
|
||||||
|
getTotalMemory,
|
||||||
|
abortOnCannotGrowMemory,
|
||||||
|
___lock: NOOP,
|
||||||
|
___syscall6: () => 0,
|
||||||
|
___setErrNo: (no) => no,
|
||||||
|
_abort: abort,
|
||||||
|
___syscall140: () => 0,
|
||||||
|
_emscripten_memcpy_big: memcpy,
|
||||||
|
___syscall54: () => 0,
|
||||||
|
___unlock: NOOP,
|
||||||
|
_llvm_trap: abort,
|
||||||
|
___syscall146: () => 0,
|
||||||
|
'memory': wasmMemory,
|
||||||
|
'table': wasmTable,
|
||||||
|
tableBase: 0,
|
||||||
|
memoryBase: STATIC_BASE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const extern = instance.exports;
|
1
js/src/api/local/ethkey/ethkey.wasm.js
Normal file
1
js/src/api/local/ethkey/ethkey.wasm.js
Normal file
File diff suppressed because one or more lines are too long
@ -17,13 +17,12 @@
|
|||||||
import workerPool from './workerPool';
|
import workerPool from './workerPool';
|
||||||
|
|
||||||
export function createKeyObject (key, password) {
|
export function createKeyObject (key, password) {
|
||||||
return workerPool.getWorker().action('createKeyObject', { key, password })
|
return workerPool.action('createKeyObject', { key, password })
|
||||||
.then((obj) => JSON.parse(obj));
|
.then((obj) => JSON.parse(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decryptPrivateKey (keyObject, password) {
|
export function decryptPrivateKey (keyObject, password) {
|
||||||
return workerPool
|
return workerPool
|
||||||
.getWorker()
|
|
||||||
.action('decryptPrivateKey', { keyObject, password })
|
.action('decryptPrivateKey', { keyObject, password })
|
||||||
.then((privateKey) => {
|
.then((privateKey) => {
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
@ -40,9 +39,9 @@ export function phraseToAddress (phrase) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function phraseToWallet (phrase) {
|
export function phraseToWallet (phrase) {
|
||||||
return workerPool.getWorker().action('phraseToWallet', phrase);
|
return workerPool.action('phraseToWallet', phrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifySecret (secret) {
|
export function verifySecret (secret) {
|
||||||
return workerPool.getWorker().action('verifySecret', secret);
|
return workerPool.action('verifySecret', secret);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
import { randomPhrase } from '@parity/wordlist';
|
import { randomPhrase } from '@parity/wordlist';
|
||||||
import { phraseToAddress, phraseToWallet } from './';
|
import { phraseToAddress, phraseToWallet } from './';
|
||||||
|
|
||||||
describe('api/local/ethkey', () => {
|
// TODO: Skipping until Node.js 8.0 comes out and we can test WebAssembly
|
||||||
|
describe.skip('api/local/ethkey', () => {
|
||||||
describe('phraseToAddress', function () {
|
describe('phraseToAddress', function () {
|
||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
|
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
// 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 secp256k1 from 'secp256k1';
|
|
||||||
import { keccak_256 as keccak256 } from 'js-sha3';
|
|
||||||
import { bytesToHex } from '~/api/util/format';
|
import { bytesToHex } from '~/api/util/format';
|
||||||
|
import { extern, slice } from './ethkey.js';
|
||||||
|
|
||||||
const isWorker = typeof self !== 'undefined';
|
const isWorker = typeof self !== 'undefined';
|
||||||
|
|
||||||
@ -42,43 +41,40 @@ function route ({ action, payload }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const input = slice(extern._input_ptr(), 1024);
|
||||||
|
const secret = slice(extern._secret_ptr(), 32);
|
||||||
|
const publicKey = slice(extern._public_ptr(), 64);
|
||||||
|
const address = slice(extern._address_ptr(), 20);
|
||||||
|
|
||||||
|
extern._ecpointg();
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
phraseToWallet (phrase) {
|
phraseToWallet (phrase) {
|
||||||
let secret = keccak256.array(phrase);
|
const phraseUtf8 = Buffer.from(phrase, 'utf8');
|
||||||
|
|
||||||
for (let i = 0; i < 16384; i++) {
|
if (phraseUtf8.length > input.length) {
|
||||||
secret = keccak256.array(secret);
|
throw new Error('Phrase is too long!');
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
input.set(phraseUtf8);
|
||||||
secret = keccak256.array(secret);
|
|
||||||
|
|
||||||
const secretBuf = Buffer.from(secret);
|
extern._brain(phraseUtf8.length);
|
||||||
|
|
||||||
if (secp256k1.privateKeyVerify(secretBuf)) {
|
|
||||||
// No compression, slice out last 64 bytes
|
|
||||||
const publicBuf = secp256k1.publicKeyCreate(secretBuf, false).slice(-64);
|
|
||||||
const address = keccak256.array(publicBuf).slice(12);
|
|
||||||
|
|
||||||
if (address[0] !== 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wallet = {
|
const wallet = {
|
||||||
secret: bytesToHex(secretBuf),
|
secret: bytesToHex(secret),
|
||||||
public: bytesToHex(publicBuf),
|
public: bytesToHex(publicKey),
|
||||||
address: bytesToHex(address)
|
address: bytesToHex(address)
|
||||||
};
|
};
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
verifySecret (secret) {
|
verifySecret (key) {
|
||||||
const key = Buffer.from(secret.slice(2), 'hex');
|
const keyBuf = Buffer.from(key.slice(2), 'hex');
|
||||||
|
|
||||||
return secp256k1.privateKeyVerify(key);
|
secret.set(keyBuf);
|
||||||
|
|
||||||
|
return extern._verify_secret();
|
||||||
},
|
},
|
||||||
|
|
||||||
createKeyObject ({ key, password }) {
|
createKeyObject ({ key, password }) {
|
||||||
@ -112,7 +108,8 @@ self.onmessage = function ({ data }) {
|
|||||||
|
|
||||||
postMessage([null, result]);
|
postMessage([null, result]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
postMessage([err, null]);
|
console.error(err);
|
||||||
|
postMessage([err.toString(), null]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ class WorkerContainer {
|
|||||||
this.busy = false;
|
this.busy = false;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
// `err` ought to be a String
|
||||||
|
reject(new Error(err));
|
||||||
} else {
|
} else {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
@ -48,20 +49,56 @@ class WorkerContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class WorkerPool {
|
class WorkerPool {
|
||||||
pool = [];
|
pool = [
|
||||||
|
new WorkerContainer(),
|
||||||
|
new WorkerContainer()
|
||||||
|
];
|
||||||
|
|
||||||
getWorker () {
|
queue = [];
|
||||||
let container = this.pool.find((container) => !container.busy);
|
|
||||||
|
|
||||||
if (container) {
|
_getContainer () {
|
||||||
return container;
|
return this.pool.find((container) => !container.busy);
|
||||||
}
|
}
|
||||||
|
|
||||||
container = new WorkerContainer();
|
action (action, payload) {
|
||||||
|
let container = this.pool.find((container) => !container.busy);
|
||||||
|
|
||||||
this.pool.push(container);
|
let promise;
|
||||||
|
|
||||||
return container;
|
// const start = Date.now();
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
promise = container.action(action, payload);
|
||||||
|
} else {
|
||||||
|
promise = new Promise((resolve, reject) => {
|
||||||
|
this.queue.push([action, payload, resolve]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.catch((err) => {
|
||||||
|
this.processQueue();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.processQueue();
|
||||||
|
|
||||||
|
// console.log('Work done in ', Date.now() - start);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processQueue () {
|
||||||
|
let container = this._getContainer();
|
||||||
|
|
||||||
|
while (container && this.queue.length > 0) {
|
||||||
|
const [action, payload, resolve] = this.queue.shift();
|
||||||
|
|
||||||
|
resolve(container.action(action, payload));
|
||||||
|
container = this._getContainer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
const register = this.register.bind(this);
|
const register = this.register.bind(this);
|
||||||
|
|
||||||
register('eth_accounts', () => {
|
register('eth_accounts', () => {
|
||||||
return accounts.mapArray((account) => account.address);
|
return accounts.addresses();
|
||||||
});
|
});
|
||||||
|
|
||||||
register('eth_coinbase', () => {
|
register('eth_coinbase', () => {
|
||||||
@ -37,13 +37,13 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
});
|
});
|
||||||
|
|
||||||
register('parity_accountsInfo', () => {
|
register('parity_accountsInfo', () => {
|
||||||
return accounts.mapObject(({ name }) => {
|
return accounts.map(({ name }) => {
|
||||||
return { name };
|
return { name };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
register('parity_allAccountsInfo', () => {
|
register('parity_allAccountsInfo', () => {
|
||||||
return accounts.mapObject(({ name, meta, uuid }) => {
|
return accounts.map(({ name, meta, uuid }) => {
|
||||||
return { name, meta, uuid };
|
return { name, meta, uuid };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -68,10 +68,31 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
return transactions.hash(id) || Promise.resolve(null);
|
return transactions.hash(id) || Promise.resolve(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_dappsList', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_defaultAccount', () => {
|
register('parity_defaultAccount', () => {
|
||||||
return accounts.lastAddress;
|
return accounts.lastAddress;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_exportAccount', ([address, password]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
password = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.isValidPassword(password)
|
||||||
|
.then((isValid) => {
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('Invalid password');
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.export();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_generateSecretPhrase', () => {
|
register('parity_generateSecretPhrase', () => {
|
||||||
return randomPhrase(12);
|
return randomPhrase(12);
|
||||||
});
|
});
|
||||||
@ -80,6 +101,10 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_getNewDappsDefaultAddress', () => {
|
||||||
|
return accounts.lastAddress;
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_hardwareAccountsInfo', () => {
|
register('parity_hardwareAccountsInfo', () => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
@ -102,18 +127,30 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_newAccountFromWallet', ([json, password]) => {
|
||||||
|
if (!password) {
|
||||||
|
password = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts.restoreFromWallet(JSON.parse(json), password);
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_setAccountMeta', ([address, meta]) => {
|
register('parity_setAccountMeta', ([address, meta]) => {
|
||||||
accounts.get(address).meta = meta;
|
accounts.getLazyCreate(address).meta = meta;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
register('parity_setAccountName', ([address, name]) => {
|
register('parity_setAccountName', ([address, name]) => {
|
||||||
accounts.get(address).name = name;
|
accounts.getLazyCreate(address).name = name;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_setNewDappsDefaultAddress', ([address]) => {
|
||||||
|
accounts.dappsDefaultAddress = address;
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_postTransaction', ([tx]) => {
|
register('parity_postTransaction', ([tx]) => {
|
||||||
if (!tx.from) {
|
if (!tx.from) {
|
||||||
tx.from = accounts.lastAddress;
|
tx.from = accounts.lastAddress;
|
||||||
@ -137,10 +174,32 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_listOpenedVaults', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_listRecentDapps', () => {
|
register('parity_listRecentDapps', () => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('parity_listVaults', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_wsUrl', () => {
|
||||||
|
// This is a hack, will be replaced by a `hostname` setting on the node itself
|
||||||
|
return `${window.location.hostname}:8546`;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_dappsUrl', () => {
|
||||||
|
// This is a hack, will be replaced by a `hostname` setting on the node itself
|
||||||
|
return `${window.location.hostname}:8545`;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_hashContent', () => {
|
||||||
|
throw new Error('Functionality unavailable on a public wallet.');
|
||||||
|
});
|
||||||
|
|
||||||
register('parity_killAccount', ([address, password]) => {
|
register('parity_killAccount', ([address, password]) => {
|
||||||
return accounts.remove(address, password);
|
return accounts.remove(address, password);
|
||||||
});
|
});
|
||||||
@ -204,6 +263,10 @@ export default class LocalAccountsMiddleware extends Middleware {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register('signer_generateAuthorizationToken', () => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
register('signer_rejectRequest', ([id]) => {
|
register('signer_rejectRequest', ([id]) => {
|
||||||
return transactions.reject(id);
|
return transactions.reject(id);
|
||||||
});
|
});
|
||||||
|
@ -71,7 +71,9 @@ describe('api/local/LocalAccountsMiddleware', function () {
|
|||||||
'parity_phraseToAddress',
|
'parity_phraseToAddress',
|
||||||
'parity_useLocalAccounts',
|
'parity_useLocalAccounts',
|
||||||
'parity_listGethAccounts',
|
'parity_listGethAccounts',
|
||||||
|
'parity_listOpenedVaults',
|
||||||
'parity_listRecentDapps',
|
'parity_listRecentDapps',
|
||||||
|
'parity_listVaults',
|
||||||
'parity_killAccount',
|
'parity_killAccount',
|
||||||
'parity_testPassword',
|
'parity_testPassword',
|
||||||
'signer_confirmRequest',
|
'signer_confirmRequest',
|
||||||
|
@ -41,6 +41,7 @@ class Accounts extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
accountsInfo: PropTypes.object.isRequired,
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
|
availability: PropTypes.string.isRequired,
|
||||||
hasAccounts: PropTypes.bool.isRequired,
|
hasAccounts: PropTypes.bool.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired
|
setVisibleAccounts: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
@ -249,21 +250,7 @@ class Accounts extends Component {
|
|||||||
|
|
||||||
renderActionbar () {
|
renderActionbar () {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Link
|
this.renderVaultsButton(),
|
||||||
to='/vaults'
|
|
||||||
key='vaults'
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={ <KeyIcon /> }
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='accounts.button.vaults'
|
|
||||||
defaultMessage='vaults'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onClick={ this.onVaultsClick }
|
|
||||||
/>
|
|
||||||
</Link>,
|
|
||||||
<Button
|
<Button
|
||||||
key='newAccount'
|
key='newAccount'
|
||||||
icon={ <AddIcon /> }
|
icon={ <AddIcon /> }
|
||||||
@ -275,17 +262,7 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
onClick={ this.onNewAccountClick }
|
onClick={ this.onNewAccountClick }
|
||||||
/>,
|
/>,
|
||||||
<Button
|
this.renderNewWalletButton(),
|
||||||
key='newWallet'
|
|
||||||
icon={ <AddIcon /> }
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='accounts.button.newWallet'
|
|
||||||
defaultMessage='wallet'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onClick={ this.onNewWalletClick }
|
|
||||||
/>,
|
|
||||||
<Button
|
<Button
|
||||||
key='restoreAccount'
|
key='restoreAccount'
|
||||||
icon={ <AddIcon /> }
|
icon={ <AddIcon /> }
|
||||||
@ -370,6 +347,50 @@ class Accounts extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderVaultsButton () {
|
||||||
|
if (this.props.availability !== 'personal') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to='/vaults'
|
||||||
|
key='vaults'
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={ <KeyIcon /> }
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='accounts.button.vaults'
|
||||||
|
defaultMessage='vaults'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onVaultsClick }
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNewWalletButton () {
|
||||||
|
if (this.props.availability !== 'personal') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key='newWallet'
|
||||||
|
icon={ <AddIcon /> }
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='accounts.button.newWallet'
|
||||||
|
defaultMessage='wallet'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onNewWalletClick }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderNewWalletDialog () {
|
renderNewWalletDialog () {
|
||||||
const { accounts } = this.props;
|
const { accounts } = this.props;
|
||||||
const { newWalletDialog } = this.state;
|
const { newWalletDialog } = this.state;
|
||||||
@ -474,10 +495,12 @@ class Accounts extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, accountsInfo, hasAccounts } = state.personal;
|
const { accounts, accountsInfo, hasAccounts } = state.personal;
|
||||||
|
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
|
availability,
|
||||||
hasAccounts
|
hasAccounts
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -99,11 +99,19 @@ function mapStateToProps (initState) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
|
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||||
const { views } = state.settings;
|
const { views } = state.settings;
|
||||||
|
|
||||||
const viewIds = Object
|
const viewIds = Object
|
||||||
.keys(views)
|
.keys(views)
|
||||||
.filter((id) => views[id].fixed || views[id].active);
|
.filter((id) => {
|
||||||
|
const view = views[id];
|
||||||
|
|
||||||
|
const isEnabled = view.fixed || view.active;
|
||||||
|
const isAllowed = !view.onlyPersonal || availability === 'personal';
|
||||||
|
|
||||||
|
return isEnabled && isAllowed;
|
||||||
|
});
|
||||||
|
|
||||||
if (isEqual(viewIds, filteredViewIds)) {
|
if (isEqual(viewIds, filteredViewIds)) {
|
||||||
return { views: filteredViews };
|
return { views: filteredViews };
|
||||||
|
@ -63,7 +63,8 @@
|
|||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"secure": true
|
"secure": true,
|
||||||
|
"onlyPersonal": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d",
|
"id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d",
|
||||||
@ -94,6 +95,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"skipBuild": true,
|
"skipBuild": true,
|
||||||
"skipHistory": true
|
"skipHistory": true,
|
||||||
|
"onlyPersonal": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -34,10 +34,11 @@ import styles from './dapps.css';
|
|||||||
class Dapps extends Component {
|
class Dapps extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired
|
accounts: PropTypes.object.isRequired,
|
||||||
|
availability: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
store = DappsStore.get(this.context.api);
|
store = DappsStore.get(this.context.api);
|
||||||
@ -133,6 +134,10 @@ class Dapps extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderApp = (app) => {
|
renderApp = (app) => {
|
||||||
|
if (app.onlyPersonal && this.props.availability !== 'personal') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DappCard
|
<DappCard
|
||||||
app={ app }
|
app={ app }
|
||||||
@ -156,6 +161,7 @@ class Dapps extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
|
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not show the Wallet Accounts in the Dapps
|
* Do not show the Wallet Accounts in the Dapps
|
||||||
@ -165,7 +171,8 @@ function mapStateToProps (state) {
|
|||||||
const _accounts = omitBy(accounts, (account) => account.wallet);
|
const _accounts = omitBy(accounts, (account) => account.wallet);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: _accounts
|
accounts: _accounts,
|
||||||
|
availability
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import HistoryStore from '~/mobx/historyStore';
|
import HistoryStore from '~/mobx/historyStore';
|
||||||
@ -32,11 +33,15 @@ import Urls from './Urls';
|
|||||||
import styles from './home.css';
|
import styles from './home.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class Home extends Component {
|
class Home extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
availability: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
dappsStore = DappsStore.get(this.context.api);
|
dappsStore = DappsStore.get(this.context.api);
|
||||||
extensionStore = ExtensionStore.get();
|
extensionStore = ExtensionStore.get();
|
||||||
webStore = WebStore.get(this.context.api);
|
webStore = WebStore.get(this.context.api);
|
||||||
@ -49,6 +54,13 @@ export default class Home extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const urls = this.props.availability !== 'personal' ? null : (
|
||||||
|
<Urls
|
||||||
|
extensionStore={ this.extensionStore }
|
||||||
|
store={ this.webStore }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
className={ styles.body }
|
className={ styles.body }
|
||||||
@ -60,10 +72,7 @@ export default class Home extends Component {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<News />
|
<News />
|
||||||
<Urls
|
{ urls }
|
||||||
extensionStore={ this.extensionStore }
|
|
||||||
store={ this.webStore }
|
|
||||||
/>
|
|
||||||
<div className={ styles.row }>
|
<div className={ styles.row }>
|
||||||
<div className={ styles.column }>
|
<div className={ styles.column }>
|
||||||
<Dapps
|
<Dapps
|
||||||
@ -79,3 +88,16 @@ export default class Home extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (initState) {
|
||||||
|
return (state) => {
|
||||||
|
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||||
|
|
||||||
|
return { availability };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(Home);
|
||||||
|
@ -59,6 +59,7 @@ const defaultViews = {
|
|||||||
|
|
||||||
contracts: {
|
contracts: {
|
||||||
active: false,
|
active: false,
|
||||||
|
onlyPersonal: true,
|
||||||
icon: <ContactsIcon />,
|
icon: <ContactsIcon />,
|
||||||
route: '/contracts',
|
route: '/contracts',
|
||||||
value: 'contract'
|
value: 'contract'
|
||||||
@ -66,6 +67,7 @@ const defaultViews = {
|
|||||||
|
|
||||||
status: {
|
status: {
|
||||||
active: false,
|
active: false,
|
||||||
|
onlyPersonal: true,
|
||||||
icon: <StatusIcon />,
|
icon: <StatusIcon />,
|
||||||
route: '/status',
|
route: '/status',
|
||||||
value: 'status'
|
value: 'status'
|
||||||
|
@ -30,7 +30,8 @@ import styles from './views.css';
|
|||||||
class Views extends Component {
|
class Views extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
settings: PropTypes.object.isRequired,
|
settings: PropTypes.object.isRequired,
|
||||||
toggleView: PropTypes.func.isRequired
|
toggleView: PropTypes.func.isRequired,
|
||||||
|
availability: PropTypes.string.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -168,6 +169,10 @@ class Views extends Component {
|
|||||||
const toggle = () => toggleView(id);
|
const toggle = () => toggleView(id);
|
||||||
const view = settings.views[id];
|
const view = settings.views[id];
|
||||||
|
|
||||||
|
if (view.onlyPersonal && this.props.availability !== 'personal') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.view } key={ id }>
|
<div className={ styles.view } key={ id }>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -196,8 +201,9 @@ class Views extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { settings } = state;
|
const { settings } = state;
|
||||||
|
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
|
||||||
|
|
||||||
return { settings };
|
return { settings, availability };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
|
3
js/wasm/README.md
Normal file
3
js/wasm/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# WASM modules
|
||||||
|
|
||||||
|
- `ethkey` -> `/js/src/api/local/ethkey/ethkey.wasm`
|
2
js/wasm/ethkey/.gitignore
vendored
Normal file
2
js/wasm/ethkey/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
17
js/wasm/ethkey/Cargo.toml
Normal file
17
js/wasm/ethkey/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
description = "Parity ethkey WASM module."
|
||||||
|
name = "parity-ethkey-wasm"
|
||||||
|
version = "1.7.0"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tiny-keccak = "1.0"
|
||||||
|
tiny-secp256k1 = "0.1"
|
||||||
|
libc = { version = "0.2.14", default-features = false }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
5
js/wasm/ethkey/base64ify.js
Normal file
5
js/wasm/ethkey/base64ify.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const file = fs.readFileSync('./ethkey.opt.wasm', { encoding: 'base64' });
|
||||||
|
|
||||||
|
fs.writeFileSync('../../src/api/local/ethkey/ethkey.wasm.js', `module.exports = new Buffer('${file}', 'base64');\n`);
|
16
js/wasm/ethkey/build.sh
Executable file
16
js/wasm/ethkey/build.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
# Remove previous build to avoid name conflicts
|
||||||
|
rm -rf target/wasm32-unknown-emscripten/*
|
||||||
|
|
||||||
|
# Build using nightly rustc + emscripten
|
||||||
|
rustup run nightly cargo build --release --target=wasm32-unknown-emscripten
|
||||||
|
|
||||||
|
# Copy final WASM file over
|
||||||
|
cp ./target/wasm32-unknown-emscripten/release/deps/parity_ethkey_wasm-*.wasm ./ethkey.wasm
|
||||||
|
|
||||||
|
# Create a Base64-encoded JS version of the wasm file for easy inclusion in Webpack
|
||||||
|
node base64ify
|
||||||
|
|
||||||
|
# Copy Base64-encoded JS version to src
|
||||||
|
cp ./ethkey.wasm.js ../../src/api/local/ethkey/ethkey.wasm.js
|
||||||
|
|
||||||
|
# rm -f ./ethkey.wasm ./ethkey.opt.wasm ./ethkey.wasm.js
|
BIN
js/wasm/ethkey/ethkey.opt.wasm
Normal file
BIN
js/wasm/ethkey/ethkey.opt.wasm
Normal file
Binary file not shown.
BIN
js/wasm/ethkey/ethkey.wasm
Normal file
BIN
js/wasm/ethkey/ethkey.wasm
Normal file
Binary file not shown.
153
js/wasm/ethkey/src/main.rs
Normal file
153
js/wasm/ethkey/src/main.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
#![feature(lang_items, core_intrinsics)]
|
||||||
|
#![feature(start)]
|
||||||
|
#![feature(link_args)]
|
||||||
|
#![no_std]
|
||||||
|
use core::intrinsics;
|
||||||
|
|
||||||
|
// Pull in the system libc library for what crt0.o likely requires.
|
||||||
|
extern crate libc;
|
||||||
|
extern crate tiny_keccak;
|
||||||
|
extern crate tiny_secp256k1;
|
||||||
|
|
||||||
|
use tiny_secp256k1::{is_valid_secret, create_public_key, ECPointG};
|
||||||
|
|
||||||
|
// #[link_args = "-s EXPORTED_FUNCTIONS=['_input_ptr','_secret_ptr','_public_ptr','_address_ptr','_ecpointg','_verify_secret','_brain']"]
|
||||||
|
// extern {}
|
||||||
|
|
||||||
|
use tiny_keccak::Keccak;
|
||||||
|
|
||||||
|
pub trait Keccak256<T: Sized> {
|
||||||
|
fn keccak256(&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keccak256<[u8; 32]> for [u8] {
|
||||||
|
#[inline]
|
||||||
|
fn keccak256(&self) -> [u8; 32] {
|
||||||
|
let mut keccak = Keccak::new_keccak256();
|
||||||
|
let mut result = [0u8; 32];
|
||||||
|
keccak.update(self);
|
||||||
|
keccak.finalize(&mut result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut INPUT: [u8; 1024] = [0; 1024];
|
||||||
|
static mut SECRET: [u8; 32] = [0; 32];
|
||||||
|
static mut PUBLIC: [u8; 64] = [0; 64];
|
||||||
|
static mut ADDRESS: [u8; 20] = [0; 20];
|
||||||
|
static mut G: Option<ECPointG> = None;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn ecpointg() -> &'static ECPointG {
|
||||||
|
let g = unsafe { &G };
|
||||||
|
|
||||||
|
if let Some(ref g) = *g {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { G = Some(ECPointG::new()) };
|
||||||
|
g.as_ref().expect("value set above; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn input_ptr() -> *const u8 {
|
||||||
|
unsafe { INPUT.as_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn secret_ptr() -> *const u8 {
|
||||||
|
unsafe { SECRET.as_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn public_ptr() -> *const u8 {
|
||||||
|
unsafe { PUBLIC.as_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn address_ptr() -> *const u8 {
|
||||||
|
unsafe { ADDRESS.as_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn verify_secret() -> bool {
|
||||||
|
is_valid_secret(unsafe { &SECRET })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn brain(input_len: usize) {
|
||||||
|
let data = unsafe { &INPUT[..input_len] };
|
||||||
|
let mut secret_out = unsafe { &mut SECRET };
|
||||||
|
let mut public_out = unsafe { &mut PUBLIC };
|
||||||
|
let mut address_out = unsafe { &mut ADDRESS };
|
||||||
|
|
||||||
|
let g = ecpointg();
|
||||||
|
let mut secret = data.keccak256();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
secret = secret.keccak256();
|
||||||
|
|
||||||
|
match i > 16384 {
|
||||||
|
false => i += 1,
|
||||||
|
true => {
|
||||||
|
if let Some(public) = create_public_key(g, &secret) {
|
||||||
|
let public = &public[1..];
|
||||||
|
let hash = public.keccak256();
|
||||||
|
|
||||||
|
address_out.copy_from_slice(&hash[12..]);
|
||||||
|
|
||||||
|
if address_out[0] == 0 {
|
||||||
|
public_out.copy_from_slice(&public);
|
||||||
|
secret_out.copy_from_slice(&secret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point for this program.
|
||||||
|
#[start]
|
||||||
|
fn start(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// These functions are used by the compiler, but not
|
||||||
|
// for a bare-bones hello world. These are normally
|
||||||
|
// provided by libstd.
|
||||||
|
#[lang = "eh_personality"]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn rust_eh_personality() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function may be needed based on the compilation target.
|
||||||
|
#[lang = "eh_unwind_resume"]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn rust_eh_unwind_resume() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[lang = "panic_fmt"]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||||
|
_file: &'static str,
|
||||||
|
_line: u32) -> ! {
|
||||||
|
unsafe { intrinsics::abort() }
|
||||||
|
}
|
@ -139,7 +139,6 @@ module.exports = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': path.resolve(__dirname, '../src'),
|
'~': path.resolve(__dirname, '../src'),
|
||||||
'secp256k1': path.resolve(__dirname, '../node_modules/secp256k1/js'),
|
|
||||||
'keythereum': path.resolve(__dirname, '../node_modules/keythereum/dist/keythereum')
|
'keythereum': path.resolve(__dirname, '../node_modules/keythereum/dist/keythereum')
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
|
@ -42,7 +42,6 @@ module.exports = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': path.resolve(__dirname, '../src'),
|
'~': path.resolve(__dirname, '../src'),
|
||||||
'secp256k1': path.resolve(__dirname, '../node_modules/secp256k1/js'),
|
|
||||||
'keythereum': path.resolve(__dirname, '../node_modules/keythereum/dist/keythereum')
|
'keythereum': path.resolve(__dirname, '../node_modules/keythereum/dist/keythereum')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -840,6 +840,7 @@ impl Configuration {
|
|||||||
hosts: self.ws_hosts(),
|
hosts: self.ws_hosts(),
|
||||||
origins: self.ws_origins(),
|
origins: self.ws_origins(),
|
||||||
signer_path: self.directories().signer.into(),
|
signer_path: self.directories().signer.into(),
|
||||||
|
support_token_api: !self.args.flag_public_node,
|
||||||
ui_address: ui.address(),
|
ui_address: ui.address(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +141,7 @@ pub struct WsConfiguration {
|
|||||||
pub origins: Option<Vec<String>>,
|
pub origins: Option<Vec<String>>,
|
||||||
pub hosts: Option<Vec<String>>,
|
pub hosts: Option<Vec<String>>,
|
||||||
pub signer_path: PathBuf,
|
pub signer_path: PathBuf,
|
||||||
|
pub support_token_api: bool,
|
||||||
pub ui_address: Option<(String, u16)>,
|
pub ui_address: Option<(String, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +156,7 @@ impl Default for WsConfiguration {
|
|||||||
origins: Some(vec!["chrome-extension://*".into()]),
|
origins: Some(vec!["chrome-extension://*".into()]),
|
||||||
hosts: Some(Vec::new()),
|
hosts: Some(Vec::new()),
|
||||||
signer_path: replace_home(&data_dir, "$BASE/signer").into(),
|
signer_path: replace_home(&data_dir, "$BASE/signer").into(),
|
||||||
|
support_token_api: true,
|
||||||
ui_address: Some(("127.0.0.1".to_owned(), 8180)),
|
ui_address: Some(("127.0.0.1".to_owned(), 8180)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,9 +209,14 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
|
|||||||
let allowed_origins = into_domains(with_domain(conf.origins, domain, &[ui_address]));
|
let allowed_origins = into_domains(with_domain(conf.origins, domain, &[ui_address]));
|
||||||
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(ws_address)]));
|
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &[Some(ws_address)]));
|
||||||
|
|
||||||
let signer_path = conf.signer_path;
|
let signer_path;
|
||||||
let signer_path = conf.ui_address.map(move |_| ::signer::codes_path(&signer_path));
|
let path = match conf.support_token_api && conf.ui_address.is_some() {
|
||||||
let path = signer_path.as_ref().map(|p| p.as_path());
|
true => {
|
||||||
|
signer_path = ::signer::codes_path(&conf.signer_path);
|
||||||
|
Some(signer_path.as_path())
|
||||||
|
},
|
||||||
|
false => None
|
||||||
|
};
|
||||||
let start_result = rpc::start_ws(
|
let start_result = rpc::start_ws(
|
||||||
&addr,
|
&addr,
|
||||||
handler,
|
handler,
|
||||||
|
@ -307,6 +307,11 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
||||||
|
// Return nothing if accounts are disabled (running as public node)
|
||||||
|
if self.accounts.is_none() {
|
||||||
|
return Ok(BTreeMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
let transactions = self.miner.local_transactions();
|
let transactions = self.miner.local_transactions();
|
||||||
let block_number = self.client.chain_info().best_block_number;
|
let block_number = self.client.chain_info().best_block_number;
|
||||||
Ok(transactions
|
Ok(transactions
|
||||||
|
Loading…
Reference in New Issue
Block a user