Merge pull request #5304 from paritytech/mh-webserver

Public node with accounts and signing in Frontend
This commit is contained in:
Maciej Hirsz 2017-03-29 17:48:47 +02:00 committed by GitHub
commit ab2c3468d5
45 changed files with 9060 additions and 89 deletions

View File

@ -29,7 +29,7 @@ impl Brain {
impl Generator for Brain {
fn generate(self) -> Result<KeyPair, Error> {
let seed = self.0;
let mut secret = seed.bytes().collect::<Vec<u8>>().keccak256();
let mut secret = seed.into_bytes().keccak256();
let mut i = 0;
loop {

View File

@ -171,6 +171,7 @@
"geopattern": "1.2.3",
"isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5",
"keythereum": "0.4.3",
"lodash": "4.17.2",
"loglevel": "1.4.1",
"marked": "0.3.6",
@ -207,6 +208,7 @@
"redux-thunk": "2.1.0",
"rlp": "2.0.0",
"scryptsy": "2.0.0",
"secp256k1": "3.2.5",
"solc": "ngotchac/solc-js",
"store": "1.3.20",
"sw-toolbox": "^3.6.0",

View File

@ -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 () {

View 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
};
}
}

View 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
};
}
}

View 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;

File diff suppressed because it is too large Load Diff

View 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(' ');
}

View 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.skip('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.skip('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');
});
});
});
});

View 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
View 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';

View 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();
});
}
}

View 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();

View 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);
});
});

View File

