Squashed: Public Node
This commit is contained in:
@@ -23,6 +23,7 @@ import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'
|
||||
import Subscriptions from './subscriptions';
|
||||
import util from './util';
|
||||
import { isFunction } from './util/types';
|
||||
import { LocalAccountsMiddleware } from './local';
|
||||
|
||||
export default class Api extends EventEmitter {
|
||||
constructor (transport) {
|
||||
@@ -45,6 +46,21 @@ export default class Api extends EventEmitter {
|
||||
this._web3 = new Web3(transport);
|
||||
|
||||
this._subscriptions = new Subscriptions(this);
|
||||
|
||||
// Doing a request here in test env would cause an error
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const middleware = this.parity
|
||||
.nodeKind()
|
||||
.then((nodeKind) => {
|
||||
if (nodeKind.availability === 'public') {
|
||||
return new LocalAccountsMiddleware(transport);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
transport.addMiddleware(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
get db () {
|
||||
|
||||
95
js/src/api/local/accounts/account.js
Normal file
95
js/src/api/local/accounts/account.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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/>.
|
||||
|
||||
import { keythereum } from '../ethkey';
|
||||
|
||||
export default class Account {
|
||||
constructor (persist, data) {
|
||||
const {
|
||||
keyObject,
|
||||
meta = {},
|
||||
name = ''
|
||||
} = data;
|
||||
|
||||
this._persist = persist;
|
||||
this._keyObject = keyObject;
|
||||
this._name = name;
|
||||
this._meta = meta;
|
||||
}
|
||||
|
||||
isValidPassword (password) {
|
||||
try {
|
||||
keythereum.recover(Buffer.from(password), this._keyObject);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
get address () {
|
||||
return `0x${this._keyObject.address.toLowerCase()}`;
|
||||
}
|
||||
|
||||
get name () {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set name (name) {
|
||||
this._name = name;
|
||||
|
||||
this._persist();
|
||||
}
|
||||
|
||||
get meta () {
|
||||
return JSON.stringify(this._meta);
|
||||
}
|
||||
|
||||
set meta (meta) {
|
||||
this._meta = JSON.parse(meta);
|
||||
|
||||
this._persist();
|
||||
}
|
||||
|
||||
get uuid () {
|
||||
return this._keyObject.id;
|
||||
}
|
||||
|
||||
decryptPrivateKey (password) {
|
||||
return keythereum.recover(Buffer.from(password), this._keyObject);
|
||||
}
|
||||
|
||||
static fromPrivateKey (persist, key, password) {
|
||||
const iv = keythereum.crypto.randomBytes(16);
|
||||
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 });
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return {
|
||||
keyObject: this._keyObject,
|
||||
name: this._name,
|
||||
meta: this._meta
|
||||
};
|
||||
}
|
||||
}
|
||||
120
js/src/api/local/accounts/accounts.js
Normal file
120
js/src/api/local/accounts/accounts.js
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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/>.
|
||||
|
||||
import Account from './account';
|
||||
import localStore from 'store';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const LS_STORE_KEY = '_parity::localAccounts';
|
||||
|
||||
export default class Accounts {
|
||||
constructor (data = localStore.get(LS_STORE_KEY) || {}) {
|
||||
const {
|
||||
last = NULL_ADDRESS,
|
||||
store = []
|
||||
} = data;
|
||||
|
||||
this.persist = debounce(() => {
|
||||
localStore.set(LS_STORE_KEY, this);
|
||||
}, 100);
|
||||
|
||||
this._last = last;
|
||||
this._store = store.map((data) => new Account(this.persist, data));
|
||||
}
|
||||
|
||||
create (secret, password) {
|
||||
const privateKey = Buffer.from(secret.slice(2), 'hex');
|
||||
const account = Account.fromPrivateKey(this.persist, privateKey, password);
|
||||
|
||||
this._store.push(account);
|
||||
this.lastAddress = account.address;
|
||||
|
||||
this.persist();
|
||||
|
||||
return account.address;
|
||||
}
|
||||
|
||||
set lastAddress (value) {
|
||||
this._last = value.toLowerCase();
|
||||
}
|
||||
|
||||
get lastAddress () {
|
||||
return this._last;
|
||||
}
|
||||
|
||||
get (address) {
|
||||
address = address.toLowerCase();
|
||||
|
||||
this.lastAddress = address;
|
||||
|
||||
const account = this._store.find((account) => account.address === address);
|
||||
|
||||
if (!account) {
|
||||
throw new Error(`Account not found: ${address}`);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
remove (address, password) {
|
||||
address = address.toLowerCase();
|
||||
|
||||
const index = this._store.findIndex((account) => account.address === address);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const account = this._store[index];
|
||||
|
||||
if (!account.isValidPassword(password)) {
|
||||
console.log('invalid password');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address === this.lastAddress) {
|
||||
this.lastAddress = NULL_ADDRESS;
|
||||
}
|
||||
|
||||
this._store.splice(index, 1);
|
||||
|
||||
this.persist();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
mapArray (mapper) {
|
||||
return this._store.map(mapper);
|
||||
}
|
||||
|
||||
mapObject (mapper) {
|
||||
const result = {};
|
||||
|
||||
this._store.forEach((account) => {
|
||||
result[account.address] = mapper(account);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return {
|
||||
last: this._last,
|
||||
store: this._store
|
||||
};
|
||||
}
|
||||
}
|
||||
21
js/src/api/local/accounts/index.js
Normal file
21
js/src/api/local/accounts/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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/>.
|
||||
|
||||
import Accounts from './accounts';
|
||||
|
||||
const accounts = new Accounts();
|
||||
|
||||
export default accounts;
|
||||
7778
js/src/api/local/ethkey/dictionary.js
Normal file
7778
js/src/api/local/ethkey/dictionary.js
Normal file
File diff suppressed because it is too large
Load Diff
87
js/src/api/local/ethkey/index.js
Normal file
87
js/src/api/local/ethkey/index.js
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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/>.
|
||||
|
||||
import dictionary from './dictionary';
|
||||
|
||||
// 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;
|
||||
|
||||
// Local accounts should never be used outside of the browser
|
||||
export let keythereum = null;
|
||||
|
||||
if (hasWebWorkers) {
|
||||
require('keythereum/dist/keythereum');
|
||||
|
||||
keythereum = window.keythereum;
|
||||
}
|
||||
|
||||
export function phraseToAddress (phrase) {
|
||||
return phraseToWallet(phrase).then((wallet) => wallet.address);
|
||||
}
|
||||
|
||||
export function phraseToWallet (phrase) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new KeyWorker();
|
||||
|
||||
worker.postMessage(phrase);
|
||||
worker.onmessage = ({ data }) => {
|
||||
resolve(data);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function randomBytes (length) {
|
||||
if (keythereum) {
|
||||
return keythereum.crypto.randomBytes(length);
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
buf[i] = Math.random() * 255;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function randomNumber (max) {
|
||||
// Use 24 bits to avoid the integer becoming signed via bitshifts
|
||||
const rand = randomBytes(3);
|
||||
|
||||
const integer = (rand[0] << 16) | (rand[1] << 8) | rand[2];
|
||||
|
||||
// floor to integer value via bitor 0
|
||||
return ((integer / 0xFFFFFF) * max) | 0;
|
||||
}
|
||||
|
||||
export function randomWord () {
|
||||
// TODO mh: use better entropy
|
||||
const index = randomNumber(dictionary.length);
|
||||
|
||||
return dictionary[index];
|
||||
}
|
||||
|
||||
export function randomPhrase (length) {
|
||||
const words = [];
|
||||
|
||||
while (length--) {
|
||||
words.push(randomWord());
|
||||
}
|
||||
|
||||
return words.join(' ');
|
||||
}
|
||||
95
js/src/api/local/ethkey/index.spec.js
Normal file
95
js/src/api/local/ethkey/index.spec.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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/>.
|
||||
|
||||
import dictionary from './dictionary';
|
||||
import {
|
||||
phraseToAddress,
|
||||
phraseToWallet,
|
||||
randomNumber,
|
||||
randomWord,
|
||||
randomPhrase
|
||||
} from './';
|
||||
|
||||
describe('api/local/ethkey', () => {
|
||||
describe('randomNumber', () => {
|
||||
it('generates numbers in range', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const number = randomNumber(7777);
|
||||
|
||||
expect(number).to.be.at.least(0);
|
||||
expect(number).to.be.below(7777);
|
||||
expect(number % 1).to.be.equal(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('randomWord', () => {
|
||||
it('generates a random word from the dictionary', () => {
|
||||
const word = randomWord();
|
||||
|
||||
expect(dictionary.includes(word)).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('randomPhrase', () => {
|
||||
it('generates a random phrase from the dictionary', () => {
|
||||
const phrase = randomPhrase(7).split(' ');
|
||||
|
||||
expect(phrase.length).to.be.equal(7);
|
||||
|
||||
phrase.forEach((word) => {
|
||||
expect(dictionary.includes(word)).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('phraseToAddress', function () {
|
||||
this.timeout(10000);
|
||||
|
||||
it('generates a valid address', () => {
|
||||
const phrase = randomPhrase(12);
|
||||
|
||||
return phraseToAddress(phrase).then((address) => {
|
||||
expect(address.length).to.be.equal(42);
|
||||
expect(address.slice(0, 4)).to.be.equal('0x00');
|
||||
});
|
||||
});
|
||||
|
||||
it('generates valid address for empty phrase', () => {
|
||||
return phraseToAddress('').then((address) => {
|
||||
expect(address).to.be.equal('0x00a329c0648769a73afac7f9381e08fb43dbea72');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('phraseToWallet', function () {
|
||||
this.timeout(10000);
|
||||
|
||||
it('generates a valid wallet object', () => {
|
||||
const phrase = randomPhrase(12);
|
||||
|
||||
return phraseToWallet(phrase).then((wallet) => {
|
||||
expect(wallet.address.length).to.be.equal(42);
|
||||
expect(wallet.secret.length).to.be.equal(66);
|
||||
expect(wallet.public.length).to.be.equal(130);
|
||||
|
||||
expect(wallet.address.slice(0, 4)).to.be.equal('0x00');
|
||||
expect(wallet.secret.slice(0, 2)).to.be.equal('0x');
|
||||
expect(wallet.public.slice(0, 2)).to.be.equal('0x');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
89
js/src/api/local/ethkey/worker.js
Normal file
89
js/src/api/local/ethkey/worker.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// 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/>.
|
||||
|
||||
import { keccak_256 as keccak256 } from 'js-sha3';
|
||||
import secp256k1 from 'secp256k1/js';
|
||||
|
||||
// Stay compatible between environments
|
||||
if (typeof self !== 'object') {
|
||||
const scope = typeof global === 'undefined' ? window : global;
|
||||
|
||||
scope.self = scope;
|
||||
}
|
||||
|
||||
function bytesToHex (bytes) {
|
||||
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join('');
|
||||
}
|
||||
|
||||
// Logic ported from /ethkey/src/brain.rs
|
||||
function phraseToWallet (phrase) {
|
||||
let secret = keccak256.array(phrase);
|
||||
|
||||
for (let i = 0; i < 16384; i++) {
|
||||
secret = keccak256.array(secret);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
secret = keccak256.array(secret);
|
||||
|
||||
const secretBuf = Buffer.from(secret);
|
||||
|
||||
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 = {
|
||||
secret: bytesToHex(secretBuf),
|
||||
public: bytesToHex(publicBuf),
|
||||
address: bytesToHex(address)
|
||||
};
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = function ({ data }) {
|
||||
const wallet = phraseToWallet(data);
|
||||
|
||||
postMessage(wallet);
|
||||
close();
|
||||
};
|
||||
|
||||
// Emulate a web worker in Node.js
|
||||
class KeyWorker {
|
||||
postMessage (data) {
|
||||
// Force async
|
||||
setTimeout(() => {
|
||||
const wallet = phraseToWallet(data);
|
||||
|
||||
this.onmessage({ data: wallet });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
onmessage (event) {
|
||||
// no-op to be overriden
|
||||
}
|
||||
}
|
||||
|
||||
if (exports != null) {
|
||||
exports.KeyWorker = KeyWorker;
|
||||
}
|
||||
17
js/src/api/local/index.js
Normal file
17
js/src/api/local/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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/>.
|
||||
|
||||
export LocalAccountsMiddleware from './middleware';
|
||||
173
js/src/api/local/middleware.js
Normal file
173
js/src/api/local/middleware.js
Normal file
@@ -0,0 +1,173 @@
|
||||
// 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/>.
|
||||
|
||||
import EthereumTx from 'ethereumjs-tx';
|
||||
import accounts from './accounts';
|
||||
import transactions from './transactions';
|
||||
import { Middleware } from '../transport';
|
||||
import { inNumber16 } from '../format/input';
|
||||
import { phraseToWallet, phraseToAddress, randomPhrase } from './ethkey';
|
||||
|
||||
export default class LocalAccountsMiddleware extends Middleware {
|
||||
// Maps transaction requests to transaction hashes.
|
||||
// This allows the locally-signed transactions to emulate the signer.
|
||||
transactionHashes = {};
|
||||
transactions = {};
|
||||
|
||||
// Current transaction id. This doesn't need to be stored, as it's
|
||||
// only relevant for the current the session.
|
||||
transactionId = 1;
|
||||
|
||||
constructor (transport) {
|
||||
super(transport);
|
||||
|
||||
const register = this.register.bind(this);
|
||||
|
||||
register('eth_accounts', () => {
|
||||
return accounts.mapArray((account) => account.address);
|
||||
});
|
||||
|
||||
register('eth_coinbase', () => {
|
||||
return accounts.lastAddress;
|
||||
});
|
||||
|
||||
register('parity_accountsInfo', () => {
|
||||
return accounts.mapObject(({ name }) => {
|
||||
return { name };
|
||||
});
|
||||
});
|
||||
|
||||
register('parity_allAccountsInfo', () => {
|
||||
return accounts.mapObject(({ name, meta, uuid }) => {
|
||||
return { name, meta, uuid };
|
||||
});
|
||||
});
|
||||
|
||||
register('parity_checkRequest', ([id]) => {
|
||||
return transactions.hash(id) || Promise.resolve(null);
|
||||
});
|
||||
|
||||
register('parity_defaultAccount', () => {
|
||||
return accounts.lastAddress;
|
||||
});
|
||||
|
||||
register('parity_generateSecretPhrase', () => {
|
||||
return randomPhrase(12);
|
||||
});
|
||||
|
||||
register('parity_getNewDappsAddresses', () => {
|
||||
return [];
|
||||
});
|
||||
|
||||
register('parity_hardwareAccountsInfo', () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
register('parity_newAccountFromPhrase', ([phrase, password]) => {
|
||||
return phraseToWallet(phrase)
|
||||
.then((wallet) => {
|
||||
return accounts.create(wallet.secret, password);
|
||||
});
|
||||
});
|
||||
|
||||
register('parity_setAccountMeta', ([address, meta]) => {
|
||||
accounts.get(address).meta = meta;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
register('parity_setAccountName', ([address, name]) => {
|
||||
accounts.get(address).name = name;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
register('parity_postTransaction', ([tx]) => {
|
||||
if (!tx.from) {
|
||||
tx.from = accounts.lastAddress;
|
||||
}
|
||||
|
||||
tx.nonce = null;
|
||||
tx.condition = null;
|
||||
|
||||
return transactions.add(tx);
|
||||
});
|
||||
|
||||
register('parity_phraseToAddress', ([phrase]) => {
|
||||
return phraseToAddress(phrase);
|
||||
});
|
||||
|
||||
register('parity_useLocalAccounts', () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
register('parity_listGethAccounts', () => {
|
||||
return [];
|
||||
});
|
||||
|
||||
register('parity_listRecentDapps', () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
register('parity_killAccount', ([address, password]) => {
|
||||
return accounts.remove(address, password);
|
||||
});
|
||||
|
||||
register('signer_confirmRequest', ([id, modify, password]) => {
|
||||
const {
|
||||
gasPrice,
|
||||
gas: gasLimit,
|
||||
from,
|
||||
to,
|
||||
value,
|
||||
data
|
||||
} = Object.assign(transactions.get(id), modify);
|
||||
|
||||
return this
|
||||
.rpcRequest('parity_nextNonce', [from])
|
||||
.then((nonce) => {
|
||||
const tx = new EthereumTx({
|
||||
nonce,
|
||||
to,
|
||||
data,
|
||||
gasLimit: inNumber16(gasLimit),
|
||||
gasPrice: inNumber16(gasPrice),
|
||||
value: inNumber16(value)
|
||||
});
|
||||
const account = accounts.get(from);
|
||||
|
||||
tx.sign(account.decryptPrivateKey(password));
|
||||
|
||||
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
||||
|
||||
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]);
|
||||
})
|
||||
.then((hash) => {
|
||||
transactions.confirm(id, hash);
|
||||
|
||||
return {};
|
||||
});
|
||||
});
|
||||
|
||||
register('signer_rejectRequest', ([id]) => {
|
||||
return transactions.reject(id);
|
||||
});
|
||||
|
||||
register('signer_requestsToConfirm', () => {
|
||||
return transactions.requestsToConfirm();
|
||||
});
|
||||
}
|
||||
}
|
||||
123
js/src/api/local/transactions.js
Normal file
123
js/src/api/local/transactions.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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/>.
|
||||
|
||||
import { toHex } from '../util/format';
|
||||
import { TransportError } from '../transport';
|
||||
|
||||
const AWAITING = Symbol('awaiting');
|
||||
const CONFIRMED = Symbol('confirmed');
|
||||
const REJECTED = Symbol('rejected');
|
||||
|
||||
class Transactions {
|
||||
constructor () {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// should only really be needed in the constructor and tests
|
||||
reset () {
|
||||
this._id = 1;
|
||||
this._states = {};
|
||||
}
|
||||
|
||||
nextId () {
|
||||
return toHex(this._id++);
|
||||
}
|
||||
|
||||
add (tx) {
|
||||
const id = this.nextId();
|
||||
|
||||
this._states[id] = {
|
||||
status: AWAITING,
|
||||
transaction: tx
|
||||
};
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
get (id) {
|
||||
const state = this._states[id];
|
||||
|
||||
if (!state || state.status !== AWAITING) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.transaction;
|
||||
}
|
||||
|
||||
hash (id) {
|
||||
const state = this._states[id];
|
||||
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (state.status) {
|
||||
case REJECTED:
|
||||
throw TransportError.requestRejected();
|
||||
case CONFIRMED:
|
||||
return state.hash;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
confirm (id, hash) {
|
||||
const state = this._states[id];
|
||||
|
||||
if (!state || state.status !== AWAITING) {
|
||||
throw new Error('Trying to confirm an invalid transaction');
|
||||
}
|
||||
|
||||
state.hash = hash;
|
||||
state.status = CONFIRMED;
|
||||
}
|
||||
|
||||
reject (id) {
|
||||
const state = this._states[id];
|
||||
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.status = REJECTED;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
requestsToConfirm () {
|
||||
const result = [];
|
||||
|
||||
Object.keys(this._states).forEach((id) => {
|
||||
const state = this._states[id];
|
||||
|
||||
if (state.status === AWAITING) {
|
||||
result.push({
|
||||
id,
|
||||
origin: {
|
||||
signer: '0x0'
|
||||
},
|
||||
payload: {
|
||||
sendTransaction: state.transaction
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Transactions();
|
||||
68
js/src/api/local/transactions.spec.js
Normal file
68
js/src/api/local/transactions.spec.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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/>.
|
||||
|
||||
import transactions from './transactions';
|
||||
import { TransportError } from '../transport/error';
|
||||
|
||||
const DUMMY_TX = 'dummy';
|
||||
|
||||
describe('api/local/transactions', () => {
|
||||
beforeEach(() => {
|
||||
transactions.reset();
|
||||
});
|
||||
|
||||
it('can store transactions', () => {
|
||||
const id1 = transactions.add(DUMMY_TX);
|
||||
const id2 = transactions.add(DUMMY_TX);
|
||||
const requests = transactions.requestsToConfirm();
|
||||
|
||||
expect(id1).to.be.equal('0x1');
|
||||
expect(id2).to.be.equal('0x2');
|
||||
expect(requests.length).to.be.equal(2);
|
||||
expect(requests[0].id).to.be.equal(id1);
|
||||
expect(requests[1].id).to.be.equal(id2);
|
||||
expect(requests[0].payload.sendTransaction).to.be.equal(DUMMY_TX);
|
||||
expect(requests[1].payload.sendTransaction).to.be.equal(DUMMY_TX);
|
||||
});
|
||||
|
||||
it('can confirm transactions', () => {
|
||||
const id1 = transactions.add(DUMMY_TX);
|
||||
const id2 = transactions.add(DUMMY_TX);
|
||||
|
||||
const hash1 = '0x1111111111111111111111111111111111111111';
|
||||
const hash2 = '0x2222222222222222222222222222222222222222';
|
||||
|
||||
transactions.confirm(id1, hash1);
|
||||
transactions.confirm(id2, hash2);
|
||||
|
||||
const requests = transactions.requestsToConfirm();
|
||||
|
||||
expect(requests.length).to.be.equal(0);
|
||||
expect(transactions.hash(id1)).to.be.equal(hash1);
|
||||
expect(transactions.hash(id2)).to.be.equal(hash2);
|
||||
});
|
||||
|
||||
it('can reject transactions', () => {
|
||||
const id = transactions.add(DUMMY_TX);
|
||||
|
||||
transactions.reject(id);
|
||||
|
||||
const requests = transactions.requestsToConfirm();
|
||||
|
||||
expect(requests.length).to.be.equal(0);
|
||||
expect(() => transactions.hash(id)).to.throw(TransportError);
|
||||
});
|
||||
});
|
||||
@@ -42,6 +42,10 @@ export const ERROR_CODES = {
|
||||
};
|
||||
|
||||
export default class TransportError extends ExtendableError {
|
||||
static requestRejected (method = null) {
|
||||
return new TransportError(method, ERROR_CODES.REQUEST_REJECTED, 'Request has been rejected.');
|
||||
}
|
||||
|
||||
constructor (method, code, message) {
|
||||
const m = `${method}: ${code}: ${message}`;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class Http extends JsonRpcBase {
|
||||
};
|
||||
}
|
||||
|
||||
execute (method, ...params) {
|
||||
_execute (method, params) {
|
||||
const request = this._encodeOptions(method, params);
|
||||
|
||||
return fetch(this._url, request)
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
|
||||
export Http from './http';
|
||||
export Ws from './ws';
|
||||
export TransportError from './error.js';
|
||||
export TransportError from './error';
|
||||
export Middleware from './middleware';
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Logging } from '../subscriptions';
|
||||
|
||||
export default class JsonRpcBase extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -23,6 +24,7 @@ export default class JsonRpcBase extends EventEmitter {
|
||||
this._id = 1;
|
||||
this._debug = false;
|
||||
this._connected = false;
|
||||
this._middlewareList = Promise.resolve([]);
|
||||
}
|
||||
|
||||
encode (method, params) {
|
||||
@@ -36,6 +38,65 @@ export default class JsonRpcBase extends EventEmitter {
|
||||
return json;
|
||||
}
|
||||
|
||||
addMiddleware (middleware) {
|
||||
this._middlewareList = Promise
|
||||
.all([
|
||||
middleware,
|
||||
this._middlewareList
|
||||
])
|
||||
.then(([middleware, middlewareList]) => {
|
||||
// Do nothing if `handlerPromise` resolves to a null-y value.
|
||||
if (middleware == null) {
|
||||
return middlewareList;
|
||||
}
|
||||
|
||||
// don't mutate the original array
|
||||
return middlewareList.concat([middleware]);
|
||||
});
|
||||
}
|
||||
|
||||
_wrapSuccessResult (result) {
|
||||
return {
|
||||
id: this._id,
|
||||
jsonrpc: '2.0',
|
||||
result
|
||||
};
|
||||
}
|
||||
|
||||
_wrapErrorResult (error) {
|
||||
return {
|
||||
id: this._id,
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.text
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
execute (method, ...params) {
|
||||
return this._middlewareList.then((middlewareList) => {
|
||||
for (const middleware of middlewareList) {
|
||||
const res = middleware.handle(method, params);
|
||||
|
||||
if (res != null) {
|
||||
const result = this._wrapSuccessResult(res);
|
||||
const json = this.encode(method, params);
|
||||
|
||||
Logging.send(method, params, { json, result });
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return this._execute(method, params);
|
||||
});
|
||||
}
|
||||
|
||||
_execute () {
|
||||
throw new Error('Missing implementation of JsonRpcBase#_execute');
|
||||
}
|
||||
|
||||
_setConnected () {
|
||||
if (!this._connected) {
|
||||
this._connected = true;
|
||||
|
||||
42
js/src/api/transport/middleware.js
Normal file
42
js/src/api/transport/middleware.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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/>.
|
||||
|
||||
export default class Middleware {
|
||||
constructor (transport) {
|
||||
this._transport = transport;
|
||||
this._handlers = {};
|
||||
}
|
||||
|
||||
register (method, handler) {
|
||||
this._handlers[method] = handler;
|
||||
}
|
||||
|
||||
handle (method, params) {
|
||||
const handler = this._handlers[method];
|
||||
|
||||
if (handler != null) {
|
||||
const response = handler(params);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
rpcRequest (method, params) {
|
||||
return this._transport._execute(method, params);
|
||||
}
|
||||
}
|
||||
58
js/src/api/transport/middleware.spec.js
Normal file
58
js/src/api/transport/middleware.spec.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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/>.
|
||||
|
||||
import Middleware from './middleware';
|
||||
import JsonRpcBase from './jsonRpcBase';
|
||||
|
||||
const MOCKED = 'mocked!';
|
||||
|
||||
class MockTransport extends JsonRpcBase {
|
||||
_execute () {
|
||||
return Promise.resolve(MOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
describe('api/transport/Middleware', () => {
|
||||
let middleware;
|
||||
let transport;
|
||||
|
||||
beforeEach(() => {
|
||||
transport = new MockTransport();
|
||||
middleware = new Middleware(transport);
|
||||
|
||||
middleware.register('mock_rpc', ([num]) => num);
|
||||
middleware.register('mock_null', () => null);
|
||||
transport.addMiddleware(middleware);
|
||||
});
|
||||
|
||||
it('Routes requests to middleware', () => {
|
||||
return transport.execute('mock_rpc', 100).then((num) => {
|
||||
expect(num).to.be.equal(100);
|
||||
});
|
||||
});
|
||||
|
||||
it('Passes non-mocked requests through', () => {
|
||||
return transport.execute('not_moced', 200).then((result) => {
|
||||
expect(result).to.be.equal(MOCKED);
|
||||
});
|
||||
});
|
||||
|
||||
it('Passes mocked requests through, if middleware returns null', () => {
|
||||
return transport.execute('mock_null', 300).then((result) => {
|
||||
expect(result).to.be.equal(MOCKED);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -244,7 +244,7 @@ export default class Ws extends JsonRpcBase {
|
||||
message.timestamp = Date.now();
|
||||
}
|
||||
|
||||
execute (method, ...params) {
|
||||
_execute (method, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.id;
|
||||
const json = this.encode(method, params);
|
||||
|
||||
Reference in New Issue
Block a user