@ -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}`;

View File

@ -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)

View File

@ -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';

View File

@ -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;

View 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);
}
}

View 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);
});
});
});

View File

@ -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);

View File

@ -56,6 +56,11 @@ module.exports = {
'babel-loader?cacheDirectory=true'
]
},
{
test: /\.js$/,
include: /node_modules\/ethereumjs-tx/,
use: 'babel-loader'
},
{
test: /\.json$/,
use: [ 'json-loader' ]

View File

@ -4,6 +4,7 @@ mode_timeout = 300
mode_alarm = 3600
auto_update = "none"
release_track = "current"
public_node = false
no_download = false
no_consensus = false

View File

@ -88,6 +88,7 @@ usage! {
flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(),
flag_auto_update: String = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(),
flag_release_track: String = "current", or |c: &Config| otry!(c.parity).release_track.clone(),
flag_public_node: bool = false, or |c: &Config| otry!(c.parity).public_node.clone(),
flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(),
flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(),
flag_chain: String = "foundation", or |c: &Config| otry!(c.parity).chain.clone(),
@ -365,6 +366,7 @@ struct Operating {
mode_alarm: Option<u64>,
auto_update: Option<String>,
release_track: Option<String>,
public_node: Option<bool>,
no_download: Option<bool>,
no_consensus: Option<bool>,
chain: Option<String>,
@ -623,6 +625,7 @@ mod tests {
flag_mode_alarm: 3600u64,
flag_auto_update: "none".into(),
flag_release_track: "current".into(),
flag_public_node: false,
flag_no_download: false,
flag_no_consensus: false,
flag_chain: "xyz".into(),
@ -825,6 +828,7 @@ mod tests {
mode_alarm: Some(10u64),
auto_update: None,
release_track: None,
public_node: None,
no_download: None,
no_consensus: None,
chain: Some("./chain.json".into()),

View File

@ -48,6 +48,9 @@ Operating Options:
testing - Testing releases (do not use).
current - Whatever track this executable was
released on (default: {flag_release_track}).
--public-node Start Parity as a public web server. Account storage
and transaction signing will be delegated to the UI.
(default: {flag_public_node}).
--no-download Normally new releases will be downloaded ready for
updating. This disables it. Not recommended.
(default: {flag_no_download}).

View File

@ -128,6 +128,7 @@ impl Configuration {
Some(true) if pruning == Pruning::Specific(Algorithm::Archive) => writeln!(&mut stderr(), "Warning: Warp Sync is disabled because pruning mode is set to archive").expect("Error writing to stderr"),
_ => {},
};
let public_node = self.args.flag_public_node;
let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive);
let geth_compatibility = self.args.flag_geth;
let ui_address = self.ui_port().map(|port| (self.ui_interface(), port));
@ -360,6 +361,7 @@ impl Configuration {
wal: wal,
vm_type: vm_type,
warp_sync: warp_sync,
public_node: public_node,
geth_compatibility: geth_compatibility,
ui_address: ui_address,
net_settings: self.network_settings(),
@ -709,14 +711,26 @@ impl Configuration {
}
fn rpc_apis(&self) -> String {
let mut apis = self.args.flag_rpcapi.clone().unwrap_or(self.args.flag_jsonrpc_apis.clone());
let mut apis: Vec<&str> = self.args.flag_rpcapi
.as_ref()
.unwrap_or(&self.args.flag_jsonrpc_apis)
.split(",")
.collect();
if self.args.flag_geth {
if !apis.is_empty() {
apis.push_str(",");
}
apis.push_str("personal");
apis.push("personal");
}
apis
if self.args.flag_public_node {
apis.retain(|api| {
match *api {
"eth" | "net" | "parity" | "rpc" | "web3" => true,
_ => false
}
});
}
apis.join(",")
}
fn cors(cors: Option<&String>) -> Option<Vec<String>> {
@ -1167,6 +1181,7 @@ mod tests {
ipc_conf: Default::default(),
net_conf: default_network_config(),
network_id: None,
public_node: false,
warp_sync: true,
acc_conf: Default::default(),
gas_pricer: Default::default(),

View File

@ -118,7 +118,7 @@ pub struct Dependencies {
pub snapshot: Arc<SnapshotService>,
pub sync: Arc<SyncProvider>,
pub net: Arc<ManageNetwork>,
pub secret_store: Arc<AccountProvider>,
pub secret_store: Option<Arc<AccountProvider>>,
pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,

View File

@ -81,6 +81,7 @@ pub struct RunCmd {
pub net_conf: NetworkConfiguration,
pub network_id: Option<u64>,
pub warp_sync: bool,
pub public_node: bool,
pub acc_conf: AccountsConfig,
pub gas_pricer: GasPricerConfig,
pub miner_extras: MinerExtras,
@ -407,6 +408,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
// set up dependencies for rpc servers
let rpc_stats = Arc::new(informant::RpcStats::default());
let signer_path = cmd.signer_conf.signer_path.clone();
let secret_store = match cmd.public_node {
true => None,
false => Some(account_provider.clone())
};
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
@ -415,7 +420,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
client: client.clone(),
sync: sync_provider.clone(),
net: manage_network.clone(),
secret_store: account_provider.clone(),
secret_store: secret_store,
miner: miner.clone(),
external_miner: external_miner.clone(),
logger: logger.clone(),

View File

@ -0,0 +1,27 @@
// 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/>.
use std::sync::{Arc, Weak};
use ethcore::account_provider::AccountProvider;
use jsonrpc_core::Error;
use v1::helpers::errors;
pub fn unwrap_provider(provider: &Option<Weak<AccountProvider>>) -> Result<Arc<AccountProvider>, Error> {
match *provider {
Some(ref weak) => weak.upgrade().ok_or_else(Error::internal_error),
None => Err(errors::public_unsupported(None)),
}
}

View File

@ -65,6 +65,14 @@ pub fn light_unimplemented(details: Option<String>) -> Error {
}
}
pub fn public_unsupported(details: Option<String>) -> Error {
Error {
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
message: "Method disallowed when running parity as a public node.".into(),
data: details.map(Value::String),
}
}
pub fn request_not_found() -> Error {
Error {
code: ErrorCode::ServerError(codes::REQUEST_NOT_FOUND),

View File

@ -17,6 +17,7 @@
#[macro_use]
pub mod errors;
pub mod accounts;
pub mod block_import;
pub mod dispatch;
pub mod fake_sign;

View File

@ -46,6 +46,7 @@ use jsonrpc_macros::Trailing;
use v1::helpers::{errors, limit_logs, fake_sign};
use v1::helpers::dispatch::{Dispatcher, FullDispatcher, default_gas_price};
use v1::helpers::block_import::is_major_importing;
use v1::helpers::accounts::unwrap_provider;
use v1::traits::Eth;
use v1::types::{
RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo,
@ -97,7 +98,7 @@ pub struct EthClient<C, SN: ?Sized, S: ?Sized, M, EM> where
client: Weak<C>,
snapshot: Weak<SN>,
sync: Weak<S>,
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
miner: Weak<M>,
external_miner: Arc<EM>,
seed_compute: Mutex<SeedHashCompute>,
@ -116,7 +117,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
client: &Arc<C>,
snapshot: &Arc<SN>,
sync: &Arc<S>,
accounts: &Arc<AccountProvider>,
accounts: &Option<Arc<AccountProvider>>,
miner: &Arc<M>,
em: &Arc<EM>,
options: EthClientOptions
@ -126,13 +127,19 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
snapshot: Arc::downgrade(snapshot),
sync: Arc::downgrade(sync),
miner: Arc::downgrade(miner),
accounts: Arc::downgrade(accounts),
accounts: accounts.as_ref().map(Arc::downgrade),
external_miner: em.clone(),
seed_compute: Mutex::new(SeedHashCompute::new()),
options: options,
}
}
/// Attempt to get the `Arc<AccountProvider>`, errors if provider was not
/// set, or if upgrading the weak reference failed.
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
fn block(&self, id: BlockId, include_txs: bool) -> Result<Option<RichBlock>, Error> {
let client = take_weak!(self.client);
match (client.block(id.clone()), client.block_total_difficulty(id)) {
@ -223,7 +230,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
}
fn dapp_accounts(&self, dapp: DappId) -> Result<Vec<H160>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store
.note_dapp_used(dapp.clone())
.and_then(|_| store.dapp_addresses(dapp))

View File

@ -40,7 +40,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo
AccountInfo, HwAccountInfo,
};
/// Parity implementation for light client.

View File

@ -37,6 +37,7 @@ use updater::{Service as UpdateService};
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::accounts::unwrap_provider;
use v1::helpers::dispatch::DEFAULT_MAC;
use v1::metadata::Metadata;
use v1::traits::Parity;
@ -46,7 +47,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo
AccountInfo, HwAccountInfo,
};
/// Parity implementation.
@ -61,7 +62,7 @@ pub struct ParityClient<C, M, S: ?Sized, U> where
sync: Weak<S>,
updater: Weak<U>,
net: Weak<ManageNetwork>,
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>,
@ -82,7 +83,7 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
sync: &Arc<S>,
updater: &Arc<U>,
net: &Arc<ManageNetwork>,
store: &Arc<AccountProvider>,
store: &Option<Arc<AccountProvider>>,
logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>,
@ -95,7 +96,7 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
sync: Arc::downgrade(sync),
updater: Arc::downgrade(updater),
net: Arc::downgrade(net),
accounts: Arc::downgrade(store),
accounts: store.as_ref().map(Arc::downgrade),
logger: logger,
settings: settings,
signer: signer,
@ -103,6 +104,12 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
dapps_port: dapps_port,
}
}
/// Attempt to get the `Arc<AccountProvider>`, errors if provider was not
/// set, or if upgrading the weak reference failed.
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
}
impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
@ -116,7 +123,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<H160, AccountInfo>, Error> {
let dapp = dapp.0;
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let dapp_accounts = store
.note_dapp_used(dapp.clone().into())
.and_then(|_| store.dapp_addresses(dapp.into()))
@ -136,7 +143,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
}
fn hardware_accounts_info(&self) -> Result<BTreeMap<H160, HwAccountInfo>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let info = store.hardware_accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
Ok(info
.into_iter()
@ -148,7 +155,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> {
let dapp_id = meta.dapp_id();
future::ok(
take_weakf!(self.accounts)
try_bf!(self.account_provider())
.dapp_default_address(dapp_id.into())
.map(Into::into)
.ok()
@ -376,9 +383,13 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
fn node_kind(&self) -> Result<::v1::types::NodeKind, Error> {
use ::v1::types::{NodeKind, Availability, Capability};
// TODO [maciej]: public availability flag.
let availability = match self.accounts {
Some(_) => Availability::Personal,
None => Availability::Public
};
Ok(NodeKind {
availability: Availability::Personal,
availability: availability,
capability: Capability::Full,
})
}

View File

@ -25,26 +25,33 @@ use ethcore::account_provider::AccountProvider;
use jsonrpc_core::Error;
use v1::helpers::errors;
use v1::helpers::accounts::unwrap_provider;
use v1::traits::ParityAccounts;
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId, Derive, DeriveHierarchical, DeriveHash};
/// Account management (personal) rpc implementation.
pub struct ParityAccountsClient {
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
}
impl ParityAccountsClient {
/// Creates new PersonalClient
pub fn new(store: &Arc<AccountProvider>) -> Self {
pub fn new(store: &Option<Arc<AccountProvider>>) -> Self {
ParityAccountsClient {
accounts: Arc::downgrade(store),
accounts: store.as_ref().map(Arc::downgrade),
}
}
/// Attempt to get the `Arc<AccountProvider>`, errors if provider was not
/// set, or if upgrading the weak reference failed.
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
}
impl ParityAccounts for ParityAccountsClient {
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
let other = store.addresses_info();
@ -66,7 +73,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let brain = Brain::new(phrase).generate().unwrap();
store.insert_account(brain.secret().clone(), &pass)
@ -75,7 +82,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn new_account_from_wallet(&self, json: String, pass: String) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.import_presale(json.as_bytes(), &pass)
.or_else(|_| store.import_wallet(json.as_bytes(), &pass))
@ -84,7 +91,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let secret = Secret::from_slice(&secret.0)
.map_err(|e| errors::account("Could not create account.", e))?;
@ -96,14 +103,14 @@ impl ParityAccounts for ParityAccountsClient {
fn test_password(&self, account: RpcH160, password: String) -> Result<bool, Error> {
let account: Address = account.into();
take_weak!(self.accounts)
self.account_provider()?
.test_password(&account, &password)
.map_err(|e| errors::account("Could not fetch account info.", e))
}
fn change_password(&self, account: RpcH160, password: String, new_password: String) -> Result<bool, Error> {
let account: Address = account.into();
take_weak!(self.accounts)
self.account_provider()?
.change_password(&account, password, new_password)
.map(|_| true)
.map_err(|e| errors::account("Could not fetch account info.", e))
@ -111,14 +118,14 @@ impl ParityAccounts for ParityAccountsClient {
fn kill_account(&self, account: RpcH160, password: String) -> Result<bool, Error> {
let account: Address = account.into();
take_weak!(self.accounts)
self.account_provider()?
.kill_account(&account, &password)
.map(|_| true)
.map_err(|e| errors::account("Could not delete account.", e))
}
fn remove_address(&self, addr: RpcH160) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let addr: Address = addr.into();
store.remove_address(addr);
@ -126,7 +133,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_account_name(&self, addr: RpcH160, name: String) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let addr: Address = addr.into();
store.set_account_name(addr.clone(), name.clone())
@ -135,7 +142,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_account_meta(&self, addr: RpcH160, meta: String) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let addr: Address = addr.into();
store.set_account_meta(addr.clone(), meta.clone())
@ -144,7 +151,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_dapp_addresses(&self, dapp: DappId, addresses: Option<Vec<RpcH160>>) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.set_dapp_addresses(dapp.into(), addresses.map(into_vec))
.map_err(|e| errors::account("Couldn't set dapp addresses.", e))
@ -152,7 +159,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<RpcH160>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.dapp_addresses(dapp.into())
.map_err(|e| errors::account("Couldn't get dapp addresses.", e))
@ -160,7 +167,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_dapp_default_address(&self, dapp: DappId, address: RpcH160) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.set_dapp_default_address(dapp.into(), address.into())
.map_err(|e| errors::account("Couldn't set dapp default address.", e))
@ -168,7 +175,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn dapp_default_address(&self, dapp: DappId) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.dapp_default_address(dapp.into())
.map_err(|e| errors::account("Couldn't get dapp default address.", e))
@ -176,7 +183,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_new_dapps_addresses(&self, addresses: Option<Vec<RpcH160>>) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store
.set_new_dapps_addresses(addresses.map(into_vec))
@ -185,7 +192,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn new_dapps_addresses(&self) -> Result<Option<Vec<RpcH160>>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.new_dapps_addresses()
.map_err(|e| errors::account("Couldn't get dapps addresses.", e))
@ -193,7 +200,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn set_new_dapps_default_address(&self, address: RpcH160) -> Result<bool, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.set_new_dapps_default_address(address.into())
.map_err(|e| errors::account("Couldn't set new dapps default address.", e))
@ -201,7 +208,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn new_dapps_default_address(&self) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.new_dapps_default_address()
.map_err(|e| errors::account("Couldn't get new dapps default address.", e))
@ -209,7 +216,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn recent_dapps(&self) -> Result<BTreeMap<DappId, u64>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.recent_dapps()
.map_err(|e| errors::account("Couldn't get recent dapps.", e))
@ -217,7 +224,7 @@ impl ParityAccounts for ParityAccountsClient {
}
fn import_geth_accounts(&self, addresses: Vec<RpcH160>) -> Result<Vec<RpcH160>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store
.import_geth_accounts(into_vec(addresses), false)
@ -226,66 +233,66 @@ impl ParityAccounts for ParityAccountsClient {
}
fn geth_accounts(&self) -> Result<Vec<RpcH160>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
Ok(into_vec(store.list_geth_accounts(false)))
}
fn create_vault(&self, name: String, password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.create_vault(&name, &password)
.map_err(|e| errors::account("Could not create vault.", e))
.map(|_| true)
}
fn open_vault(&self, name: String, password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.open_vault(&name, &password)
.map_err(|e| errors::account("Could not open vault.", e))
.map(|_| true)
}
fn close_vault(&self, name: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.close_vault(&name)
.map_err(|e| errors::account("Could not close vault.", e))
.map(|_| true)
}
fn list_vaults(&self) -> Result<Vec<String>, Error> {
take_weak!(self.accounts)
self.account_provider()?
.list_vaults()
.map_err(|e| errors::account("Could not list vaults.", e))
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
take_weak!(self.accounts)
self.account_provider()?
.list_opened_vaults()
.map_err(|e| errors::account("Could not list vaults.", e))
}
fn change_vault_password(&self, name: String, new_password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.change_vault_password(&name, &new_password)
.map_err(|e| errors::account("Could not change vault password.", e))
.map(|_| true)
}
fn change_vault(&self, address: RpcH160, new_vault: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.change_vault(address.into(), &new_vault)
.map_err(|e| errors::account("Could not change vault.", e))
.map(|_| true)
}
fn get_vault_meta(&self, name: String) -> Result<String, Error> {
take_weak!(self.accounts)
self.account_provider()?
.get_vault_meta(&name)
.map_err(|e| errors::account("Could not get vault metadata.", e))
}
fn set_vault_meta(&self, name: String, meta: String) -> Result<bool, Error> {
take_weak!(self.accounts)
self.account_provider()?
.set_vault_meta(&name, &meta)
.map_err(|e| errors::account("Could not update vault metadata.", e))
.map(|_| true)
@ -293,7 +300,7 @@ impl ParityAccounts for ParityAccountsClient {
fn derive_key_index(&self, addr: RpcH160, password: String, derivation: DeriveHierarchical, save_as_account: bool) -> Result<RpcH160, Error> {
let addr: Address = addr.into();
take_weak!(self.accounts)
self.account_provider()?
.derive_account(
&addr,
Some(password),
@ -306,7 +313,7 @@ impl ParityAccounts for ParityAccountsClient {
fn derive_key_hash(&self, addr: RpcH160, password: String, derivation: DeriveHash, save_as_account: bool) -> Result<RpcH160, Error> {
let addr: Address = addr.into();
take_weak!(self.accounts)
self.account_provider()?
.derive_account(
&addr,
Some(password),
@ -319,7 +326,7 @@ impl ParityAccounts for ParityAccountsClient {
fn export_account(&self, addr: RpcH160, password: String) -> Result<KeyFile, Error> {
let addr = addr.into();
take_weak!(self.accounts)
self.account_provider()?
.export_account(
&addr,
password,

View File

@ -26,39 +26,44 @@ use futures::{future, Future, BoxFuture};
use jsonrpc_core::Error;
use v1::helpers::errors;
use v1::helpers::dispatch::{Dispatcher, SignWith};
use v1::helpers::accounts::unwrap_provider;
use v1::traits::Personal;
use v1::types::{H160 as RpcH160, H256 as RpcH256, U128 as RpcU128, TransactionRequest};
use v1::metadata::Metadata;
/// Account management (personal) rpc implementation.
pub struct PersonalClient<D: Dispatcher> {
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
dispatcher: D,
allow_perm_unlock: bool,
}
impl<D: Dispatcher> PersonalClient<D> {
/// Creates new PersonalClient
pub fn new(store: &Arc<AccountProvider>, dispatcher: D, allow_perm_unlock: bool) -> Self {
pub fn new(store: &Option<Arc<AccountProvider>>, dispatcher: D, allow_perm_unlock: bool) -> Self {
PersonalClient {
accounts: Arc::downgrade(store),
accounts: store.as_ref().map(Arc::downgrade),
dispatcher: dispatcher,
allow_perm_unlock: allow_perm_unlock,
}
}
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
}
impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
type Metadata = Metadata;
fn accounts(&self) -> Result<Vec<RpcH160>, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let accounts = store.accounts().map_err(|e| errors::account("Could not fetch accounts.", e))?;
Ok(accounts.into_iter().map(Into::into).collect::<Vec<RpcH160>>())
}
fn new_account(&self, pass: String) -> Result<RpcH160, Error> {
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
store.new_account(&pass)
.map(Into::into)
@ -67,7 +72,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
fn unlock_account(&self, account: RpcH160, account_pass: String, duration: Option<RpcU128>) -> Result<bool, Error> {
let account: Address = account.into();
let store = take_weak!(self.accounts);
let store = self.account_provider()?;
let duration = match duration {
None => None,
Some(duration) => {
@ -96,7 +101,7 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
fn send_transaction(&self, meta: Metadata, request: TransactionRequest, password: String) -> BoxFuture<RpcH256, Error> {
let dispatcher = self.dispatcher.clone();
let accounts = take_weakf!(self.accounts);
let accounts = try_bf!(self.account_provider());
let default = match request.from.as_ref() {
Some(account) => Ok(account.clone().into()),

View File

@ -26,13 +26,14 @@ use futures::{future, BoxFuture, Future, IntoFuture};
use jsonrpc_core::Error;
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
use v1::helpers::dispatch::{self, Dispatcher, WithToken};
use v1::helpers::accounts::unwrap_provider;
use v1::traits::Signer;
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes};
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<D: Dispatcher> {
signer: Weak<SignerService>,
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
dispatcher: D
}
@ -40,17 +41,21 @@ impl<D: Dispatcher + 'static> SignerClient<D> {
/// Create new instance of signer client.
pub fn new(
store: &Arc<AccountProvider>,
store: &Option<Arc<AccountProvider>>,
dispatcher: D,
signer: &Arc<SignerService>,
) -> Self {
SignerClient {
signer: Arc::downgrade(signer),
accounts: Arc::downgrade(store),
accounts: store.as_ref().map(Arc::downgrade),
dispatcher: dispatcher,
}
}
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
fn confirm_internal<F, T>(&self, id: U256, modification: TransactionModification, f: F) -> BoxFuture<WithToken<ConfirmationResponse>, Error> where
F: FnOnce(D, Arc<AccountProvider>, ConfirmationPayload) -> T,
T: IntoFuture<Item=WithToken<ConfirmationResponse>, Error=Error>,
@ -60,7 +65,7 @@ impl<D: Dispatcher + 'static> SignerClient<D> {
let dispatcher = self.dispatcher.clone();
let setup = || {
Ok((take_weak!(self.accounts), take_weak!(self.signer)))
Ok((self.account_provider()?, take_weak!(self.signer)))
};
let (accounts, signer) = match setup() {

View File

@ -30,6 +30,7 @@ use v1::helpers::{
SIGNING_QUEUE_LIMIT, SigningQueue, ConfirmationPromise, ConfirmationResult, SignerService
};
use v1::helpers::dispatch::{self, Dispatcher};
use v1::helpers::accounts::unwrap_provider;
use v1::metadata::Metadata;
use v1::traits::{EthSigning, ParitySigning};
use v1::types::{
@ -55,7 +56,7 @@ enum DispatchResult {
/// Implementation of functions that require signing when no trusted signer is used.
pub struct SigningQueueClient<D> {
signer: Weak<SignerService>,
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
dispatcher: D,
pending: Arc<Mutex<TransientHashMap<U256, ConfirmationPromise>>>,
}
@ -91,17 +92,21 @@ fn collect_garbage(map: &mut TransientHashMap<U256, ConfirmationPromise>) {
impl<D: Dispatcher + 'static> SigningQueueClient<D> {
/// Creates a new signing queue client given shared signing queue.
pub fn new(signer: &Arc<SignerService>, dispatcher: D, accounts: &Arc<AccountProvider>) -> Self {
pub fn new(signer: &Arc<SignerService>, dispatcher: D, accounts: &Option<Arc<AccountProvider>>) -> Self {
SigningQueueClient {
signer: Arc::downgrade(signer),
accounts: Arc::downgrade(accounts),
accounts: accounts.as_ref().map(Arc::downgrade),
dispatcher: dispatcher,
pending: Arc::new(Mutex::new(TransientHashMap::new(MAX_PENDING_DURATION_SEC))),
}
}
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
fn dispatch(&self, payload: RpcConfirmationPayload, default_account: DefaultAccount, origin: Origin) -> BoxFuture<DispatchResult, Error> {
let accounts = take_weakf!(self.accounts);
let accounts = try_bf!(self.account_provider());
let default_account = match default_account {
DefaultAccount::Provided(acc) => acc,
DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(),

View File

@ -24,6 +24,7 @@ use futures::{future, BoxFuture, Future};
use jsonrpc_core::Error;
use v1::helpers::{errors, DefaultAccount};
use v1::helpers::dispatch::{self, Dispatcher};
use v1::helpers::accounts::unwrap_provider;
use v1::metadata::Metadata;
use v1::traits::{EthSigning, ParitySigning};
use v1::types::{
@ -38,21 +39,25 @@ use v1::types::{
/// Implementation of functions that require signing when no trusted signer is used.
pub struct SigningUnsafeClient<D> {
accounts: Weak<AccountProvider>,
accounts: Option<Weak<AccountProvider>>,
dispatcher: D,
}
impl<D: Dispatcher + 'static> SigningUnsafeClient<D> {
/// Creates new SigningUnsafeClient.
pub fn new(accounts: &Arc<AccountProvider>, dispatcher: D) -> Self {
pub fn new(accounts: &Option<Arc<AccountProvider>>, dispatcher: D) -> Self {
SigningUnsafeClient {
accounts: Arc::downgrade(accounts),
accounts: accounts.as_ref().map(Arc::downgrade),
dispatcher: dispatcher,
}
}
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
fn handle(&self, payload: RpcConfirmationPayload, account: DefaultAccount) -> BoxFuture<RpcConfirmationResponse, Error> {
let accounts = take_weakf!(self.accounts);
let accounts = try_bf!(self.account_provider());
let default = match account {
DefaultAccount::Provided(acc) => acc,
DefaultAccount::ForDapp(dapp) => accounts.dapp_default_address(dapp).ok().unwrap_or_default(),

View File

@ -84,15 +84,16 @@ impl EthTester {
let client = blockchain_client();
let sync = sync_provider();
let ap = accounts_provider();
let opt_ap = Some(ap.clone());
let miner = miner_service();
let snapshot = snapshot_service();
let hashrates = Arc::new(Mutex::new(HashMap::new()));
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
let eth = EthClient::new(&client, &snapshot, &sync, &ap, &miner, &external_miner, options).to_delegate();
let eth = EthClient::new(&client, &snapshot, &sync, &opt_ap, &miner, &external_miner, options).to_delegate();
let filter = EthFilterClient::new(&client, &miner).to_delegate();
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
let sign = SigningUnsafeClient::new(&ap, dispatcher).to_delegate();
let sign = SigningUnsafeClient::new(&opt_ap, dispatcher).to_delegate();
let mut io: IoHandler<Metadata> = IoHandler::default();
io.extend_with(eth);
io.extend_with(sign);

View File

@ -72,13 +72,15 @@ impl Dependencies {
}
pub fn client(&self, signer: Option<Arc<SignerService>>) -> TestParityClient {
let opt_accounts = Some(self.accounts.clone());
ParityClient::new(
&self.client,
&self.miner,
&self.sync,
&self.updater,
&self.network,
&self.accounts,
&opt_accounts,
self.logger.clone(),
self.settings.clone(),
signer,

View File

@ -40,7 +40,8 @@ fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider
}
fn setup_with_accounts_provider(accounts_provider: Arc<AccountProvider>) -> ParityAccountsTester {
let parity_accounts = ParityAccountsClient::new(&accounts_provider);
let opt_ap = Some(accounts_provider.clone());
let parity_accounts = ParityAccountsClient::new(&opt_ap);
let mut io = IoHandler::default();
io.extend_with(parity_accounts.to_delegate());

View File

@ -51,11 +51,12 @@ fn miner_service() -> Arc<TestMinerService> {
fn setup() -> PersonalTester {
let accounts = accounts_provider();
let opt_accounts = Some(accounts.clone());
let client = blockchain_client();
let miner = miner_service();
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
let personal = PersonalClient::new(&accounts, dispatcher, false);
let personal = PersonalClient::new(&opt_accounts, dispatcher, false);
let mut io = IoHandler::default();
io.extend_with(personal.to_delegate());

View File

@ -57,12 +57,13 @@ fn miner_service() -> Arc<TestMinerService> {
fn signer_tester() -> SignerTester {
let signer = Arc::new(SignerService::new_test(None));
let accounts = accounts_provider();
let opt_accounts = Some(accounts.clone());
let client = blockchain_client();
let miner = miner_service();
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
let mut io = IoHandler::default();
io.extend_with(SignerClient::new(&accounts, dispatcher, &signer).to_delegate());
io.extend_with(SignerClient::new(&opt_accounts, dispatcher, &signer).to_delegate());
SignerTester {
signer: signer,

View File

@ -51,13 +51,14 @@ impl Default for SigningTester {
let client = Arc::new(TestBlockChainClient::default());
let miner = Arc::new(TestMinerService::default());
let accounts = Arc::new(AccountProvider::transient_provider());
let opt_accounts = Some(accounts.clone());
let mut io = IoHandler::default();
let dispatcher = FullDispatcher::new(Arc::downgrade(&client), Arc::downgrade(&miner));
let rpc = SigningQueueClient::new(&signer, dispatcher.clone(), &accounts);
let rpc = SigningQueueClient::new(&signer, dispatcher.clone(), &opt_accounts);
io.extend_with(EthSigning::to_delegate(rpc));
let rpc = SigningQueueClient::new(&signer, dispatcher, &accounts);
let rpc = SigningQueueClient::new(&signer, dispatcher, &opt_accounts);
io.extend_with(ParitySigning::to_delegate(rpc));
SigningTester {